Current version: 0.5

Welcome to the documentation of django-modeltree#

Note

While modeltree is functional and well tested, it’s in an early state of its development. Backward incompatible api changes are possible. Feedback and suggestions about the api are very welcome. Just open an issue on github.

About#

Do you have a model layout with various relations and looking for a way to navigate it with ease? Then django-modeltree is what you are looking for. Build your modeltree in a single line and accessing related models and their objects in a elegant and performant way. No need for complex query building anymore. Give it a try…

What is a ModelTree?#

A ModelTree describes a Model and all its recursive relations to other models. It is Node based, iterable, walkable, searchable and can be populated by items.

Guess you have these models:

class ModelOne(models.Model):
    model_two = models.ManyToManyField(
        'ModelTwo',
        related_name='model_one',
        blank=True)

class ModelTwo(models.Model):
    model_three = models.ForeignKey(
        'ModelThree',
        related_name='model_two',
        blank=True, null=True,
        on_delete=models.SET_NULL)

class ModelThree(models.Model):
    pass

class ModelFour(models.Model):
    model_three = models.OneToOneField(
        'ModelThree',
        related_name='model_four',
        blank=True, null=True,
        on_delete=models.SET_NULL)

class ModelFive(models.Model):
    model_two = models.ManyToManyField(
        'ModelThree',
        related_name='model_five',
        blank=True)

Then a tree representing these models will look like:

>>> tree = ModelTree(ModelOne)
>>> tree.show()
ModelOne
└── model_two -> ModelTwo
    └── model_three -> ModelThree
        ├── model_four -> ModelFour
        └── model_five -> ModelFive

Or rendered by using the field_path attribute:

>>> tree = ModelTree(ModelOne)
>>> tree.show('{node.field_path}')
ModelOne
└── model_two
    └── model_two__model_three
        ├── model_two__model_three__model_four
        └── model_two__model_three__model_five

How to build a ModelTree?#

This is very easy. Simply pass in the model the tree should be rooted to:

tree = ModelTree(ModelOne)

Optionally you can pass in a queryset of your model:

>>> items = ModelOne.objects.all()
>>> tree = ModelTree(ModelOne, items)

You will be able then to recieve a queryset of related objects of the models of your modeltree by accessing the items attribute of each node:

>>> items = ModelOne.objects.all()
>>> tree = ModelTree(ModelOne, items)
>>> len(tree['model_two']['model_three']['model_five'].items)
3

See the items section for more information about how items are processed.

How to navigate a modeltree?#

A model tree has a simple dict api implementation that allows you to access each child node by the name of its field attribute as key:

>>> tree = ModelTree(ModelOne)
>>> tree['model_two'].field.name
'model_two'

Building your tree is a one-liner but gives you access to all related models and model objects at once - regardless of the direction or type of the involved relations:

>>> tree = ModelTree(ModelOne, ModelOne.objects.filter(pk__in=[1, 2, 3]))
>>> tree['model_two']['model_three'].model
<class 'testapp.models.ModelThree'>
>>> tree['model_two']['model_three'].items
<QuerySet [<ModelThree: ModelThree object (0)>, <ModelThree: ModelThree object (1)>]>

For further options to access related tree nodes see get() and find().

How to customize my modeltree?#

You can easily adjust the way your tree is build up. Therefore overwrite one or more of the following class attributes in your ModelTree subclass:

Guess you whish to only follow specific relation-types:

>>> class MyModelTree(ModelTree):
...     RELATION_TYPES = [
...         'many_to_many',
...         'many_to_one',
...     ]
...
>>> tree = MyModelTree(ModelOne)
>>> tree.show()
ModelOne
└── model_two -> ModelTwo
    └── model_three -> ModelThree
        └── model_five -> ModelFive

For further adjustments you could also overwrite the private _follow() method. See the description below.


Api reference#

class modeltree.ModelTree(model, items=None, field=None, **kwargs)#

A ModelTree is technical a Subclass of AnyNode, that builds its own children nodes based on the recursives model relations. Means you just have to pass in a model and get a complete tree of this model and all its relations:

