Package: django-tomselect
Tom Select for DjangoDjango autocomplete widgets and views usingTom Select.This package provides a Django autocomplete widget and view that can be used
together to provide a user interface for selecting a model instance from a
database table.The package is adapted from the fantastic work ofPhilip Beckerinmizdb-tomselect, with the goal
of a more generalized solution for Django autocompletion.Tom Select for DjangoInstallationUsageWidgetsTomSelectWidgetTomSelectTabularWidgetAdding more columnsSettingsTOMSELECT_BOOTSTRAP_VERSIONFunction & FeaturesModifying the initial QuerySetSearchingOption creationAJAX requestList View linkChained Dropdown FilteringManually Initializing Tom Select FieldsDevelopment & DemoInstallationInstall:pipinstall-Udjango-tomselectUsageAdd to installed apps:INSTALLED_APPS=[# ..."django_tomselect"]Configure an endpoint for autocomplete requests:# urls.pyfromdjango.urlsimportpathfromdjango_tomselect.viewsimportAutocompleteViewurlpatterns=[# ...path("autocomplete/",AutocompleteView.as_view(),name="my_autocomplete_view")]Use the widgets in a form.fromdjangoimportformsfromdjango_tomselect.widgetsimportTomSelectWidget,TomSelectTabularWidgetfrom.modelsimportCity,PersonclassMyForm(forms.Form):city=forms.ModelChoiceField(City.objects.all(),widget=TomSelectWidget(City,url="my_autocomplete_view"),)# Display results in a table, with additional columns for fields# "first_name" and "last_name":person=forms.ModelChoiceField(Person.objects.all(),widget=TomSelectTabularWidget(url="my_autocomplete_view",search_lookups=["full_name__icontains",],# for extra columns pass a mapping of {"model_field": "Column Header Label"}extra_columns={"first_name":"First Name","last_name":"Last Name"},# The column header label for the labelField columnlabel_field_label="Full Name",),)NOTE: Make sure to includebootstrapsomewhere. For example in the template:<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><title>Django Tom Select Demo</title><scriptsrc="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"crossorigin="anonymous"></script><linkhref="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"rel="stylesheet"integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65"crossorigin="anonymous">{{ form.media }}</head><body><divclass="container"><form>{% csrf_token %}
        {{ form.as_div }}<buttontype="submit"class="btn btn-success">Save</button></form></div></body></html>WidgetsThe widgets pass attributes necessary to make autocomplete requests to the
HTML element via the dataset property. The Tom Select element is then initialized
from the attributes in the dataset property.TomSelectWidget & TomSelectMultipleWidgetBase autocomplete widgets forModelChoiceFieldandModelMultipleChoiceField. The arguments of TomSelectWidget & TomSelectMultipleWidget are:ArgumentDefault valueDescriptionmodelrequiredthe model class that provides the choicesurl"autocomplete"URL pattern name of the autocomplete viewvalue_fieldf"{model._meta.pk.name}"model field that provides the value of an optionlabel_fieldgetattr(model, "name_field", "name")model field that provides the label of an optionsearch_lookups[f"{self.value_field}__icontains", f"{self.label_field}__icontains"]the list of lookups to use when filtering the resultscreate_field""model field to create new objects with (see below)listview_url""URL name of the list view for this model (see below)add_url""URL name of the add view for this model(see below)edit_url""URL name of the edit view for each instance of this model(see below)filter_by()a 2-tuple defining an additional filter (see below)bootstrap_version5the bootstrap version to use, either4or5TomSelectTabularWidget & TomSelectTabularMultipleWidgetThese widgets displays the results in tabular form. A table header will be added
to the dropdown. By default, the table contains two columns: one column for the choice
value (commonly the "ID" of the option) and one column for the choice label (the
human-readable part of the choice).TomSelectTabularWidget & TomSelectTabularMultipleWidget have the following additional arguments:ArgumentDefault valueDescriptionextra_columnsa mapping for additional columnsvalue_field_labelf"{value_field.title()}"table header for the value columnlabel_field_labelf"{model._meta.verbose_name}"table header for the label columnlabel_field_labelf"{model._meta.verbose_name}"table header for the label columnshow_value_fieldFalseshow the value field column (typicallyid)Adding more columns to the tabular widgetsTo add more columns, pass a dictionary mapping field names to column labels asextra_columnsto the widget's arguments.fromdjangoimportformsfromdjango_tomselect.widgetsimportTomSelectTabularWidgetfrom.modelsimportPersonclassMyForm(forms.Form):person=forms.ModelChoiceField(Person.objects.all(),widget=TomSelectTabularWidget(url="my_autocomplete_view",# for extra columns pass a mapping of {"model_field": "Column Header Label"}extra_columns={"first_name":"First Name","last_name":"Last Name"},),)The column label is the header label for a given column in the table.The attribute name tells Tom Select what value to look up on a result for the column.Important: that means that the result visible to Tom Select must have an attribute
or property with that name or the column will remain empty.
The results for Tom Select are created by the view callingvalues()on the
result queryset, so you must make sure that the attribute name is available
on the view's root queryset as either a model field or as an annotation.SettingsSettingDefault valueDescriptionTOMSELECT_BOOTSTRAP_VERSION5The bootstrap version to use. Either4or5. Defaults to5. This sets the project-wide default for thebootstrap_versionargument of the widgets.You can overwrite the default for a specific widget by passing thebootstrap_versionargument to the widget. This sets the project-wide default for thebootstrap_versionargument of the widgets. You can overwrite the default for a specific widget by passing thebootstrap_versionargument to the widget.TOMSELECT_PROXY_REQUEST"django_tomselect.utils.DefaultProxyRequest"Either a direct reference to a DefaultProxyRequest subclass or the path to the DefaultProxyRequest subclass to use. This class is used to obtain the model details for the autocomplete.In order to simplify the process of creating a custom autocomplete view, django-tomselect provides aDefaultProxyRequestclass that can be used to obtain the model details from the queryset and the request. This class is used by the widget to obtain the model details for the autocomplete. In most cases, you will not need to use this class directly.Function & FeaturesModifying the initial QuerySetIf you want to modify all autocomplete queries for a subclassed AutocompleteView, you can usesuper()with theget_queryset()method.fromdjango_tomselect.viewsimportAutocompleteViewclassMyAutocompleteView(AutocompleteView):defget_queryset(self):"""Toy example of filtering all queries in this view to id values less than 10"""queryset=super().get_queryset()queryset.filter(id__lt=10)returnquerysetSearchingThe AutocompleteView filters the result QuerySet against thesearch_lookupspassed to the widget. The default value for the lookup isname__icontains.
Overwrite theAutocompleteView.searchmethod to modify the search process.fromdjango_tomselect.viewsimportAutocompleteViewclassMyAutocompleteView(AutocompleteView):defsearch(self,queryset,q):# Filter using your own queryset method:returnqueryset.search(q)Option creationTo enable option creation in the dropdown, pass the URL pattern name of the
add page of the given model to the widget. This will add an 'Add' button to the
bottom of the dropdown.# urls.pyfromdjango.urlsimportpathfromdjango_tomselect.viewsimportAutocompleteViewfromdjango_tomselect.widgetsimportTomSelectWidgetfrom.modelsimportCityfrom.viewsimportCityAddViewurlpatterns=[# ...path("autocomplete/",AutocompleteView.as_view(),name="my_autocomplete_view"),path("city/add/",CityAddView.as_view(),name="city_add"),]# forms.pywidget=TomSelectWidget(City,url="my_autocomplete_view",add_url="city_add")Clicking on that button sends the user to the add page of the model.AJAX requestIfcreate_fieldwas also passed to the widget, clicking on the button will
create a new object using an AJAX POST request to the autocomplete URL. The
autocomplete view will use the search term that the user put in on thecreate_fieldto create the object:classAutocompleteView:defcreate_object(self,data):"""Create a new object with the given data."""returnself.model.objects.create(**{self.create_field:data[self.create_field]})Override the view'screate_objectmethod to change the creation process.List View linkThe dropdown will include a link to the list view of the given model if you
pass in the URL pattern name of the list view.# urls.pyfromdjango.urlsimportpathfromdjango_tomselect.viewsimportAutocompleteViewfromdjango_tomselect.widgetsimportTomSelectWidgetfrom.modelsimportCityfrom.viewsimportCityListViewurlpatterns=[# ...path("autocomplete/",AutocompleteView.as_view(),name="my_autocomplete_view"),path("city/list/",CityListView.as_view(),name="city_listview"),]# forms.pywidget=TomSelectWidget(City,url="my_autocomplete_view",listview_url="city_listview")Chained Dropdown FilteringUse thefilter_byargument to restrict the available options of one
TomSelectWidget to the value selected in another form field. The parameter must
be a 2-tuple:(name_of_the_other_form_field, django_field_lookup)# models.pyfromdjangoimportformsfromdjango.dbimportmodelsfromdjango_tomselect.widgetsimportTomSelectWidgetclassPerson(models.Model):name=models.CharField(max_length=50)city=models.ForeignKey("City",on_delete=models.SET_NULL,blank=True,null=True)classCity(models.Model):name=models.CharField(max_length=50)is_capitol=models.BooleanField(default=False)# forms.pyclassPersonsFromCapitolsForm(forms.Form):capitol=forms.ModelChoiceField(queryset=City.objects.filter(is_capitol=True))person=forms.ModelChoiceField(queryset=Person.objects.all(),widget=TomSelectWidget(Person,filter_by=("capitol","city_id")),)This will result in the Person result queryset to be filtered againstcity_idfor the currently selectedcapitolformfield value.NOTE: When usingfilter_by, the declaring element nowrequiresthat the
other field provides a value, since its choices are dependent on the other
field. If the other field does not have a value, the search will not return any
results.Advanced TopicsManually Initializing Tom Select FieldsIf a form is added dynamically after the page loads (e.g.: with htmx), the new form
fields will not be initialized as django-tomselect fields. In order to manually
initialize them, dispatch atriggerTomSelectevent, providing the id of the
form field as a value indetailas follows.<script>window.dispatchEvent(newCustomEvent('triggerTomSelect',{detail:{elemID:'id_tomselect_tabular'}}));</script>Development & Demopython3-mvenvvenvsourcevenv/bin/activate
makeinitThen see the demo for a preview:python demo/manage.py runserverRun tests withmake testormake tox. To install required browsers for playwright:playwright install.
See the makefile for other commands.