=============
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.
.. _example_auto_tagmodel:
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
:ref:`command_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
.. _example_custom_tag_model:
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 :ref:`tagmeta` - you cannot
pass them as arguments in tag fields.
See :doc:`models/tag_models` to see which field names Tagulous uses internally.
.. _example_tag_trees:
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 :ref:`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 :doc:`models/tag_trees` to see a full list of available tree methods and
properties.
.. _example_tag_url:
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 %}
{{ skill.name }}
{% 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 :ref:`option_get_absolute_url` option for more details.
.. _example_modelform:
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 :doc:`forms` for how to use tag fields in forms.
.. _example_form:
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 :doc:`forms` for how to use tag fields in forms.
.. _example_filter_embedded:
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 :ref:`option_autocomplete_initial` for more details.
.. _example_filter_related:
Filtering autocomplete by related fields
----------------------------------------
This example will embed the tags into the HTML of the response; if you are
using autocomplete views, see :ref:`example_filter_autocomplete_view` instead.
Filter the ``autocomplete_tags`` queryset after the form initialises::
from django.db import models
from django import forms
import tagulous
class Pet(models.Model):
owner = models.ForeignKey('auth.User')
name = models.CharField(max_length=255)
skills = tagulous.models.TagField()
class PetForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(PetForm, self).__init__(*args, **kwargs)
# Filter skills to initial skills, or ones added by this user
self.fields['skills'].autocomplete_tags = \
self.fields['skills'].autocomplete_tags.filter_or_initial(
pet__owner=user
).distinct()
class Meta:
model = Pet
Then call ``PetForm`` with the user as the first argument, for example::
def add_pet(request):
form = PetForm(request.user)
# ...
For more details, see :ref:`filter_by_related` and :ref:`filter_autocomplete`.
.. _example_autocomplete_views:
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 :doc:`views` for more details.
.. _example_filter_autocomplete_view:
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"]