Customizing taggit
==================

Using a Custom Tag or Through Model
-----------------------------------
By default ``django-taggit`` uses a "through model" with a
``GenericForeignKey`` on it, that has another ``ForeignKey`` to an included
``Tag`` model.  However, there are some cases where this isn't desirable, for
example if you want the speed and referential guarantees of a real
``ForeignKey``, if you have a model with a non-integer primary key, or if you
want to store additional data about a tag, such as whether it is official.  In
these cases ``django-taggit`` makes it easy to substitute your own through
model, or ``Tag`` model.

To change the behavior there are a number of classes you can subclass to obtain
different behavior:

=============================== =======================================================================
Class name                      Behavior
=============================== =======================================================================
``TaggedItemBase``              Allows custom ``ForeignKeys`` to models.
``GenericTaggedItemBase``       Allows custom ``Tag`` models. Tagged models use an integer primary key.
``GenericUUIDTaggedItemBase``   Allows custom ``Tag`` models. Tagged models use a UUID primary key.
``CommonGenericTaggedItemBase`` Allows custom ``Tag`` models and ``GenericForeignKeys`` to models.
``ItemBase``                    Allows custom ``Tag`` models and ``ForeignKeys`` to models.
=============================== =======================================================================

Custom ForeignKeys
~~~~~~~~~~~~~~~~~~

Your intermediary model must be a subclass of
``taggit.models.TaggedItemBase`` with a foreign key to your content
model named ``content_object``. Pass this intermediary model as the
``through`` argument to ``TaggableManager``::

    from django.db import models

    from taggit.managers import TaggableManager
    from taggit.models import TaggedItemBase


    class TaggedFood(TaggedItemBase):
        content_object = models.ForeignKey('Food')

    class Food(models.Model):
        # ... fields here

        tags = TaggableManager(through=TaggedFood)


Once this is done, the API works the same as for GFK-tagged models.

Custom GenericForeignKeys
~~~~~~~~~~~~~~~~~~~~~~~~~

The default ``GenericForeignKey`` used by ``django-taggit`` assume your
tagged object use an integer primary key. For non-integer primary key,
your intermediary model must be a subclass of ``taggit.models.CommonGenericTaggedItemBase``
with a field named ``"object_id"`` of the type of your primary key.

For example, if your primary key is a string::

    from django.db import models

    from taggit.managers import TaggableManager
    from taggit.models import CommonGenericTaggedItemBase, TaggedItemBase

    class GenericStringTaggedItem(CommonGenericTaggedItemBase, TaggedItemBase):
        object_id = models.CharField(max_length=50, verbose_name=_('Object id'), db_index=True)

    class Food(models.Model):
        food_id = models.CharField(primary_key=True)
        # ... fields here

        tags = TaggableManager(through=GenericStringTaggedItem)

GenericUUIDTaggedItemBase
~~~~~~~~~~~~~~~~~~~~~~~~~

.. note::

    ``GenericUUIDTaggedItemBase`` relies on Django UUIDField introduced with
    Django 1.8. Therefore ``GenericUUIDTaggedItemBase`` is only defined
    if you are using Django 1.8+.

A common use case of a non-integer primary key, is UUID primary key.
``django-taggit`` provides a base class ``GenericUUIDTaggedItemBase`` ready
to use with models using an UUID primary key::

    from django.db import models
    from django.utils.translation import ugettext_lazy as _

    from taggit.managers import TaggableManager
    from taggit.models import GenericUUIDTaggedItemBase, TaggedItemBase

    class UUIDTaggedItem(GenericUUIDTaggedItemBase, TaggedItemBase):
        # If you only inherit GenericUUIDTaggedItemBase, you need to define
        # a tag field. e.g.
        # tag = models.ForeignKey(Tag, related_name="uuid_tagged_items", on_delete=models.CASCADE)

        class Meta:
            verbose_name = _("Tag")
            verbose_name_plural = _("Tags")

    class Food(models.Model):
        id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
        # ... fields here

        tags = TaggableManager(through=UUIDTaggedItem)

Custom tag
~~~~~~~~~~

When providing a custom ``Tag`` model it should be a ``ForeignKey`` to your tag
model named ``"tag"``:

  .. code-block:: python

    from django.db import models
    from django.utils.translation import ugettext_lazy as _

    from taggit.managers import TaggableManager
    from taggit.models import TagBase, GenericTaggedItemBase


    class MyCustomTag(TagBase):
        # ... fields here

        class Meta:
            verbose_name = _("Tag")
            verbose_name_plural = _("Tags")

        # ... methods (if any) here


    class TaggedWhatever(GenericTaggedItemBase):
        # TaggedWhatever can also extend TaggedItemBase or a combination of
        # both TaggedItemBase and GenericTaggedItemBase. GenericTaggedItemBase
        # allows using the same tag for different kinds of objects, in this
        # example Food and Drink.

        # Here is where you provide your custom Tag class.
        tag = models.ForeignKey(MyCustomTag,
                                related_name="%(app_label)s_%(class)s_items")


    class Food(models.Model):
        # ... fields here

        tags = TaggableManager(through=TaggedWhatever)


    class Drink(models.Model):
        # ... fields here

        tags = TaggableManager(through=TaggedWhatever)


.. class:: TagBase

    .. method:: slugify(tag, i=None)

        By default ``taggit`` uses :func:`django.template.defaultfilters.slugify`
        to calculate a slug for a given tag.  However, if you want to implement
        your own logic you can override this method, which receives the ``tag``
        (a string), and ``i``, which is either ``None`` or an integer, which
        signifies how many times the slug for this tag has been attempted to be
        calculated, it is ``None`` on the first time, and the counting begins
        at ``1`` thereafter.


Using a custom tag string parser
--------------------------------
By default ``django-taggit`` uses :func:`taggit.utils._parse_tags` which
accepts a string which may contain one or more tags and returns a list of tag
names.  This parser is quite intelligent and can handle a number of edge cases;
however, you may wish to provide your own parser for various reasons (e.g. you
can do some preprocessing on the tags so that they are converted to lowercase,
reject certain tags, disallow certain characters, split only on commas rather
than commas and whitespace, etc.).  To provide your own parser, write a
function that takes a tag string and returns a list of tag names.  For example,
a simple function to split on comma and convert to lowercase::

    def comma_splitter(tag_string):
        return [t.strip().lower() for t in tag_string.split(',') if t.strip()]

You need to tell ``taggit`` to use this function instead of the default by
adding a new setting, ``TAGGIT_TAGS_FROM_STRING`` and providing it with the
dotted path to your function.  Likewise, you can provide a function to convert
a list of tags to a string representation and use the setting
``TAGGIT_STRING_FROM_TAGS`` to override the default value (which is
:func:`taggit.utils._edit_string_for_tags`)::

    def comma_joiner(tags):
        return ', '.join(t.name for t in tags)

If the functions above were defined in a module, ``appname.utils``, then your
project settings.py file should contain the following::

    TAGGIT_TAGS_FROM_STRING = 'appname.utils.comma_splitter'
    TAGGIT_STRING_FROM_TAGS = 'appname.utils.comma_joiner'
