Model Fields

Tagulous offers two new model field types:

These will automatically create the models for the tags themselves, or you can provide a custom model to use instead with to - see Custom Tag Models for more details.

Tagulous lets you get and set string values using these fields, while still leaving the underlying relationships available. For example, not only can you assign a queryset or list of tag primary keys to a TagField, but you can also assign a list of tag names, or a tag string to parse.

Like a CharField, changes made by assigning a value will not be committed until the model is saved, although you can still make immediate changes by calling the standard m2m methods add, remove and clear.

If TAGULOUS_ENHANCE_MODELS is True (which it is by default - see Settings), you can also use tag strings and lists of tag names in get and filter, and model constructors and object.create() - see Tagged Models for more details.

Model Field Arguments

The SingleTagField supports most standard ForeignKey arguments, except for to_field and rel_class.

The TagField supports most normal ManyToManyField arguments, except for db_table, through and symmetrical. Also note that blank has no effect at the database level, it is just used for form validation - as is the case with a normal ManyToManyField.

The related_name will default to <field>_set, as is normal for a ForeignKey or ManyToManyField. If using the same tag table on multiple fields, you will need to set this to something else to avoid clashes.

Auto-generating a tag model

If the to argument is not set, a tag model will be auto-generated for you. It will be given a class name based on the names of the tagged model and tag field; for example, the class name of the auto-generated model for MyModel.tags would be Tagulous_MyModel_tags.

When auto-generating a model, any model option can be passed as a field argument - see the Automatic tag models example.

If you want to override the default base class, for convenience you can specify a custom base class for the auto tag model - see the to_base=MyTagModelBase argument for details.

Specifying a tag model

You can specify the tag model for the tag field to use with the to argument. You cannot specify any tag options.

to=MyTagModel (or first unnamed argument)

Manually specify a tag model for this tag field. This can either be a reference to the tag model class, or string reference to it in the format app.model.

This will normally be a custom tag model, which must be a subclass of tagulous.models.TagModel.

It can also be a reference to a tag model already auto-generated by another tag field, eg to=MyOtherModel.tags.tag_model, although you must be confident that MyOtherModel will always be defined first.

It can also be a string containing the name of the tag model, eg to='MyTagModel'. However, this is resolved using Django's standard model name resolution, so you have to reference auto-generated models by their class name, not via the field - eg to='otherapp.Tagulous_MyOtherModel_tags'.

If the tagged model for this field is also a custom tag model, you can specify a recursive relationship as normal, using 'self'.

If it is a custom tag model, it should have a TagMeta class. Fields which specify their tag model cannot provide new tag model options; they will take their options from the model - see Tag Options for more details.

This argument is optional; if omitted, a tag model will be auto-generated for you.

Default: Tagulous_<ModelName>_<FieldName> (auto-generated)

to_base=MyTagModelBase

You can specify a base class to use for an auto-generated tag model, instead of using TagModel.

This can be useful on complex sites where multiple auto-generated tag models need to share common custom functionality - for example, tracking and filtering by user who creates the tags. This argument will allow you to define one base class and re-use it across your project with less boilerplate than defining many empty custom tag models.

Default: tagulous.models.TagModel

tagulous.models.SingleTagField

Unbound field

An unbound SingleTagField (called on a model class, eg MyModel.tag) acts in the same way an unbound ForeignKey field would, but also has:

tag_model

The related tag model

tag_options

A TagOptions class, containing the options from the tag model's TagMeta or passed as arguments when initialising the field.

Bound to an instance

A bound SingleTagField (called on an instance, eg instance.tags) acts in a similar way to a bound ForeignKey, but with some differences:

Assignment (setter)

A bound SingleTagField can be assigned a tag (an instance of the tag model) or a tag name.

If it is passed None, a current tag will be cleared if it is set.

The instance must be saved afterwards.

Example:

person.title = "Mr"
person.save()

Evaluation (getter)

The value of a bound SingleTagField will return an instance of the tag model. The tag may not exist in the database yet (its pk may be None).

Example:

tag = person.title
report = "Tag %s used %d times " % (tag.name, tag.count)

The tag_model and tag_options attributes are not available on a bound field. If you only have an instance of the tagged model, you can access them by finding its class, eg type(person).title.tag_model.

tagulous.models.TagField

Unbound field

An unbound TagField (called on a model class, eg MyModel.tags) acts in the same way an unbound ManyToManyField would, but also has:

tag_model

