========= Tag Trees ========= Tags can be nested using tag trees for detailed categorisation, with tags having parents, children and siblings. Tags in tag trees denote parents using the forward slash character (``/``). For example, ``Animal/Mammal/Cat`` is a ``Cat`` with a parent of ``Mammal`` and grandparent of ``Animal``. To use a slash in a tag name, escape it with a second slash; for example the tag name ``Animal/Vegetable`` can be entered as ``Animal//Vegetable``. A custom tag tree model must be a subclass of :ref:`tagtreemodel` instead of the normal :ref:`tagmodel`; for automatically-generated tag models, this is managed by setting the :ref:`option_tree` field option to ``True``. Tag Tree Model Classes ====================== .. _tagtreemodel: ``tagulous.models.TagTreeModel`` -------------------------------- Because tree tag names are fully qualified (include all ancestors) and unique, there is no difference to normal tags in how they are set or compared. A ``TagTreeModel`` subclasses :ref:`tagmodel`; it inherits all the normal fields and methods, and adds the following: .. note:: Field values are computed and set automatically in the ``save()`` method - so don't try to use them until the tag has been saved. ``parent`` ~~~~~~~~~~ A ``ForeignKey`` to the parent tag. Tagulous sets this automatically when saving, creating missing ancestors as needed. ``children`` ~~~~~~~~~~~~ The reverse relation manager for ``parent``, eg ``mytag.children.all()``. ``label`` ~~~~~~~~~ A ``CharField`` containing the name of the tag without its ancestors. Example: a tag named ``Animal/Mammal/Cat`` has the label ``Cat`` ``slug`` ~~~~~~~~ A ``SlugField`` containing the slug for the tag label. Example: a tag named ``Animal/Mammal/Cat`` has the slug ``cat`` .. _model_path: ``path`` ~~~~~~~~ A ``TextField`` containing the path for this tag - this slug, plus all ancestor slugs, separated by the ``/`` character, suitable for use in URLs. Tagulous sets this automatically when saving. Example: a tag named ``Animal/Mammal/Cat`` has the path ``animal/mammal/cat`` ``level`` ~~~~~~~~~ An ``IntegerField`` containing the level of this tag in the tree (starting from 1). .. _tagtreemodel_merge_tags: ``merge_tags(tags, children=False)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Merge the specified tags into this tag. ``tags`` can be a queryset, list of tags or tag names, or a tag string. If ``children=False``, only the specified tags will be merged; tagged items will be reassigned to this tag, but if there are child tags they will not be touched. If child tags do exist, although the merged tags' counts will be 0, they will not be cleared. If ``children=True``, child tags will be merged into children of this tag, retaining structure; eg merging ``Pet`` into ``Animal`` will merge ``Pet/Mammal`` into ``Animal/Mammal``, ``Pet/Mammal/Cat`` into ``Animal/Mammal/Cat`` etc. Tags will be created if they don't exist. ``get_ancestors()`` ~~~~~~~~~~~~~~~~~~~ Returns a queryset of all ancestors, ordered by level. ``get_descendants()`` ~~~~~~~~~~~~~~~~~~~~~ Returns a queryset of all descendants, ordered by level. ``get_siblings()`` ~~~~~~~~~~~~~~~~~~~~~ Returns a queryset of all siblings, ordered by name. This includes the node itself; if you don't want it in the results, exclude it afterwards, eg:: siblings = node.get_siblings().exclude(pk=node.pk) .. _tagtreemodel_manager: ``tagulous.models.TagTreeModelManager`` --------------------------------------- A ``TagTreeModelManager`` is the standard manager for a :ref:`tagtreemodel`; it is a subclass of :ref:`tagmodel_manager` so provides those methods, but its queries return a :ref:`tagtreemodel_queryset` instead. .. _tagtreemodel_queryset: ``tagulous.models.TagTreeModelQuerySet`` ---------------------------------------- This is returned by the :ref:`tagtreemodel_manager`; it is a subclass of :ref:`tagmodel_queryset` so provides those methods, but also: ``with_ancestors()`` ~~~~~~~~~~~~~~~~~~~~ Returns a new queryset containing the nodes from the calling queryset, plus their ancestor nodes. ``with_descendants()`` ~~~~~~~~~~~~~~~~~~~~~~ Returns a new queryset containing the nodes from the calling queryset, plus their descendant nodes. ``with_siblings()`` ~~~~~~~~~~~~~~~~~~~ Returns a new queryset containing the nodes from the calling queryset, plus theirm sibling nodes. .. _converting_tag_trees: Converting from to tree tags from normal tags ============================================= When converting from a normal tag model to a tag tree model, you will need to add extra fields. One of those (``path``) is a unique field, which means extra steps are needed to build the migration. These instructions will convert an existing ``TagModel`` to a ``TagTreeModel``. Look through the code snippets and change the app and model names as required: 1. Create a data migration to escape the tag names. You can skip this step if you have been using slashes in normal tags and want them to be converted to nested tree nodes. Run ``manage.py makemigrations myapp --empty`` and add:: def escape_tag_names(apps, schema_editor): model = apps.get_model('myapp', 'Tagulous_MyModel_Tags') for tag in model.objects.all(): tag.name = tag.name.replace('/', '//') tag.save() operations = RunPython(escape_tag_names) 2. Create a schema migration to change the model fields. Because paths are not allowed to be null, you need to add the ``path`` field as a non-unique field, set some unique data on it (such as the object's ``pk``), and then change the field to add back the unique constraint. To do this reliably on all database types, see `Migrations that add unique fields `_ in the official Django documentation. If you are only working with databases which support transactions, you can use a tagulous helper to add the unique field: 1. When you create the migration, Django will prompt you for a default value for the unique ``path`` field; answer with ``'x'`` (do the same for the ``label`` field when asked). Change the new migration to use the Tagulous helper to add the ``path`` field. 2. Add the unique field:: import tagulous.models.migrations ... class Migration(migrations.Migration): # ... rest of Migration as generated operations = [ ... # Leave other operations as they are, just replace AddField: ] + tagulous.models.migration.add_unique_field( model_name='_tagulous_mymodel_tags', name='path', field=models.TextField(unique=True), preserve_default=False, set_fn=lambda obj: setattr(obj, 'path', str(obj.pk)), ) + [ ... ] .. warning:: Although ``add_unique_column`` and ``add_unique_field`` do work with non-transactional databases, it is not without risk. See :doc:`migrations` for more details. 3. We have changed the abstract base class of the tag model, but Django migrations have no native way to do this. You will need to use the Tagulous helper operation ``ChangeModelBases`` to do it manually, otherwise future data migrations will think it is a ``TagModel``, not a ``TagTreeModel``. Modify the migration from step 2; if you followed the official Django documentation and have several migrations, modify the last one. Add the ``ChangeModelBases`` to the end of your ``operations`` list, as the last operation:: import tagulous.models.migrations class Migration(migrations.Migration): # ... rest of Migration as generated operations = [ # ... rest of operations tagulous.models.migrations.ChangeModelBases( name='_tagulous_mymodel_tags', bases=(tagulous.models.models.BaseTagTreeModel, models.Model), ) ] 4. Create another data migration to rebuild the tag model and set the paths:: def rebuild_tag_model(apps, schema_editor): model = apps.get_model('myapp', 'Tagulous_MyModel_Tags') model.objects.rebuild() operations = RunPython(rebuild_tag_model) If you skipped step 1, this will also create and set parent tags as necessary. 5. Run the migrations You can see a working migration using steps 2 and 3 in the Tagulous tests, for :source:`Django migrations `.