Example Usage

This section contains code examples of how to set up and use Tagulous. If you’d like a more interactive demonstration, there is a static demo showing the front-end, or an example project for you to install locally and play with some of these code examples.

Automatic tag models

This simple example creates a SingleTagField (a glorified ForeignKey) and two TagField (a typical tag field, using ManyToManyField):

from django.db import models
import tagulous.models

class Person(models.Model):
    title = tagulous.models.SingleTagField(
        label="Your preferred title",
        initial="Mr, Mrs, Ms",
    )
    name = models.CharField(max_length=255)
    skills = tagulous.models.TagField(
        force_lowercase=True,
        max_count=5,
    )
  • This will create two new models at runtime to store the tags, Tagulous_Person_title and Tagulous_Person_skills.

  • These models will act like normal models, and can be managed in the database using Django migrations

  • Person.title will now act as a ForeignKey to Tagulous_Person_title

  • Person.skills will now act as a ManyToManyField to Tagulous_Person_skills

Initial tags need to be loaded into the database with the Loading initial tags management command.

You can use the fields to assign and query values:

# Person.skills.tag_model == Tagulous_Person_skills

# Set tags on an instance with a string
instance = Person()
instance.skills = 'run, "kung fu", jump'

# They're not committed to the database until you save
instance.save()

# Get a list of all tags
tags = Person.skills.tag_model.objects.all()

# Assign a list of tags
instance.skills = ['jump', 'kung fu']
# Tags are readable before saving
# str(instance.skills) == 'jump, "kung fu"'
instance.save()

# Step through the list of instances in the tag model
for skill in instance.skills.all():
    do_something(skill)

# Compare tag fields
if instance.skills == other.skills:
    return True

Custom models

You can create a tag model manually, and specify it in one or more tag fields:

import tagulous.models

class Hobbies(tagulous.models.TagModel):
    class TagMeta:
        # Tag options
        initial = "eating, coding, gaming"
        force_lowercase = True
        autocomplete_view = 'myapp.views.hobbies_autocomplete'

class Person(models.Model):
    name = models.CharField(max_length=255)
    hobbies = tagulous.models.TagField(to=Hobbies)

Options for a custom tag model must be set in TagMeta - you cannot pass them as arguments in tag fields.

See Tag Models to see which field names Tagulous uses internally.

Tag Trees

A tag field can specify tree=True to use slashes in tag names to denote children:

import tagulous.models
class Person(models.Model):
    name = models.CharField(max_length=255)
    skills = tagulous.models.TagField(
        force_lowercase=True,
        max_count=5,
        tree=True,
    )

This can’t be set in the tag model’s TagMeta object; the tag model must instead subclass tagulous.models.TagTreeModel:

class Hobbies(tagulous.models.TagTreeModel):
    class TagMeta:
        initial = "food/eating, food/cooking, gaming/football"
        force_lowercase = True
        autocomplete_view = 'myapp.views.hobbies_autocomplete'

class Person(models.Model):
    name = models.CharField(max_length=255)
    hobbies = tagulous.models.TagField(to=Hobbies)

You can add tags as normal, and then query using tree relationships:

person.hobbies = "food/eating/mexican, sport/football"
person.save()

# Get all root nodes: "food", "gaming" and "sport"
root_nodes = Hobbies.objects.filter(parent=None)

# Get the direct children of food: "food/eating", "food/cooking"
food_children = Hobbies.objects.get(name="food").children.all()

# Get all descendants of food:
#   "food/eating", "food/eating/mexican", "food/cooking"
food_children = Hobbies.objects.get(name="food").get_descendants()

See Tag Trees to see a full list of available tree methods and properties.

Tag URL

You can set the get_absolute_url tag option to a callable to give tag objects absolute URLs without needing to create a custom tag model:

from django.db import models
from django.core.urlresolvers import reverse
import tagulous.models

class Person(models.Model):
    name = models.CharField(max_length=255)
    skills = tagulous.models.TagField(
        get_absolute_url=lambda tag: reverse(
            'myapp.views.by_skill', kwargs={'skill_slug': tag.slug}
        ),
    )

The get_absolute_url method can now be called as normal; for example, from a template:

{% for skill in person.skills.all %}
    <a href="{{ skill.get_absolute_url }}">{{ skill.name }}</a>
{% endfor %}

If you are using a tree, you will want to use the path instead:

skills = tagulous.models.TagField(
    tree=True,
    get_absolute_url=lambda tag: reverse(
        'myapp.views.by_skill', kwargs={'skill_path': tag.path}
    ),
)

See the get_absolute_url option for more details.

ModelForms

A ModelForm with tag fields needs no special treatment:

from django.db import models
from django import forms
import tagulous.models

class Person(models.Model):
    name = models.CharField(max_length=255)
    skills = tagulous.models.TagField()