>>> tree = ModelTree(ModelOne)
>>> tree.show()
ModelOne
└── model_two -> ModelTwo
    └── model_three -> ModelThree
        ├── model_four -> ModelFour
        └── model_five -> ModelFive

In advance you can pass in some items of your model as a queryset. The items property of each node of your tree then reflects the items that are derived from the initial items by the direct or indirect relations of their models.

>>> items = ModelOne.objects.all()
>>> tree = ModelTree(ModelOne, items)

Guess you want all ModelFour items that are related to the ModelOne items with which you initiated your tree:

>>> items = ModelOne.objects.all()
>>> tree = ModelTree(ModelOne, items)
>>> model_four_node = tree.get(filter=lambda n: n.model == ModelFour)
>>> len(model_four_node.items)
2
Parameters
  • model (Model) – model to start with

  • items (QuerySet) – model items (optional)

MAX_DEPTH = 3#

Max depth of the tree structure.

FOLLOW_ACROSS_APPS = False#

Follow relations across different apps.

RELATION_TYPES = ['one_to_one', 'one_to_many', 'many_to_one', 'many_to_many']#

A list of relation-types as strings to follow when building the tree. By default all relation-types will be followed:

RELATION_TYPES = [
    'one_to_one',
    'one_to_many',
    'many_to_one',
    'many_to_many',
]
FIELD_TYPES = [<class 'django.db.models.fields.related.OneToOneField'>, <class 'django.db.models.fields.reverse_related.OneToOneRel'>, <class 'django.db.models.fields.related.ForeignKey'>, <class 'django.db.models.fields.reverse_related.ManyToOneRel'>, <class 'django.db.models.fields.related.ManyToManyField'>, <class 'django.db.models.fields.reverse_related.ManyToManyRel'>]#

A list of field-types to follow when building the tree. By default all field-types and their reverse field-types are followed:

FIELD_TYPES = [
    models.OneToOneField,
    models.OneToOneRel,
    models.ForeignKey,
    models.ManyToOneRel,
    models.ManyToManyField,
    models.ManyToManyRel,
]

Note

Generic relations using the contenttypes framework are not supported.

FIELD_PATHS = None#

A list of field_paths to follow when building the tree. Intermediated field-paths will be complemented. Guess you have this field-path specified:

FIELD_PATHS = [
    'model_two__model_three__model_four',
]

Then in effect the tree will be build by following these paths altogether:

  • model_two

  • model_two__model_three

  • model_two__model_three__model_four

By default all field-paths will be followed.

MODELS = None#

A list of models to follow when building the tree.

MODELS = [
    ModelOne,
    ModelTwo,
    ModelThree
]

By default all models will be followed.

property model#

django.db.models.Model of the node.

property field#

The relation field of the parent node’s model leading to this node. This is None for the root node.

property relation_type#

String describing the relation type of field. See RELATION_TYPES for possible values. This is an empty string for the root node.

property field_path#

String describing the node’s path using the name of the node’s field:

>>> node_four = list(tree.iterate())[3]
>>> node_four.field_path
'model_two__model_three__model_four'

Since the root-modelnode has no field by its own it is represented by the string ‘root’:

>>> tree.root.field_path
'root'
property items#

If the ModelTree was initiated with a QuerySet it will be the items attribute of the root node. All child nodes hold a queryset of elements that are derived of the initial one:

>>> items_one = ModelOne.objects.all()
>>> items_two = ModelTwo.objects.filter(
...     model_one__pk__in=items_one.values_list('pk', flat=True))
>>> items_three = ModelThree.objects.filter(
...     model_two__pk__in=items_two.values_list('pk', flat=True))
>>> tree = ModelTree(ModelOne, items_one)
>>> node_three = tree.get('model_two__model_three')
>>> set(node_three.items) == set(items_three)
True

Items of a node are lazy. Querysets are evaluated not until an items attribute is accessed. And only for those nodes that link the current one with the root node. For each intermediated node the database will be hit once.

If no queryset was passed for ModelTree initiation, then items of all nodes will be None.

render()#

Return a RenderTree instance for self.

Returns

instance of RenderTree

show(format='{node}', root_format='{node}', with_items=False)#

