Overview of Django-Select2
mainautocomplete_fields feature.repository·main·Indexed 42 days ago
https://github.com/codingjoe/django-select2A Django integration of Select2 that provides custom autocomplete fields for Django forms. It includes Light widgets for small datasets, Heavy widgets for AJAX-based selection, and ModelSelect2Widget for automatic model-based AJAX. Features include support for tagging, dependent cascading fields, and customizable cache backends, themes, and i18n assets.
autocomplete_fields feature.A single Select2 widget can depend on multiple other fields simultaneously. You can pass multiple entries into the dependent_fields dictionary to filter the queryset based on the values of several other form fields.
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'},
)
)To use Select2 in a Django project, follow these steps:
ForeignKey or ManyToManyField).forms.py, subclass s2forms.ModelSelect2Widget (for single selection) or s2forms.ModelSelect2MultipleWidget (for multiple selection) from django_select2.forms. Define search_fields to specify which model fields are searchable.ModelForm, map the model fields to your custom Select2 widgets in the Meta.widgets dictionary.{{ form.media.css }} in the <head> and {{ form.media.js }} (usually near the end of the <body>). Note that django-select2 requires jQuery to be present in the page, but it is not bundled with the package.Note on jQuery: You must manually include jQuery in your template (e.g., via CDN) before {{ form.media.js }}.
# 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,
}<!-- template.html -->
<head>
{{ form.media.css }}
</head>
<body>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit">
</form>
<!-- jQuery is required -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
{{ form.media.js }}
</body>To set up and run the sample application locally, follow these steps:
git clone https://github.com/codingjoe/django-select2.git
cd django-select2/example
python3 -m pip install -r requirements.txt
python3 manage.py migrate
python3 manage.py createsuperuser
python3 manage.py runserverThe sample application requires Redis to be installed on your system for caching purposes. Use the following commands depending on your operating system:
Debian/Ubuntu:
sudo apt-get install redis-server -ymacOS:
brew install redissudo apt-get install redis-server -yTo prevent sensitive data from being accessible to the general public, you can create a secure JSON endpoint for your Select2 widgets.
LoginRequiredMixin and AutoResponseView.data_view attribute to the name of your secure view.Additionally, it is recommended to set up a separate cache database for Select2 to mitigate potential Denial of Service (DoS) attacks where an attacker attempts to fill the cache by repeatedly reloading public forms.
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']You can create a dependency where the options in one Select2 widget are filtered based on the selection of another. This is achieved using the dependent_fields argument in the ModelSelect2Widget.
Key Details:
dependent_fields argument is a dictionary where the key is the name of the field in the Django form, and the value is the name of the field in the model used for the queryset filter.django_select2.forms.Select2Widget).Example:
In an address form, selecting a Country should filter the available City options.
class AddressForm(forms.Form):
country = forms.ModelChoiceField(
queryset=Country.objects.all(),
label="Country",
widget=ModelSelect2Widget(
model=Country,
search_fields=['name__icontains'],
)
)
city = forms.ModelChoiceField(
queryset=City.objects.all(),
label="City",
widget=ModelSelect2Widget(
model=City,
search_fields=['name__icontains'],
dependent_fields={'country': 'country'}, # 'country' is form field, 'country' is model field
max_results=500,
)
)Interdependent widgets allow two or more fields to influence each other's available options, rather than having a strict parent-child hierarchy. This is useful when you want to suggest options for any field based on the selection in another.
Implementation Note:
When setting dependent_fields, the value must correspond to the model's related name or field used in the filter condition. If the model field name differs from the widget field name, use the model's attribute name.
Caution: When using interdependent widgets in a parent-child relationship, selecting a child may restrict the user from changing the parent (as only one value remains valid). It is recommended to allow users to reset the child field to release the parent Select2.
class AddressForm(forms.Form):
country = forms.ModelChoiceField(
queryset=Country.objects.all(),
label="Country",
widget=ModelSelect2Widget(
search_fields=['name__icontains'],
dependent_fields={'city': 'cities'}, # 'cities' is the related_name on the Country model
)
)
city = forms.ModelChoiceField(
queryset=City.objects.all(),
label="City",
widget=ModelSelect2Widget(
search_fields=['name__icontains'],
dependent_fields={'country': 'country'},
max_results=500,
)
)Install the package using pip and register it in your Django project settings. Since version 8, you must also ensure django.contrib.admin is included in your INSTALLED_APPS.
Install via pip:
python3 -m pip install django-select2
Update settings.py:
Add django_select2 and django.contrib.admin to INSTALLED_APPS.
Update urls.py:
Include django_select2.urls in your project's URL configuration.
# settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django_select2",
]
# urls.py
from django.urls import include, path
urlpatterns = [
path("select2/", include("django_select2.urls")),
]Django-Select2 can handle field initialization automatically. To enable this, include {{ form.media.js }} in your template before the closing </body> tag.
If you are inserting forms dynamically after the initial page load, or if you prefer manual control, use the provided jQuery plugin. Replace all existing .select2() calls with .djangoSelect2() to ensure both normal and heavy fields are handled correctly.
<!-- In your template -->
{{ form.media.js }}
<!-- Manual initialization for dynamic forms -->
<script>
$('.django-select2').djangoSelect2();
</script>The Model-widgets require a persistent cache backend across all application servers to store metadata for result fetching.
Important: The .DummyCache backend will not work. While .LocMemCache works for single-node setups, multi-server environments require a shared backend like Redis.
To use Redis:
apt or brew).django-redis client: python3 -m pip install django-redis.CACHES and assign it to SELECT2_CACHE_BACKEND in settings.py.# settings.py
CACHES = {
"select2": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/2",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
}
}
SELECT2_CACHE_BACKEND = "select2"You can link Select2 widgets so that changing one resets the others. This is achieved using the data-select2-dependent-fields attribute.
If element A is selected, any element B that has element A's name in its data-select2-dependent-fields list will have its value cleared (.val('').trigger('change')).
Implementation Details:
[data-select2-dependent-fields~=${name}] to find targets.<!-- The 'parent' field -->
<select class="django-select2" name="country"></select>
<!-- The 'dependent' field: will be cleared when 'country' changes -->
<select
class="django-select2 django-select2-heavy"
name="city"
data-select2-dependent-fields="country">
</select>