===================
Models and Managers
===================

.. admonition:: About this document

   This document describes the changes Django MPTT makes to your Django
   model classes and the custom tree manager it provides, so you can
   easily work with model instances as trees.

.. contents::
   :depth: 3

``mptt.register`` - setting up a Django model for MPTT
======================================================

The ``mptt`` module contains a ``register`` function, which users can
call to set a Django model up for MPTT. This function takes as its
arguments the model class itself and additional options related to
working with the model as a tree.

.. admonition:: ``mptt.register`` and ``syncdb``

   As discussed further below, the ``mptt.register`` function will
   dynamically add new fields to your models if certain required tree
   fields are not present.

   As such, unless you are manually setting up all required tree fields
   in your models, you must have the call to ``mptt.register`` in place
   **before** you use Django's ``syncdb`` management command to create
   tables. Otherwise, you will have to recreate tables so the
   appropriate tree columns are in place in the database.

The following argument is required:

``model``
   The model class which is to be set up for MPTT.

All remaining arguments are optional, but you should take care to
specify appropriate fields names where the default field names do not
fit with your model class' definition:

``parent_attr``
   The name of a field which relates the model back to itself such that
   each instance can be a child of another instance. Defaults to
   ``'parent'``.

   Users are responsible for setting this field up on the model class,
   which can be done like so::

      parent = models.ForeignKey('self', null=True, blank=True, related_name='children')

For the following four arguments, if fields with the given names do not
exist, they will be added to the model dynamically:

``left_attr``
   The name of a field which contains the left tree node edge indicator,
   which should be a ``PositiveIntegerField``. Defaults to ``'lft'``.

``right_attr``
   The name of a field which contains the right tree node edge
   indicator, which should be a ``PositiveIntegerField``. Defaults to
   ``'rght'``.

``tree_id_attr``
   The name of a field which contains the tree id of each node, which
   should be a ``PositiveIntegerField``. Defaults to ``'tree_id'``.

   Items which do not have a parent are considered to be "root" nodes in
   the tree and will be allocated a new tree id. All descendants of root
   nodes will be given the same tree id as their root node.

``level_attr``
   The name of a field which contains the (zero-based) level at which an
   item sits in the tree, which should be a ``PositiveIntegerField``.
   Defaults to ``'level'``.

   For example, root nodes would have a level of ``0`` and their
   immediate children would have have a level of ``1``.

``tree_manager_attr``
   The name of an attribute which will hold a custom manager which is
   used to work with trees of model instances. Defaults to ``'tree'``.

``order_insertion_by``
   The name of a field which should define ordering when new tree nodes
   are being inserted or existing nodes are being reparented. Defaults
   to ``None``.

   Note that this will require an extra database query to determine
   where nodes should be positioned when they are being saved. This
   option is handy if you're maintaining mostly static structures, such
   as trees of categories, which should always be in alphabetical order.

.. _`minimal example usage`:

A mimimal example usage of ``mptt.register`` is given below, where the
model being set up for MPTT is suitable for use with the default
arguments which specify fields and the tree manager attribute::

   from django.db import models

   import mptt

   class Genre(models.Model):
       name = models.CharField(max_length=50, unique=True)
       parent = models.ForeignKey('self', null=True, blank=True, related_name='children')

   mptt.register(Genre, order_insertion_by='name')


Model instance methods added by Django MPTT
===========================================

The following instance methods will be added to your Django models when
you set them up for MPTT:

* ``get_ancestors(ascending=False)`` -- creates a ``QuerySet``
  containing the ancestors of the model instance.

  These default to being in descending order (root ancestor first,
  immediate parent last); passing ``True`` for the ``ascending``
  argument will reverse the ordering (immediate parent first, root
  ancestor last).

* ``get_children()`` -- creates a ``QuerySet`` containing the immediate
  children of the model instance, in tree order.

  The benefit of using this method over the reverse relation provided
  by the ORM to the instance's children is that a database query can be
  avoided in the case where the instance is a leaf node (it has no
  children).

* ``get_descendants(include_self=False)`` -- creates a ``QuerySet``
  containing descendants of the model instance, in tree order.

  If ``include_self`` is ``True``, the ``QuerySet`` will also include
  the model instance itself.

* ``get_descendant_count()`` -- returns the number of descendants the
  model instance has, based on its left and right tree node edge
  indicators. As such, this does not incur any database access.

* ``get_next_sibling()`` -- returns the model instance's next sibling
  in the tree, or ``None`` if it doesn't have a next sibling.

* ``get_previous_sibling()`` -- returns the model instance's previous
  sibling in the tree, or ``None`` if it doesn't have a previous
  sibling.

* ``get_root()`` -- returns the root node of the model instance's tree.

* ``get_siblings(include_self=False)`` -- creates a ``QuerySet``
  containing siblings of the model instance. Root nodes are considered
  to be siblings of other root nodes.

  If ``include_self`` is ``True``, the ``QuerySet`` will also include
  the model instance itself.

* ``insert_at(target, position='first-child', commit=False)`` --
  positions the model instance (which must not yet have been inserted
  into the database) in the tree based on ``target`` and ``position``
  (when appropriate).

  If ``commit`` is True, the model instance's ``save()`` method will be
  called before the instance is returned.

