.. _selectize: ================ Selectize Widget ================ Rendering choice fields using a ```` element. .. _lookup expression: https://docs.djangoproject.com/en/stable/ref/models/lookups/#lookup-reference Endpoint for Dynamic Queries ---------------------------- In comparison to other libraries offering autocomplete fields, such as `Django-Select2`_, **django-formset** does not require adding an explicit endpoint to the URL routing. Instead it shares the same endpoint for form submission as for querying for extra options out of the database. This means that the form containing a field using the ``Selectize`` widget *must* be controlled by a view inheriting from :class:`formset.views.IncompleteSelectResponseMixin`. .. note:: The default view offered by **django-formset**, :class:`formset.views.FormView` already inherits from ``IncompleteSelectResponseMixin``. .. _Django-Select2: https://django-select2.readthedocs.io/en/latest/ Implementation Details ---------------------- The client part of the ``Selectize`` widget relies on Tom-Select_ which itself is a fork of the popular `Selectize.js`_-library, but rewritten in pure TypeScript and without any other external dependencies. This made it suitable for the client part of **django-formset**, which itself is a self-contained JavaScript library compiled out of TypeScript. .. _Tom-Select: https://tom-select.js.org/ .. _Selectize.js: https://selectize.dev/ .. _selectize-multiple: SelectizeMultiple Widget ======================== If the form field for "``city``" shall accept more than one selection, in Django we replace it by a :class:`django.forms.fields.MultipleChoiceField`. The widget then used to handle such an input field also must be replaced. **django-formset** offers the special widget :class:`formset.widgets.SelectizeMultiple` to handle more than one option to select from. From a functional point of view, this behaves similar to the Selectize widget described before. But instead of replacing a chosen option by another one, selected options are lined up to build a set of options. .. image:: _static/selectize-multiple.png :width: 760 :alt: SelectizeMultiple widget By default a ``SelectizeMultiple`` widget can accept up to 5 different options. This limit can be adjusted by increasing the argument of ``max_items``. This value however shall not exceed more than say 15 items, otherwise the input field might become unmanageable. If you need a multiple select field able to accept hundreds of items, consider using the :ref:`dual-selector` widget. Handling ForeignKey and ManyToManyField ======================================= If we create a form out of a Django model, we explicitly have to tell it to either use the ``Selectize`` or the ``SelectizeMultiple`` widget. Say that we have an address model using a foreign key to existing cities .. code-block:: python from django.db import models class AddressModel(models.Model): # other fields city = models.ForeignKey( CityModel, verbose_name="City", on_delete=models.CASCADE, ) then when creating the corresponding Django form, we must replace the default widget ``Select`` against our special widget ``Selectize``: .. code-block:: python from django.forms import models from formset.widgets import Selectize class AddressForm(models.ModelForm): class Meta: model = AddressModel fields = '__all__' widgets = { # other fields 'city': Selectize(search_lookup='label__icontains'), } The argument ``search_lookup`` is used to build the search query. If we want to allow the user to select more than one city, we have to replace the ``ForeignKey`` against a ``ManyToManyField`` – and conveniently rename "city" to "cities". Then in the above example, we'd have to replace the ``Selectize`` widget against ``SelectizeMultiple``: .. code-block:: python from django.forms import models from formset.widgets import SelectizeMultiple class AddressForm(models.ModelForm): class Meta: model = AddressModel fields = '__all__' widgets = { # other fields 'cities': SelectizeMultiple(search_lookup='label__icontains'), } Remember that the view connecting this form must inherit from :class:`formset.views.IncompleteSelectResponseMixin`. This mixin class also handles the Ajax endpoint for the :ref:`dual-selector`. Therefore, the only task we have to do when switching from a ``SelectizeMultiple`` to a ``DualSelector`` widget, is to rewrite the widget mapping in the form's ``Meta``-class. Grouping Options ================ Sometimes it may be desirable to group options the user may select from. As an example, consider the use case where we want to choose a county in the United States. Here we use two models with a simple relationship: .. code-block:: python :caption: models.py class State(models.Model): code = models.CharField(max_length=2) name = models.CharField( max_length=20, db_index=True, ) class Meta: ordering = ['name'] def __str__(self): return self.name class County(models.Model): state = models.ForeignKey( State, on_delete=models.CASCADE, ) name = models.CharField(max_length=30) class Meta: ordering = ['state', 'name'] def __str__(self): return f"{self.name} ({self.state.code})" Since there are 3143 counties, many of them using the same name, it would be really confusing to show them in a simple list of options. Instead we typically would render them grouped by state. To achieve this, we have to tell the field ``county`` how to group them, by using the attribute ``group_field_name``. This sets up the ``Selectize``-widget to use the named field from the model specified by the queryset for grouping. .. code-block:: python :caption: forms.py class AddressForm(models.ModelForm): # other fields county = models.ModelChoiceField( queryset=County.objects.all(), widget=Selectize( search_lookup='name__icontains', group_field_name='state', ), ) When rendered, the ````-s using the state's name as their label: .. image:: _static/selectize-optgroups.png :width: 760 :alt: Selectize with option groups