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