class PersonForm(forms.ModelForm):
    class Meta:
        fields = ['name', 'skills']
        model = Person

They are normal forms so can be used in normal ways; for example, with class-based views:

from django.views.generic.edit import CreateView

class PersonCreate(CreateView):
    model = Person
    fields = ['name', 'skills']

or with view functions:

def person_create(request, template_name="my_app/person_form.html"):
    form = PersonForm(request.POST or None)
    if form.is_valid():
        form.save()
        return redirect('home')
    return render(request, template_name, {'form': form})

However, because a TagField is based on a ManyToManyField, if you save your form using commit=False, you will need to call save_m2m to save the tags:

class Pet(models.Model):
    owner = models.ForeignKey('auth.User')
    name = models.CharField(max_length=255)
    skills = tagulous.models.TagField()

class PetForm(forms.ModelForm):
    class Meta:
        fields = ['owner', 'name', 'skills']
        model = Pet

def pet_create(request, template_name="my_app/pet_form.html"):
    form = PetForm(request.POST or None)
    if form.is_valid():
        pet = form.save(commit=False)
        pet.owner = request.user

        # Next line will save all non M2M fields (including SingleTagField)
        pet.save()

        # Next line will save any ``TagField`` values
        form.save_m2m()

        return redirect('home')
    return render(request, template_name, {'form': form})

As shown above, this only applies to TagField - a SingleTagField is based on ForeignKey, so will be saved without needing save_m2m.

See Forms for how to use tag fields in forms.

Forms without models

Tagulous form fields take tag options as a single TagOptions object, rather than as separate arguments as a model form does:

from django import forms
import tagulous.forms

class PersonForm(forms.ModelForm):
    title = tagulous.forms.SingleTagField(
        autocomplete_tags=['Mr', 'Mrs', 'Ms']
    )
    name = forms.CharField(max_length=255)
    skills = tagulous.forms.TagField(
        tag_options=tagulous.models.TagOptions(
            force_lowercase=True,
        ),
        autocomplete_tags=['running', 'jumping', 'judo']
    )

A SingleTagField will return a string, and a TagField will return a list of strings:

form = PersonForm(data={
    'title':    'Mx',
    'skills':   'Running, judo',
})
assert form.is_valid()
assert form.cleaned_data['title'] == 'Mx'
assert form.cleaned_data['skills'] == ['running', 'judo']

See Forms for how to use tag fields in forms.

Filtering embedded autocomplete

Filtering autocomplete to initial tags only

If it often useful for autocomplete to only list your initial tags, and not those added by others; Tagulous makes this easy with the autocomplete_initial field option:

class Person(models.Model):
    title = tagulous.models.SingleTagField(
        label="Your preferred title",
        initial="Mr, Mrs, Ms",
        autocomplete_initial=True,
    )

Even if users add new tags, only the initial tags will ever be shown as autocomplete options.

See autocomplete_initial for more details.

Autocomplete AJAX Views

To use AJAX to populate your autocomplete using JavaScript, set the tag option autocomplete_view in your models to a value for reverse():

class Person(models.Model):
    name = models.CharField(max_length=255)
    skills = tagulous.models.TagField(
        autocomplete_view='person_skills_autocomplete'
    )

You can then use the default autocomplete views directly in your urls:

import tagulous
from myapp.models import Person
urlpatterns = [
    url(
        r'^person/skills/autocomplete/',
        tagulous.views.autocomplete,
        {'tag_model': Person},
        name='person_skills_autocomplete',
    ),
]

See Views and Templates for more details.

Filtering an autocomplete view

Add a wrapper function which filters the queryset before it calls the normal autocomplete view:

@login_required
def autocomplete_pet_skills(request):
    return tagulous.views.autocomplete(
        request,
        Pet.skills.tag_model.objects.filter_or_initial(
            pet__owner=user
        ).distinct()
    )

Django REST Framework

The Django REST framework’s ModelSerializer will serialize tag fields to their primary keys; for example:

class PersonKeySerializer(ModelSerializer):
    class Meta:
        model = Person
        fields = ["name", "title", "skills"]

person = Person.objects.create(name="adam", title="mr", skills="run, jump")
PersonKeySerializer(Person).data == {
    "name": "adam",
    "title": 1,
    "skills": [1, 2]

If you’d prefer to serialize to strings, use the Tagulous TagSerializer:

from tagulous.contrib.drf import TagSerializer

class PersonStringSerializer(TagSerializer):
    class Meta:
        model = Person
        fields = ["name", "title", "skills"]

person = Person.objects.create(name="adam", title="mr", skills="run, jump")
PersonStringSerializer(Person).data == {
    "name": "adam",
    "title": "mr",
    "skills": ["run", "jump"]