Tagged Models

Models which have tag fields are called tagged models. In most situations, all you need to do is add the tag field to the model and Tagulous will do the rest.

Because Tagulous’s fields work by subclassing ForeignKey and ManyToManyField, there are some places in Django’s models where you would expect to use tag strings but cannot - constructors and filtering, for example. Tagulous therefore adds this functionality through the tagulous.models.TaggedModel base class for tagged models.

If TAGULOUS_ENHANCE_MODELS = True (which it is by default - see Settings), this base class will be applied automatically, otherwise read on to Setting tagged base classes manually.

Note

Tagulous sets TaggedModel as the base class for your existing tagged model by listening for the class_prepared signal, sent when a model has been constructed. If the model contains tag fields, Tagulous will dynamically add TaggedModel to the model’s base classes and TaggedManager to the manager’s base classes, which in turn adds TaggedQuerySet to the querysets the manager creates. It does this by calling the cast_class class method on each of the base classes, which change the original classes in place.

This all happens seamlessly behind the scenes; the only thing you may notice is that the names of your manager and queryset classes now have the prefix CastTagged to indicate that they have been automatically cast to their equivalents for tagged models.

Tagged model classes

tagulous.models.TaggedModel

This is the base class for all tagged models. It changes the model constructor so that TagField values can be passed as keywords.

tagulous.models.TaggedManager

The base class for managers of tagged models. It only exists to ensure querysets are subclasses of tagulous.TaggedQuerySet.

tagulous.models.TaggedQuerySet

The base class for querysets on tagged models. It changes get, filter and exclude to work with string values, and create and get_or_create to work with string and TagField values.

It also adds get_similar_objects() - see Finding similar objects for usage.

See Querying using tag fields for more details.

Setting tagged base classes manually

However, if you want to avoid this automatic subclassing, you can set TAGULOUS_ENHANCE_MODELS = False and manage this yourself:

The three tagged base classes each have a class method cast_class which can change existing classes so that they become CastTagged subclasses of themselves; for example:

class MyModel(tagulous.TaggedModel):
    name = models.CharField(max_length=255)
    tags = tagulous.models.TagField()
    objects = tagulous.models.TaggedManager.cast_class(MyModelManager)
    other_manager = MyOtherManager
tagulous.models.TaggedManager.cast_class(MyModel.other_manager)

This can be useful when working with other third-party libraries which insist on you doing things a certain way.

Querying using tag fields

When querying a tagged model, remember that a SingleTagField is really a ForeignKey, and a TagField is really a ManyToManyField. You can query using these relationships in conventional ways.

If you have correctly made your tagged model subclass tagulous.models.TaggedModel, you can also compare a tag field to a tag string in get, filter and exclude:

qs = MyModel.objects.get(name="Bob", title="Mr", tags="red, blue, green")

When querying a tag field, case sensitivity will default to whatever the tag field option was. For example, if the title tag field above was defined with case_sensitive=False, .filter(title='Mr') will match Mr, mr etc.

Note that when querying a TagField in this way, the returned queryset will include (or exclude) any object which contains all the specified tags - but it may also have other tags. To only return objects which have the specified tags and no others, use the __exact field lookup suffix:

# Find all MyModel objects which have the tag 'red':
qs = MyModel.objects.filter(tags='red')
# (will include those tagged 'red, blue' etc)

# Find all MyModel objects which are only tagged 'red':
qs = MyModel.objects.filter(tags__exact='red')
# (will not include those tagged 'red, blue')

This currently does not work across database relations; you will need to use the name field on the tag model for those:

# Find
qs = MyRelatedModel.objects.filter(
    foreign_model__tags__name__in=['red', 'blue', 'green'],
)

Finding similar objects

The QuerySet on a tagged model provides the method get_similar_objects, which takes the instance and field name to compare similarity by, and returns a queryset of similar objects from that tagged model, ordered by similarity:

myobj = MyModel.objects.first()
similar = MyModel.objects.get_similar_objects(myobj, 'tags')

There is a convenience wrapper on the related manager which detects the instance and field to compare by:

similar = myobj.tags.get_similar_objects()

Although less useful, there is a similar function for single tag fields, which finds all objects with the same tag:

similar = myobj.singletag.get_similar_objects()

The similar querysets will exclude the object being compared - in the above examples, myobj will not be in the queryset.