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
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
A custom tag tree model must be a subclass of
tagulous.models.TagTreeModel instead of the normal
tagulous.models.TagModel; for automatically-generated tag models, this is managed by setting the
tree field option to
Tag Tree Model Classes
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.
tagulous.models.TagModel; it inherits all the normal fields and methods, and adds the following:
ForeignKey to the parent tag. Tagulous sets this automatically when saving, creating missing ancestors as needed.
The reverse relation manager for
CharField containing the name of the tag without its ancestors.
Example: a tag named
Animal/Mammal/Cat has the label
SlugField containing the slug for the tag label.
Example: a tag named
Animal/Mammal/Cat has the slug
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
IntegerField containing the level of this tag in the tree (starting from 1).
Merge the specified tags into this tag.
tags can be a queryset, list of tags or tag names, or a tag string.
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.
children=True, child tags will be merged into children of this tag, retaining structure; eg merging
Animal will merge
Animal/Mammal/Cat etc. Tags will be created if they don't exist.
Returns a queryset of all ancestors, ordered by level.
Returns a queryset of all descendants, ordered by level.
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)
TagTreeModelManager is the standard manager for a
tagulous.models.TagTreeModel; it is a subclass of
tagulous.models.TagModelManager so provides those methods, but its queries return a
Returns a new queryset containing the nodes from the calling queryset, plus their ancestor nodes.
Returns a new queryset containing the nodes from the calling queryset, plus their descendant nodes.
Returns a new queryset containing the nodes from the calling queryset, plus theirm sibling nodes.
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:
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.
When using Django migrations, run
manage.py makemigrations myapp --emptyand 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)
With South, run
manage.py datamigration myapp escape_tagsand add:
def forwards(self, orm): for tag in orm['myapp.Tagulous_MyModel_tags'].objects.all(): tag.name = tag.name.replace('/', '//') tag.save()
Create a schema migration to change the model fields. Because paths are not allowed to be null, you need to add the
pathfield 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:
When you create the migration, Django or South will prompt you for a default value for the unique
pathfield; answer with
'x'(do the same for the
labelfield when asked).
Change the new migration to use the Tagulous helper to add the
When using Django migrations:
from django.utils import six 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', six.text_type(obj.pk)), ) + [ ... ]
def forwards(self, orm): ... # Leave other migration statements as they are - just replace the # call to db.add_column for the path field with add_unique_column. # Replace ``myapp`` with your app name, and # replace ``Tagulous_MyModel_tags`` with your tag model name from tagulous.models.migrations import add_unique_column from django.utils import six # Adding field 'Tagulous_MyModel_tags.path' add_unique_column( self, db, orm['myapp.Tagulous_MyModel_tags'], 'path', lambda obj: setattr(obj, 'path', six.text_type(obj.pk)), 'django.db.models.fields.TextField', )
Skip this step if you are using South.
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
ChangeModelBasesto do it manually, otherwise future data migrations will think it is a
TagModel, not a
Modify the migration from step 2; if you followed the official Django documentation and have several migrations, modify the last one. Add the
ChangeModelBasesto the end of your
operationslist, 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), ) ]
Create another data migration to rebuild the tag model and set the paths.
When using Django migrations:
def rebuild_tag_model(apps, schema_editor): model = apps.get_model('myapp', 'Tagulous_MyModel_Tags') model.objects.rebuild() operations = RunPython(rebuild_tag_model)
def forwards(self, orm): orm['myapp.Tagulous_MyModel_tags'].objects.rebuild()
If you skipped step 1, this will also create and set parent tags as necessary.
- Run the migrations