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.