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 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
- 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_path
s 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
. SeeRELATION_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’sfield
:>>> 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 theitems
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