Print a tree. Each node will be rendered by using a format string which reference the node object by the key node:

>>> tree = ModelTree(ModelOne)
>>> tree.show('{node.field.model._meta.object_name}.{node.field.name} -> {node.model._meta.object_name}')
ModelOne
└── ModelOne.model_two -> ModelTwo
    └── ModelTwo.model_three -> ModelThree
        ├── ModelThree.model_four -> ModelFour
        └── ModelThree.model_five -> ModelFive

A format string referencing the field attribute won’t be appropriate for the root node since it has no field. To adjust the way the root node is rendered use the root_format keyword argument:

>>> tree = ModelTree(ModelOne)
>>> tree.show(root_format='<{node.model._meta.verbose_name}>')
<model one>
└── model_two -> ModelTwo
    └── model_three -> ModelThree
        ├── model_four -> ModelFour
        └── model_five -> ModelFive

Use the show_items argument to render the tree with each node’s items listed below the node:

>>> tree = ModelTree(ModelTwo, ModelTwo.objects.filter(pk=4))
>>> tree.show(with_items=True)
ModelTwo
│ ~ ModelTwo object (4)
├── model_one -> ModelOne
│     ~ ModelOne object (2)
└── model_three -> ModelThree
    │ ~ ModelThree object (0)
    ├── model_four -> ModelFour
    │     ~ ModelFour object (0)
    └── model_five -> ModelFive
          ~ ModelFive object (3)
          ~ ModelFive object (4)
          ~ ModelFive object (5)
Parameters
  • format (str) – format string to render a node object (optional)

  • root_format (str) – format string to render the root node object (optional)

  • with_items (bool) – include the node’s items (optional)

get(field_path=None, filter=None, **params)#

Either lookup a node by its field_path or a filter or node attributes:

>>> tree = ModelTree(ModelOne)
>>> tree.get('model_two') == tree.get(filter=lambda n: n.model == ModelTwo)
True
>>> tree.get('model_two') == tree.get(model=ModelTwo)
True
Parameters
  • field_path (str) – a node’s field_path (optional)

  • filter (callable) – callable taking a node and returning a boolean (optional)

  • **params – any node attribute to use for a lookup (optional)

Returns

node object or None if no node matches the search parameters

Return type

ModelTree or None

Raises

CountError: if more than one node was found

find(filter=None, **params)#

Find nodes using a filter or node attributes:

>>> tree = ModelTree(ModelOne)
>>> tree.find(lambda n: n.model == ModelTwo) == tree.find(model=ModelTwo)
True
>>> len(tree.find(lambda n: n.relation_type == 'many_to_many'))
2
>>> len(tree.find(lambda n: type(n.field) == models.OneToOneRel))
1
Parameters
  • filter (callable) – callable taking a node and returning a boolean (optional)

  • **params – any node attribute to use for a lookup (optional)

Returns

tuple of nodes

iterate(by_level=False, by_grouped_level=False, maxlevel=None, has_items=False, filter=None)#

Return a tree iterator using the iteration classes of anytree:

By default an instance of the ProOrderIter class will be returned.

Parameters
  • by_level (bool) – use the LevelOrderIter class

  • by_grouped_level (bool) – use the LevelOrderGroupIter class

  • maxlevel (int) – maximum iteration level

  • has_items (bool) – iterate only over nodes with more than 0 items

  • filter (callable) – a callable recieving the node and returning a boolean.

Returns

iterator

ModelTree._follow(field)#

To fine-tune the way a tree is build overwrite this method. You can do what ever you want evaluating a field. Guess you only want to build your tree for specific django-apps:

>>> class MyModelTree(ModelTree):
...    FOLLOW_ACROSS_APPS = True
...    def _follow(self, field):
...       if field.related_model._meta.app_label in ['testapp']:
...          return True
...       else:
...          return False
...
>>> tree = MyModelTree(ModelOne)
>>> all(n.model._meta.app_label == 'testapp' for n in tree.iterate())
True
Parameters

field (a model's relation field) – the field of model to follow building the tree

Return boolean

True to follow, False to not follow


Indices and tables#