* ``is_child_node()`` -- returns ``True`` if the model instance is a
  child node, ``False`` otherwise.

* ``is_leaf_node()`` -- returns ``True`` if the model instance is a leaf
  node (it has no children), ``False`` otherwise.

* ``is_root_node()`` -- returns ``True`` if the model instance is a root
  node, ``False`` otherwise.

.. _`move_to documentation`:

* ``move_to(target, position='first-child')`` -- moves the model
  instance elsewhere in the tree based on ``target`` and ``position``
  (when appropriate).

  .. note::
     Is is assumed that when you call this method, the tree fields in
     the instance you've called it on, and in any ``target`` instance passed
     in, reflect the current state of the database.

     Modifying the tree fields manually before calling this method or
     using tree fields which are out of sync with the database can
     result in the tree structure being put into an inaccurate state.

  If ``target`` is another model instance, it will be used to determine
  the type of movement which needs to take place, and will be used as
  the basis for positioning the model when it is moved, in combination
  with the ``position`` argument.

  A ``target`` of ``None`` indicates that the model instance should be
  turned into a root node. The ``position`` argument is disregarded in
  this case.

  Valid values for the ``position`` argument and their effects on
  movement are:

     ``'first-child'``
        The instance being moved should have ``target`` set as its new
        parent and be placed as its *first* child in the tree structure.

     ``'last-child'``
        The instance being moved should have ``target`` set as its new
        parent and be placed as its *last* child in the tree structure.

     ``'left'``
        The instance being moved should have ``target``'s parent set as
        its new parent and should be placed *directly before* ``target``
        in the tree structure.

     ``'right'``
        The instance being moved should have ``target``'s parent set as
        its new parent and should be placed *directly after* ``target``
        in the tree structure.

  A ``ValueError`` will be raised if an invalid value is given for the
  ``position`` argument.

  Note that some of the moves you could attempt to make with this method
  are invalid - for example, trying to make an instance be its own its
  own child or the child of one of its descendants. In these cases, a
  ``mptt.exceptions.InvalidMove`` exception will be raised.

  The instance itself will be also modified as a result of this call, to
  reflect the state of its updated tree fields in the database, so it's
  safe to go on to save it or use its tree fields after you've called
  this method.


The ``TreeManager`` custom manager
==================================

A custom manager, ``TreeManager`` is also added to your Django models
when you set them up for MPTT - the attribute this manager can be
accessed through is specified by the ``tree_manager_attr`` argument to
``mptt.register``.

Any ``QuerySet`` created with this manager will be ordered based on the
tree structure, with root nodes appearing in tree id order and and their
descendants being ordered in a depth-first fashion.

The following manager methods are available:

* ``add_related_count(queryset, rel_cls, rel_field, count_attr, cumulative=False)``
  -- adds a related item count to a given ``QuerySet`` using its
  `extra method`_, for a model which has a relation to this manager's
  model.

  ``rel_cls``
     A Django model class which has a relation to this manager's model.

  ``rel_field``
     The name of the field in ``rel_cls`` which holds the
     relation.

  ``count_attr``
     The name of an attribute which should be added to each item in
     this ``QuerySet``, containing a count of how many instances
     of ``rel_cls`` are related to it through ``rel_field``.

  ``cumulative``
     If ``True``, the count will be for each item and all of its
     descendants, otherwise it will be for each item itself.

* ``get_root(tree_id)`` -- returns the root node of tree with the given
  id.

* ``insert_node(node, target, position='last-child', commit=False)`` --
  sets up the tree state for ``node`` (which has not yet been inserted
  into in the database) so it will be positioned relative to a given
  ``target`` node as specified by ``position`` (when appropriate) when
  it is inserted, with any neccessary space already having been made for
  it.

  A ``target`` of ``None`` indicates that ``node`` should be the last
  root node.

  If ``commit`` is ``True``, ``node``'s ``save()`` method will be
  called before it is returned.

* ``move_node(node, target, position='last-child')`` -- moves ``node``
  based on ``target``, relative to ``position`` when appropriate.

  A ``target`` of ``None`` indicates that ``node`` should be removed
  from its current position and turned into a root node. If ``node`` is
  a root node in this case, no action will be taken.

  The given ``node`` will be modified to reflect its new tree state in
  the database.

  For more details, see the `move_to documentation`_ above.

* ``root_nodes()`` -- creates a ``QuerySet`` containing root nodes.

.. _`extra method`: http://www.djangoproject.com/documentation/db-api/#extra-select-none-where-none-params-none-tables-none

Example usage
-------------

In the following examples, we have ``Category`` and ``Question`` models.
``Question`` has a ``category`` field which is a ``ForeignKey`` to
``Category``.

Retrieving a list of root Categories which have a ``question_count``
attribute containing the number of Questions associated with each root
and all of its descendants::

   roots = Category.tree.add_related_count(Category.tree.root_nodes(), Question,
                                           'category', 'question_counts',
                                           cumulative=True)

Retrieving a list of child Categories which have a ``question_count``
attribute containing the number of Questions associated with each of
them::

   node = Category.objects.get(name='Some Category')
   children = Category.tree.add_related_count(node.get_children(), Question,
                                              'category', 'question_counts')
