Django-Select2¶
This is a Django integration of Select2.
The app includes Select2 driven Django Widgets.
Documentation¶
Documentation available at https://django-select2.readthedocs.io/.
Note
Django’s admin comes with builtin support for Select2 via the autocomplete_fields feature.
Installation¶
Install django-select2
python3 -m pip install django-select2
Add django_select2
to your INSTALLED_APPS
in your project settings.
Add django_select
to your URL root configuration:
from django.urls import include, path
urlpatterns = [
# … other patterns
path("select2/", include("django_select2.urls")),
# … other patterns
]
Finally make sure you have a persistent cache backend setup (NOT
DummyCache
or LocMemCache
), we will use Redis in this
example. Make sure you have a Redis server up and running:
# Debian
sudo apt-get install redis-server
# macOS
brew install redis
# install Redis python client
python3 -m pip install django-redis
Next, add the cache configuration to your settings.py
as follows:
CACHES = {
# … default cache config and others
"select2": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/2",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
# Tell select2 which cache configuration to use:
SELECT2_CACHE_BACKEND = "select2"
External Dependencies¶
- jQuery is not included in the package since it is expected that in most scenarios this would already be available.
Quick Start¶
Here is a quick example to get you started:
First make sure you followed the installation instructions above. Once everything is setup, let’s start with a simple example.
We have the following model:
# models.py
from django.conf import settings
from django.db import models
class Book(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
co_authors = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='co_authored_by')
Next, we create a model form with custom Select2 widgets.
# forms.py
from django import forms
from django_select2 import forms as s2forms
from . import models
class AuthorWidget(s2forms.ModelSelect2Widget):
search_fields = [
"username__icontains",
"email__icontains",
]
class CoAuthorsWidget(s2forms.ModelSelect2MultipleWidget):
search_fields = [
"username__icontains",
"email__icontains",
]
class BookForm(forms.ModelForm):
class Meta:
model = models.Book
fields = "__all__"
widgets = {
"author": AuthorWidget,
"co_authors": CoAuthorsWidget,
}
A simple class based view will do, to render your form:
# views.py
from django.views import generic
from . import forms, models
class BookCreateView(generic.CreateView):
model = models.Book
form_class = forms.BookForm
success_url = "/"
Make sure to add the view to your urls.py
:
# urls.py
from django.urls import include, path
from . import views
urlpatterns = [
# … other patterns
path("select2/", include("django_select2.urls")),
# … other patterns
path("book/create", views.BookCreateView.as_view(), name="book-create"),
]
Finally, we need a little template, myapp/templates/myapp/book_form.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Create Book</title>
{{ form.media.css }}
<style>
input, select {width: 100%}
</style>
</head>
<body>
<h1>Create a new Book</h1>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit">
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
{{ form.media.js }}
</body>
</html>
Done - enjoy the wonders of Select2!
Changelog¶
See Github releases.
All Contents¶
Contents:
API Documentation¶
Configuration¶
Settings for Django-Select2.
-
class
django_select2.conf.
Select2Conf
(**kwargs)[source]¶ Bases:
appconf.base.AppConf
Settings for Django-Select2.
-
LIB_VERSION
= '4.0.12'¶ Version of the Select2 library.
-
CACHE_BACKEND
= 'default'¶ Django-Select2 uses Django’s cache to sure a consistent state across multiple machines.
Example of settings.py:
CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } }, 'select2': { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/2", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } } # Set the cache backend to select2 SELECT2_CACHE_BACKEND = 'select2'
Tip
To ensure a consistent state across all you machines you need to user a consistent external cache backend like Memcached, Redis or a database.
Note
Should you have copied the example configuration please make sure you have Redis setup. It’s recommended to run a separate Redis server in a production environment.
Note
The timeout of select2’s caching backend determines how long a browser session can last. Once widget is dropped from the cache the json response view will return a 404.
-
CACHE_PREFIX
= 'select2_'¶ If you caching backend does not support multiple databases you can isolate select2 using the cache prefix setting. It has set select2_ as a default value, which you can change if needed.
-
JS
= 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.12/js/select2.min.js'¶ The URI for the Select2 JS file. By default this points to the Cloudflare CDN.
If you want to select the version of the JS library used, or want to serve it from the local ‘static’ resources, add a line to your settings.py like so:
SELECT2_JS = 'assets/js/select2.min.js'
If you provide your own JS and would not like Django-Select2 to load any, change this setting to a blank string like so:
SELECT2_JS = ''
Tip
Change this setting to a local asset in your development environment to develop without an Internet connection.
-
CSS
= 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.12/css/select2.min.css'¶ The URI for the Select2 CSS file. By default this points to the Cloudflare CDN.
If you want to select the version of the library used, or want to serve it from the local ‘static’ resources, add a line to your settings.py like so:
SELECT2_CSS = 'assets/css/select2.css'
If you provide your own CSS and would not like Django-Select2 to load any, change this setting to a blank string like so:
SELECT2_CSS = ''
Tip
Change this setting to a local asset in your development environment to develop without an Internet connection.
-
I18N_PATH
= 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.12/js/i18n'¶ The base URI for the Select2 i18n files. By default this points to the Cloudflare CDN.
If you want to select the version of the I18N library used, or want to serve it from the local ‘static’ resources, add a line to your settings.py like so:
SELECT2_I18N_PATH = 'assets/js/i18n'
Tip
Change this setting to a local asset in your development environment to develop without an Internet connection.
-
I18N_AVAILABLE_LANGUAGES
= ['ar', 'az', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es', 'et', 'eu', 'fa', 'fi', 'fr', 'gl', 'he', 'hi', 'hr', 'hu', 'id', 'is', 'it', 'ja', 'km', 'ko', 'lt', 'lv', 'mk', 'ms', 'nb', 'nl', 'pl', 'pt-BR', 'pt', 'ro', 'ru', 'sk', 'sr-Cyrl', 'sr', 'sv', 'th', 'tr', 'uk', 'vi', 'zh-CN', 'zh-TW']¶ List of available translations.
List of ISO 639-1 language codes that are supported by Select2. If currently set language code (e.g. using the HTTP
Accept-Language
header) is in this list, Django-Select2 will use the language code to create load the proper translation.The full path for the language file consists of:
from django.utils import translations full_path = "{i18n_path}/{language_code}.js".format( i18n_path=settings.DJANGO_SELECT2_I18N, language_code=translations.get_language(), )
settings.DJANGO_SELECT2_I18N
refers toI18N_PATH
.
-
Widgets¶
Django-Select2 Widgets.
These components are responsible for rendering the necessary HTML data markups. Since this whole package is to render choices using Select2 JavaScript library, hence these components are meant to be used with choice fields.
Widgets are generally of two types:
1. Light – They are not meant to be used when there are too many options, 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 a drop-in-replacement for Django’s default select widgets.
2(a). Heavy – They are suited for scenarios when the number of options are large and need complex queries (from maybe different sources) to get the options.
This dynamic fetching of options undoubtedly 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.
2(b). Model – Model-widgets are a further specialized versions of Heavies. These do not require views to serve Ajax requests. When they are instantiated, they register themselves with one central view which handles Ajax requests for them.
Heavy and Model widgets have respectively the word ‘Heavy’ and ‘Model’ in their name. Light widgets are normally named, i.e. there is no ‘Light’ word in their names.

-
class
django_select2.forms.
Select2Mixin
[source]¶ Bases:
object
The base mixin of all Select2 widgets.
This mixin is responsible for rendering the necessary data attributes for select2 as well as adding the static form media.
-
empty_label
= ''¶
-
media
¶ Construct Media as a dynamic property.
Note
For more information visit https://docs.djangoproject.com/en/stable/topics/forms/media/#media-as-a-dynamic-property
-
-
class
django_select2.forms.
Select2TagMixin
[source]¶ Bases:
object
Mixin to add select2 tag functionality.
-
class
django_select2.forms.
Select2Widget
(attrs=None, choices=())[source]¶ Bases:
django_select2.forms.Select2Mixin
,django.forms.widgets.Select
Select2 drop in widget.
Example usage:
class MyModelForm(forms.ModelForm): class Meta: model = MyModel fields = ('my_field', ) widgets = { 'my_field': Select2Widget }
or:
class MyForm(forms.Form): my_choice = forms.ChoiceField(widget=Select2Widget)
-
media
¶
-
-
class
django_select2.forms.
Select2MultipleWidget
(attrs=None, choices=())[source]¶ Bases:
django_select2.forms.Select2Mixin
,django.forms.widgets.SelectMultiple
Select2 drop in widget for multiple select.
Works just like
Select2Widget
but for multi select.-
media
¶
-
-
class
django_select2.forms.
Select2TagWidget
(attrs=None, choices=())[source]¶ Bases:
django_select2.forms.Select2TagMixin
,django_select2.forms.Select2Mixin
,django.forms.widgets.SelectMultiple
Select2 drop in widget for for tagging.
Example for
django.contrib.postgres.fields.ArrayField
:class MyWidget(Select2TagWidget): def value_from_datadict(self, data, files, name): values = super().value_from_datadict(data, files, name): return ",".join(values) def optgroups(self, name, value, attrs=None): values = value[0].split(',') if value[0] else [] selected = set(values) subgroup = [self.create_option(name, v, v, selected, i) for i, v in enumerate(values)] return [(None, subgroup, 0)]
-
media
¶
-
-
class
django_select2.forms.
HeavySelect2Mixin
(attrs=None, choices=(), **kwargs)[source]¶ Bases:
object
Mixin that adds select2’s AJAX options and registers itself on Django’s cache.
-
dependent_fields
= {}¶
-
-
class
django_select2.forms.
HeavySelect2Widget
(attrs=None, choices=(), **kwargs)[source]¶ Bases:
django_select2.forms.HeavySelect2Mixin
,django_select2.forms.Select2Widget
Select2 widget with AJAX support that registers itself to Django’s Cache.
Usage example:
class MyWidget(HeavySelect2Widget): data_view = 'my_view_name'
or:
class MyForm(forms.Form): my_field = forms.ChoiceField( widget=HeavySelect2Widget( data_url='/url/to/json/response' ) )
-
media
¶
-
-
class
django_select2.forms.
HeavySelect2MultipleWidget
(attrs=None, choices=(), **kwargs)[source]¶ Bases:
django_select2.forms.HeavySelect2Mixin
,django_select2.forms.Select2MultipleWidget
Select2 multi select widget similar to
HeavySelect2Widget
.-
media
¶
-
-
class
django_select2.forms.
HeavySelect2TagWidget
(attrs=None, choices=(), **kwargs)[source]¶ Bases:
django_select2.forms.HeavySelect2Mixin
,django_select2.forms.Select2TagWidget
Select2 tag widget.
-
media
¶
-
-
class
django_select2.forms.
ModelSelect2Mixin
(*args, **kwargs)[source]¶ Bases:
object
Widget mixin that provides attributes and methods for
AutoResponseView
.-
empty_label
¶
-
model
= None¶
-
queryset
= None¶
-
search_fields
= []¶ Model lookups that are used to filter the QuerySet.
Example:
search_fields = [ 'title__icontains', ]
-
max_results
= 25¶ Maximal results returned by
AutoResponseView
.
-
set_to_cache
()[source]¶ Add widget’s attributes to Django’s cache.
Split the QuerySet, to not pickle the result set.
-
filter_queryset
(request, term, queryset=None, **dependent_fields)[source]¶ Return QuerySet filtered by search_fields matching the passed term.
Parameters: - request (django.http.request.HttpRequest) – The request is being passed from the JSON view and can be used to dynamically alter the response queryset.
- term (str) – Search term
- queryset (django.db.models.query.QuerySet) – QuerySet to select choices from.
- **dependent_fields – Dependent fields and their values. If you want to inherit from ModelSelect2Mixin and later call to this method, be sure to pop everything from keyword arguments that is not a dependent field.
Returns: Filtered QuerySet
Return type: QuerySet
-
get_queryset
()[source]¶ Return QuerySet based on
queryset
ormodel
.Returns: QuerySet of available choices. Return type: QuerySet
-
optgroups
(name, value, attrs=None)[source]¶ Return only selected options and set QuerySet from ModelChoicesIterator.
-
label_from_instance
(obj)[source]¶ Return option label representation from instance.
Can be overridden to change the representation of each choice.
Example usage:
class MyWidget(ModelSelect2Widget): def label_from_instance(obj): return str(obj.title).upper()
Parameters: obj (django.db.models.Model) – Instance of Django Model. Returns: Option label. Return type: str
-
-
class
django_select2.forms.
ModelSelect2Widget
(*args, **kwargs)[source]¶ Bases:
django_select2.forms.ModelSelect2Mixin
,django_select2.forms.HeavySelect2Widget
Select2 drop in model select widget.
Example usage:
class MyWidget(ModelSelect2Widget): search_fields = [ 'title__icontains', ] class MyModelForm(forms.ModelForm): class Meta: model = MyModel fields = ('my_field', ) widgets = { 'my_field': MyWidget, }
or:
class MyForm(forms.Form): my_choice = forms.ChoiceField( widget=ModelSelect2Widget( model=MyOtherModel, search_fields=['title__icontains'] ) )
Tip
The ModelSelect2(Multiple)Widget will try to get the QuerySet from the fields choices. Therefore you don’t need to define a QuerySet, if you just drop in the widget for a ForeignKey field.
-
media
¶
-
-
class
django_select2.forms.
ModelSelect2MultipleWidget
(*args, **kwargs)[source]¶ Bases:
django_select2.forms.ModelSelect2Mixin
,django_select2.forms.HeavySelect2MultipleWidget
Select2 drop in model multiple select widget.
Works just like
ModelSelect2Widget
but for multi select.-
media
¶
-
-
class
django_select2.forms.
ModelSelect2TagWidget
(*args, **kwargs)[source]¶ Bases:
django_select2.forms.ModelSelect2Mixin
,django_select2.forms.HeavySelect2TagWidget
Select2 model widget with tag support.
This it not a simple drop in widget. It requires to implement you own
value_from_datadict()
that adds missing tags to you QuerySet.Example:
class MyModelSelect2TagWidget(ModelSelect2TagWidget): queryset = MyModel.objects.all() def value_from_datadict(self, data, files, name): '''Create objects for given non-pimary-key values. Return list of all primary keys.''' values = set(super().value_from_datadict(data, files, name)) # This may only work for MyModel, if MyModel has title field. # You need to implement this method yourself, to ensure proper object creation. pks = self.queryset.filter(**{'pk__in': list(values)}).values_list('pk', flat=True) pks = set(map(str, pks)) cleaned_values = list(values) for val in values - pks: cleaned_values.append(self.queryset.create(title=val).pk) return cleaned_values
-
media
¶
-
URLs¶
Django-Select2 URL configuration.
Add django_select to your urlconf
if you use any ‘Model’ fields:
from django.urls import path
path('select2/', include('django_select2.urls')),
-
django_select2.urls.
path
(route, view, kwargs=None, name=None, *, Pattern=<class 'django.urls.resolvers.RoutePattern'>)¶
Views¶
JSONResponse views for model widgets.
-
class
django_select2.views.
AutoResponseView
(**kwargs)[source]¶ Bases:
django.views.generic.list.BaseListView
View that handles requests from heavy model widgets.
The view only supports HTTP’s GET method.
-
get
(request, *args, **kwargs)[source]¶ Return a
django.http.JsonResponse
.Example:
{ 'results': [ { 'text': "foo", 'id': 123 } ], 'more': true }
-
get_widget_or_404
()[source]¶ Get and return widget from cache.
Raises: Http404
– If if the widget can not be found or no id is provided.Returns: Widget from cache. Return type: ModelSelect2Mixin
-
Cache¶
Shared memory across multiple machines to the heavy AJAX lookups.
Select2 uses django.core.cache to share fields across multiple threads and even machines.
Select2 uses the cache backend defined in the setting
SELECT2_CACHE_BACKEND
[default=``default``].
It is advised to always setup a separate cache server for Select2.
JavaScript¶
DjangoSelect2 handles the initialization of select2 fields automatically. Just include
{{ form.media.js }}
in your template before the closing body
tag. That’s it!
If you insert forms after page load or if you want to handle the initialization
yourself, DjangoSelect2 provides a jQuery plugin, replacing and enhancing the Select2
plugin. It will handle both normal and heavy fields. Simply call
djangoSelect2(options)
on your select fields.:
$('.django-select2').djangoSelect2();
You can pass see Select2 options if needed:
$('.django-select2').djangoSelect2({placeholder: 'Select an option'});
Please replace all your .select2
invocations with the here provided
.djangoSelect2
.
Security & Authentication¶
Security is important. Therefore make sure to read and understand what the security measures in place and their limitations.
Set up a separate cache. If you have a public form that uses a model widget make sure to setup a separate cache database for Select2. An attacker could constantly reload your site and fill up the select2 cache. Having a separate cache allows you to limit the effect to select2 only.
You might want to add a secure select2 JSON endpoint for data you don’t want to be accessible to the general public. Doing so is easy:
class UserSelect2View(LoginRequiredMixin, AutoResponseView):
pass
class UserSelect2WidgetMixin(object):
def __init__(self, *args, **kwargs):
kwargs['data_view'] = 'user-select2-view'
super(UserSelect2WidgetMixin, self).__init__(*args, **kwargs)
class MySecretWidget(UserSelect2WidgetMixin, ModelSelect2Widget):
model = MySecretModel
search_fields = ['title__icontains']
Extra¶
Chained select2¶
Suppose you have an address form where a user should choose a Country and a City. When the user selects a country we want to show only cities belonging to that country. So the one selector depends on another one.
Models¶
Here are our two models:
class Country(models.Model):
name = models.CharField(max_length=255)
class City(models.Model):
name = models.CharField(max_length=255)
country = models.ForeignKey('Country', related_name="cities")
Customizing a Form¶
Lets link two widgets via dependent_fields.
class AddressForm(forms.Form):
country = forms.ModelChoiceField(
queryset=Country.objects.all(),
label=u"Country",
widget=ModelSelect2Widget(
model=Country,
search_fields=['name__icontains'],
)
)
city = forms.ModelChoiceField(
queryset=City.objects.all(),
label=u"City",
widget=ModelSelect2Widget(
model=City,
search_fields=['name__icontains'],
dependent_fields={'country': 'country'},
max_results=500,
)
)
Interdependent select2¶
Also you may want not to restrict the user to which field should be selected first. Instead you want to suggest to the user options for any select2 depending of his selection in another one.
Customize the form in a manner:
class AddressForm(forms.Form):
country = forms.ModelChoiceField(
queryset=Country.objects.all(),
label=u"Country",
widget=ModelSelect2Widget(
search_fields=['name__icontains'],
dependent_fields={'city': 'cities'},
)
)
city = forms.ModelChoiceField(
queryset=City.objects.all(),
label=u"City",
widget=ModelSelect2Widget(
search_fields=['name__icontains'],
dependent_fields={'country': 'country'},
max_results=500,
)
)
Take attention to country’s dependent_fields. The value of ‘city’ is ‘cities’ because of related name used in a filter condition cities which differs from widget field name city.
Caution
Be aware of using interdependent select2 in parent-child relation. When a child is selected, you are restricted to change parent (only one value is available). Probably you should let the user reset the child first to release parent select2.
Multi-dependent select2¶
Furthermore you may want to filter options on two or more select2 selections (some code is dropped for clarity):
class SomeForm(forms.Form):
field1 = forms.ModelChoiceField(
widget=ModelSelect2Widget(
)
)
field2 = forms.ModelChoiceField(
widget=ModelSelect2Widget(
)
)
field3 = forms.ModelChoiceField(
widget=ModelSelect2Widget(
dependent_fields={'field1': 'field1', 'field2': 'field2'},
)
)
Contributing¶
This package uses the pyTest test runner. To run the tests locally simply run:
python setup.py test
If you need to the development dependencies installed of you local IDE, you can run:
python setup.py develop
Documentation pull requests welcome. The Sphinx documentation can be compiled via:
python setup.py build_sphinx
Bug reports welcome, even more so if they include a correct patch. Much more so if you start your patch by adding a failing unit test, and correct the code until zero unit tests fail.
The list of supported Django and Python version can be found in the CI suite setup. Please make sure to verify that none of the linters or tests failed, before you submit a patch for review.