Create a Tag field using Django-Select2.

The excellent framework – Select2, have had support for tags for a long time, but Django-Select2 lacked that, until now (version 4.2.0).

Tag fields are very much like any multiple value input field except that it allows users to enter values which does not exist in the backing model. So, that the users can add new tags.

For this purpose few new fields have been added to Django-Select2. They all have the suffix – TagField. Few widgets too have been added to auto configure Select2 Js to run in “tagging mode”.

You can see the full reference in docs – http://django-select2.readthedocs.org/en/latest/ref_fields.html and http://django-select2.readthedocs.org/en/latest/ref_widgets.html.

Simple tag field implementation example

You can find this code in testapp too.

models.py:-

class Tag(models.Model):
    tag = models.CharField(max_length=10, unique=True)

    def __unicode__(self):
        return unicode(self.tag)

class Question(models.Model):
    question = models.CharField(max_length=200)
    description = models.CharField(max_length=800)
    tags = models.ManyToManyField(Tag)

    def __unicode__(self):
        return unicode(self.question)

forms.py:-

class TagField(AutoModelSelect2TagField):
    queryset = Tag.objects
    search_fields = ['tag__icontains', ]
    def get_model_field_values(self, value):
        return {'tag': value}

class QuestionForm(forms.ModelForm):
    question = forms.CharField()
    description = forms.CharField(widget=forms.Textarea)
    tags = TagField()

    class Meta:
        model = Question

Above I am trying to create a form which the website users can use to submit questions. Things to note in TagField is that it is almost like we use any other AutoModel fields in Django-Select2, except that here we override one new method get_model_field_values().

When users submit a tag field, the usual validation runs, but with a twist. While checking if the provided value exists, if it is found not to exist then the field, instead of raising error, creates that value. However, the library does not know how to create a new value instance, hence it invokes create_new_value() to do that job.

Model version of the tag field already knows that it is dealing with Django models so it knows how to instantiate that, but does not know what values to set for the attributes. So it implements create_new_value() which in-turn invokes get_model_field_values() to get required attribute names mapped to their values.

Before you continue

One key thing to remember is that unlike other widgets, you ideally should not be using tag widgets with normal Django fields. Django fields do not allow creation of a new choice value. However, if you want tagging support but do not want to allow users to create new tags then you can very much use the tag widget here and pair it up with normal Django fields. However, that may not be a good idea, since then UI would still allow creation of new tags, but on submit the user would get an error.

Making it better

We can make this better and interesting. A typical tagging system should have the following features.

  • Ability to detect misspellings. Peter Norvig’s essay on this is excellent. More information can be found on Stack Overflow.
  • Use statistics to order the results. This very much useful when the tag counts ballon up. The statistics could be based on how many tags are frequently used together. Of course when you start a site you would not have any data, in that case for a period of time you can set the algo to only learn.
  • Cache frequently used tags. This is a normal optimization technique which is frequently used. A memcache like layer is usually used to cache the DB data, and if the data is not found there, then a DB hit is made.
Comments
8 Responses to “Create a Tag field using Django-Select2.”
  1. David Miller says:

    I’m new to this so I have a question about this TagField that may seem basic. In my application, each Tag belongs to a specific User so I don’t want to return the list of all Tags, I want to apply a “user” filter to the list of Tags.

    So, from your example above, instead of this –

    queryset = Tag.objects

    I’d like to do something like this –

    queryset = Tag.objects.filter(user=user)

    I can’t seem to figure out how to do it. How would I add filters to the queryset? Is there a tag equivalent to the “ModelSelect2MultipleField” field (because I’ve figured out how to apply a filter to that field)?

    Thanks for the help.

    • Tag extends ModelMultipleChoiceField. So I guess in this case you need to define your own queryset property getter. In the getter return queryset which is limited to this user.

      • David Miller says:

        I have no idea how to do that. Would you be kind enough to send me sample code that adds a getter for the above example. I’m not sure what to do from here.

      • David Miller says:

        I was able to do this and it worked:

        self.fields[‘tags’] = TagField(
        required=False,
        queryset=Tag.objects.filter(model=self.model)\
        .order_by(‘name’))

        However, I don’t know how to pass the additional “model” field in get_model_field_values(self, value) so I’m getting this error when I save my form:

        null value in column “model_id” violates not-null constraint

        Any idea how to pass an extra value when saving a new Tag?

        • In Tag fields you are required to override get_model_field_values(). Here you return all the fields and their values in a map. These will be used to create a new tag instance.

          • David Miller says:

            How do I add the model field and its value into get_model_field_values()? I tried this and it didn’t work.

            class TagField(AutoModelSelect2TagField):
            queryset = Tag.objects
            search_fields = [‘name__icontains’, ]
            def get_model_field_values(self, value, model):
            return {‘name’: value, ‘model’, model}

            —-in form—-
            self.fields[‘tags’] = TagField(
            queryset=Tag.objects.filter(model=self.model),
            model=self.model)

  2. Ed says:

    Hi!
    models.py:
    —-
    from django.contrib.postgres.fields import ArrayField

    class Tag(models.Model):
    slug = models.CharField(max_length=40, primary_key=True, blank=True)
    name = models.CharField(max_length=40, db_index=True)

    class AdDO(models.Model):

    tags_name = ArrayField(models.CharField(max_length=40,
    blank=True, null=True),
    blank=True, null=True)
    —–

    Models are not related.

    I need: just fill in the “AdDO.tags_name” multiple “Tag.name”

    Can you please tell how to do it?

Leave A Comment