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:-

#!python
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:-

#!python
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.

Django-Select2 version 3.1.1 released

Just released Django-Select2‘s version 3.1.1.

The key changes are:-

  • Updated Select2 Javascript library to versino 3.2. This new version fixes a lot of bugs, along with providing a high resolution icon image, to be used in Retina displays.
  • In my last release (version 3.0.2) I introduced some new template tags but unfortunately I did not update the setup.py and Manifest.in files to include that directory. That directory was missing in the released package. Now it is included and installed.

If you see the Changelog then you will notice that I jumped from version 3.0.2 to 3.1.1. In fact there was a version 3.1.0 in-between, but that was again a faulty package. That package had the templatetags directory but that does not install that. That was again due to some missing code in setup.py.

Django-Select2: Select2 for Django

Select2

Select2 is an excellent Javascript framework which transforms mundane <select> fields to cool looking and searchable. This is a very handy when there are quite a number of options to select from.

Basic Select2 options field.

Select2 also allows dynamic fetching of options from server via Ajax. Select2’s webpage has a neat demo of this.

Select2 fetching data via Ajax. In the above screenshot it is using RottenTomatoes’ API to get them.

Django-Select2

Django includes basic select widget, which just generates <select><option>...</option>...</select> tags.  Although their ‘looks’ can be improved using basic CSS, but we hit a usability problem when there are too many options to select from. This is where Django-Select2 comes into picture.

Light Components

Django-Select2 includes many widgets suited to various use-cases. Select2Widget and Select2MultipleWidget widgets are suited for scenarios where we have a static list of choices which may not may not be large. They are not meant to be used when the options are too many, say, in thousands. This is because all those options would have to be pre-rendered onto the page and Javascript would be used to search through them. Said that, they are also one the most easiest to use. They are almost drop-in-replacement for Django’s default select widgets, and they look much much better.

Heavy Components

HeavySelect2Widget and HeavySelect2MultipleWidget widgets are suited for scenarios when the number of options are large and need complex queries (from maybe different data sources) to get the options. This dynamic fetching of options undoubtably requires Ajax communication with the server. Django-Select2 includes a helper JS file which is included automatically, so you need not worry about writing any Ajax related JS code. Although on the server side you do need to create a view specifically to respond to the queries. The format of the response is decided by the JS code being used on the client side. The included abstract view – Select2View, will make sure to format the response into the format expected by the helper JS code. Below is a example on how to use it.

#!python
from django.db.models import Q
from django_select2 import Select2View, NO_ERR_RESP
from .models import Employee

class EmployeeSelect2View(Select2View):
    def check_all_permissions(self, request, *args, **kwargs):
        user = request.user if not (user.is_authenticated() and user.has_perms('emp.view_employees')):
            raise PermissionDenied

    def get_results(self, request, term, page, context):
        emps = Employee.objects.filter( Q(first_name__icontains=term) | Q(last_name__icontains=term) | Q(emp_no__icontains=term))
        res = [ (emp.id, "%s %s" % (emp.first_name, emp.last_name),) for emp in emps ]
        return (NO_ERR_RESP, False, res) # Any error response, Has more results, options list

How many such views you will need depends totally on your use-case. From Django-Select2 there is no restriction on their reuse. If you feel that writing these views are too much of a hassle then you have an alternate option – sub-class AutoSelect2Field field. In your sub-classed field you need to override security_check(self, request, *args, **kwargs) and get_results(self, request, term, page, context) methods. When your field will be instantiated for the first time, it will register its own instance with AutoResponseView. When the related field is used in the browser, the queries would be directed to AutoResponseView which will direct it to your ‘auto’ field instance. For ‘auto’ fields to work you must use the following code in your urls.py to register the url for AutoResponseView.

#!python
urlpatterns += patterns("", url(r"^select2/", include("django_select2.urls")), )

Django-Select2 Fields

The following fields are available in Django-Select2.

  • Select2ChoiceField – Uses Select2Widget.
  • Select2MultipleChoiceField – Uses Select2MultipleWidget.
  • HeavySelect2ChoiceField – Uses HeavySelect2Widget.
  • HeavySelect2MultipleChoiceField – Uses HeavySelect2MultipleWidget.
  • ModelSelect2Field – Uses Select2ChoiceField. It additionally requires queryset argument. It similar to Django’s ModelChoiceField.
  • AutoSelect2Field – Uses HeavySelect2ChoiceField. Auto register’s itself with AutoResponseView.
  • AutoModelSelect2Field – Similar to AutoSelect2Field, but like ModelSelect2Field, normalizes values to Django model objects.

Download Django-Select2

You can download it or fork it from https://github.com/applegrew/django-select2. You can also add this to you pip requirement files as:-

-e git+https://github.com/applegrew/django-select2.git#egg=django-select2

Update: Now can simply add django_select2 to your pip requirement. If you want to install it manually then you can simply run:-

pip install django_select2

Update: Now you can install beta version of django_select2 compatible with Python3.

pip install Django-Select2-Py3

Closing Statement

It is recommended that you go through the codes to familiarize yourself with how to efficiently use Django-Select2. The code is not very complex so you should not face much problem in understanding it.