1Customizing taggit
2==================
3
4Using a Custom Tag or Through Model
5-----------------------------------
6By default ``django-taggit`` uses a "through model" with a
7``GenericForeignKey`` on it, that has another ``ForeignKey`` to an included
8``Tag`` model.  However, there are some cases where this isn't desirable, for
9example if you want the speed and referential guarantees of a real
10``ForeignKey``, if you have a model with a non-integer primary key, or if you
11want to store additional data about a tag, such as whether it is official.  In
12these cases ``django-taggit`` makes it easy to substitute your own through
13model, or ``Tag`` model.
14
15Note: Including 'taggit' in ``settings.py`` INSTALLED_APPS list will create the
16default ``django-taggit`` and "through model" models. If you would like to use
17your own models, you will need to remove 'taggit' from ``settings.py``'s
18INSTALLED_APPS list.
19
20To change the behavior there are a number of classes you can subclass to obtain
21different behavior:
22
23=============================== =======================================================================
24Class name                      Behavior
25=============================== =======================================================================
26``TaggedItemBase``              Allows custom ``ForeignKeys`` to models.
27``GenericTaggedItemBase``       Allows custom ``Tag`` models. Tagged models use an integer primary key.
28``GenericUUIDTaggedItemBase``   Allows custom ``Tag`` models. Tagged models use a UUID primary key.
29``CommonGenericTaggedItemBase`` Allows custom ``Tag`` models and ``GenericForeignKeys`` to models.
30``ItemBase``                    Allows custom ``Tag`` models and ``ForeignKeys`` to models.
31=============================== =======================================================================
32
33Custom ForeignKeys
34~~~~~~~~~~~~~~~~~~
35
36Your intermediary model must be a subclass of
37``taggit.models.TaggedItemBase`` with a foreign key to your content
38model named ``content_object``. Pass this intermediary model as the
39``through`` argument to ``TaggableManager``::
40
41    from django.db import models
42
43    from taggit.managers import TaggableManager
44    from taggit.models import TaggedItemBase
45
46
47    class TaggedFood(TaggedItemBase):
48        content_object = models.ForeignKey('Food', on_delete=models.CASCADE)
49
50    class Food(models.Model):
51        # ... fields here
52
53        tags = TaggableManager(through=TaggedFood)
54
55
56Once this is done, the API works the same as for GFK-tagged models.
57
58Custom GenericForeignKeys
59~~~~~~~~~~~~~~~~~~~~~~~~~
60
61The default ``GenericForeignKey`` used by ``django-taggit`` assume your
62tagged object use an integer primary key. For non-integer primary key,
63your intermediary model must be a subclass of ``taggit.models.CommonGenericTaggedItemBase``
64with a field named ``"object_id"`` of the type of your primary key.
65
66For example, if your primary key is a string::
67
68    from django.db import models
69
70    from taggit.managers import TaggableManager
71    from taggit.models import CommonGenericTaggedItemBase, TaggedItemBase
72
73    class GenericStringTaggedItem(CommonGenericTaggedItemBase, TaggedItemBase):
74        object_id = models.CharField(max_length=50, verbose_name=_('Object id'), db_index=True)
75
76    class Food(models.Model):
77        food_id = models.CharField(primary_key=True)
78        # ... fields here
79
80        tags = TaggableManager(through=GenericStringTaggedItem)
81
82GenericUUIDTaggedItemBase
83~~~~~~~~~~~~~~~~~~~~~~~~~
84
85A common use case of a non-integer primary key, is UUID primary key.
86``django-taggit`` provides a base class ``GenericUUIDTaggedItemBase`` ready
87to use with models using an UUID primary key::
88
89    from django.db import models
90    from django.utils.translation import ugettext_lazy as _
91
92    from taggit.managers import TaggableManager
93    from taggit.models import GenericUUIDTaggedItemBase, TaggedItemBase
94
95    class UUIDTaggedItem(GenericUUIDTaggedItemBase, TaggedItemBase):
96        # If you only inherit GenericUUIDTaggedItemBase, you need to define
97        # a tag field. e.g.
98        # tag = models.ForeignKey(Tag, related_name="uuid_tagged_items", on_delete=models.CASCADE)
99
100        class Meta:
101            verbose_name = _("Tag")
102            verbose_name_plural = _("Tags")
103
104    class Food(models.Model):
105        id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
106        # ... fields here
107
108        tags = TaggableManager(through=UUIDTaggedItem)
109
110Custom tag
111~~~~~~~~~~
112
113When providing a custom ``Tag`` model it should be a ``ForeignKey`` to your tag
114model named ``"tag"``. If your custom ``Tag`` model has extra parameters you want to initialize during setup, you can do so by passing it along via the ``tag_kwargs`` parameter of ``TaggableManager.add``. For example ``my_food.tags.add("tag_name1", "tag_name2", tag_kwargs={"my_field":3})``:
115
116  .. code-block:: python
117
118    from django.db import models
119    from django.utils.translation import ugettext_lazy as _
120
121    from taggit.managers import TaggableManager
122    from taggit.models import TagBase, GenericTaggedItemBase
123
124
125    class MyCustomTag(TagBase):
126        # ... fields here
127
128        class Meta:
129            verbose_name = _("Tag")
130            verbose_name_plural = _("Tags")
131
132        # ... methods (if any) here
133
134
135    class TaggedWhatever(GenericTaggedItemBase):
136        # TaggedWhatever can also extend TaggedItemBase or a combination of
137        # both TaggedItemBase and GenericTaggedItemBase. GenericTaggedItemBase
138        # allows using the same tag for different kinds of objects, in this
139        # example Food and Drink.
140
141        # Here is where you provide your custom Tag class.
142        tag = models.ForeignKey(
143            MyCustomTag,
144            on_delete=models.CASCADE,
145            related_name="%(app_label)s_%(class)s_items",
146        )
147
148
149    class Food(models.Model):
150        # ... fields here
151
152        tags = TaggableManager(through=TaggedWhatever)
153
154
155    class Drink(models.Model):
156        # ... fields here
157
158        tags = TaggableManager(through=TaggedWhatever)
159
160
161.. class:: TagBase
162
163    .. method:: slugify(tag, i=None)
164
165        By default ``taggit`` uses :func:`django.utils.text.slugify` to
166        calculate a slug for a given tag. However, if you want to implement
167        your own logic you can override this method, which receives the ``tag``
168        (a string), and ``i``, which is either ``None`` or an integer, which
169        signifies how many times the slug for this tag has been attempted to be
170        calculated, it is ``None`` on the first time, and the counting begins
171        at ``1`` thereafter.
172
173
174Using a custom tag string parser
175--------------------------------
176
177By default ``django-taggit`` uses ``taggit.utils._parse_tags`` which accepts a
178string which may contain one or more tags and returns a list of tag names. This
179parser is quite intelligent and can handle a number of edge cases; however, you
180may wish to provide your own parser for various reasons (e.g. you can do some
181preprocessing on the tags so that they are converted to lowercase, reject
182certain tags, disallow certain characters, split only on commas rather than
183commas and whitespace, etc.). To provide your own parser, write a function that
184takes a tag string and returns a list of tag names. For example, a simple
185function to split on comma and convert to lowercase::
186
187    def comma_splitter(tag_string):
188        return [t.strip().lower() for t in tag_string.split(',') if t.strip()]
189
190You need to tell ``taggit`` to use this function instead of the default by
191adding a new setting, ``TAGGIT_TAGS_FROM_STRING`` and providing it with the
192dotted path to your function. Likewise, you can provide a function to convert a
193list of tags to a string representation and use the setting
194``TAGGIT_STRING_FROM_TAGS`` to override the default value (which is
195``taggit.utils._edit_string_for_tags``)::
196
197    def comma_joiner(tags):
198        return ', '.join(t.name for t in tags)
199
200If the functions above were defined in a module, ``appname.utils``, then your
201project settings.py file should contain the following::
202
203    TAGGIT_TAGS_FROM_STRING = 'appname.utils.comma_splitter'
204    TAGGIT_STRING_FROM_TAGS = 'appname.utils.comma_joiner'
205