The related tag model

tag_options

A TagOptions class, containing the options from the tag model's TagMeta or passed as arguments when initialising the field.

Bound to an instance

A bound TagField (called on an instance, eg instance.tags) acts in a similar way to a bound ManyToManyField, but with some differences:

Assignment (setter)

A bound TagField can be assigned a tag string or an iterable of tags or tag names, eg a list of strings, or a queryset of instances of the tag model.

If it is passed None, any current tags will be cleared.

The instance must be saved afterwards.

Example:

person.skills = 'Judo, "Kung Fu"'
person.save()

Evaluation (getter)

A bound TagField will return a tagulous.models.TagRelatedManager object, which has functions to get and set tag values.

tagulous.models.TagRelatedManager

A TagRelatedManager is a subclass of Django's standard RelatedManager, so you can do anything you would normally do with a bound ManyToManyField:

person.skills.get(name='judo')
tags = person.skills.all()
person.skills.add(MyTag)
person.skills.clear()

Because it's a relationship to a tag model, you can also filter by its fields:

filtered_tags = person.skills.filter(name__startswith='a')
popular_tags = person.skills.filter(count__gte=10)

A TagRelatedManager also provides access to the field's tag_model and tag_options:

person.skills.tag_model.objects.all()
is_lowercase = person.skills.tag_options.force_lowercase

It also provides the following additional methods:

set_tag_string(tag_string)

Sets the tags for this instance, given a tag string.

person.skills.set_tag_string('Judo, "Kung Fu"')
person.save()

set_tag_list(tag_list)

Sets the tags for this instance, given an iterable of tag names or tag

instances.

person.skills.set_tag_list(['Judo', kung_fu_tag])
person.save()

get_tag_string()

Gets the tags as a tag string.

tag_string = person.skills.get_tag_string()
# tag_string == 'Judo, "Kung Fu"'

get_tag_list()

Returns a list of tag names.

tag_list = person.skills.get_tag_list()
# tag_list == ['Judo', 'Kung Fu']

__str__(), __unicode__()

Same as get_tag_string

report = '%s' % person.skills

__eq__, __ne__

Compare the tags on this instance to a tag string, or an iterable of tags or tag names. Order does not matter, and case sensitivity is determined by

the options case_sensitive and force_lowercase.

if (
    first.tags == second.tags
    or first.tags == ['Judo', kung_fu_tag]
    or first.tags != 'foo, bar'
    or first.tags != second.tags.filter(name__istartswith='k')
):
    ...

__contains__

See if the tag (or string of a tag name) is in the tags. Case sensitivity is determined by the options case_sensitive and

force_lowercase.

if 'Judo' in person.skills and kung_fu_tag in person.skills:
    candidates.append(person)

__len__

Return the number of tags set for this instance.

person.skills = 'judo, "kung fu", karate'
len(person.skills) == 3

reload()

Discard any unsaved changes to the tags and load tags from the database

person.skills = 'judo'
person.save()
person.skills = 'karate'
person.skills.reload()
# person.skills == 'judo'

save(force=False)

Commit any tag changes to the database.

If you are only changing the tags you can call this directly to reduce database operations.

In most circumstances you can ignore the force flag:

  • The manager has a .changed flag which is set to False whenever the internal tag cache is loaded or saved. It is set to True when the tags are changed without being saved.
  • If force=False (default), this method will only update the database if the .changed flag is True - in other words, the database will only be updated if there are changes to the internal cache since last load or save.
  • If force=True, the .changed flag will be ignored, and the current tag status will be forced upon the database. This can be useful in the rare cases where you have multiple references to the same database object, and want the tags on this instance to override any changes other instances may have made.

For example:

person = Person.objects.create(name='Adam', skills='judo')
person.name = 'Bob'
person.skills = 'karate'
person.skills.save()
# person.name == 'Adam'
# person.skills == 'judo'

add(tag, tag, ...)

Based on the normal RelatedManager.add method, but has support for tag names.

Adds a list of tags or tag names directly to the instance - there is no need to save afterwards.

Will call reload() first, so any unsaved changes to tags will be lost.

person.skills.add('Judo', kung_fu_tag)

remove(tag, tag, ...)

Based on the normal RelatedManager.remove method, but has support for tag names.

Removes a list of tags or tag names directly from the instance - there is no need to save afterwards.

Will call reload() first, so any unsaved changes to tags will be lost.

person.skills.remove('Judo', kung_fu_tag)