django-select2

repository·main·Indexed 42 days ago

https://github.com/codingjoe/django-select2

A 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.

Tokens
8.2K
Snippets
29
Records
32
Agent score
52%

What's inside django-select2

  1. Overview of Django-Select2

    main
    Django-Select2 provides custom autocomplete fields for Django applications. It allows you to implement Select2-powered autocomplete functionality in your Django forms, extending beyond the built-in support provided by Django's admin autocomplete_fields feature.
  2. Implement Multi-dependent Select2 widgets

    main

    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'},
            )
        )
  3. Quick Start: Implement Select2 autocomplete fields

    main

    To use Select2 in a Django project, follow these steps:

    1. Define your Models: Use standard Django models (e.g., a ForeignKey or ManyToManyField).
    2. Create Select2 Widgets: In your 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.
    3. Configure the Form: In your ModelForm, map the model fields to your custom Select2 widgets in the Meta.widgets dictionary.
    4. Render in Template: In your HTML template, you must include {{ 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>
  4. Run the django-select2 sample app

    main

    To set up and run the sample application locally, follow these steps:

    1. Clone the repository and navigate to the example directory.
    2. Install the required Python dependencies.
    3. Run database migrations.
    4. Create a Django superuser (follow the interactive prompts).
    5. Start the development server.
    6. Open your browser to access the application.
    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 runserver
  5. Install Redis for caching

    main

    The 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 -y

    macOS:

    brew install redis
    sudo apt-get install redis-server -y
  6. Secure Select2 data endpoints

    main

    To prevent sensitive data from being accessible to the general public, you can create a secure JSON endpoint for your Select2 widgets.

    1. Create a view that inherits from LoginRequiredMixin and AutoResponseView.
    2. Create a mixin that sets the data_view attribute to the name of your secure view.
    3. Use this mixin in your widget definition.

    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']
  7. Implement Chained Select2 widgets

    main

    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:

    • The 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.
    • Limitation: This feature does not work with the 'light' version (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,
            )
        )
  8. Implement Interdependent Select2 widgets

    main

    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,
            )
        )
  9. Install django-select2

    main

    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.

    1. Install via pip: python3 -m pip install django-select2

    2. Update settings.py: Add django_select2 and django.contrib.admin to INSTALLED_APPS.

    3. 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")),
    ]
  10. Initialize Django-Select2 JavaScript

    main

    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>
  11. Configure a persistent cache for Select2

    main

    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:

    1. Install a Redis server (via apt or brew).
    2. Install the django-redis client: python3 -m pip install django-redis.
    3. Define a named cache in 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"
  12. Configure dependent fields for cascading Select2 widgets

    main

    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:

    • The attribute accepts a space-separated list of field names.
    • The plugin uses a CSS selector [data-select2-dependent-fields~=${name}] to find targets.
    • This is essential for maintaining data integrity in forms with hierarchical dependencies.
    <!-- 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>