Package: mizdb-tomselect
TomSelect for Django (MIZDB)Django autocomplete widgets and views usingTomSelect.Note that this was written specifically with theMIZDBapp in mind - it may not
apply to your app.TomSelect for Django (MIZDB)InstallationUsageWidgetsMIZSelectMIZSelectTabularAdding more columnsMIZSelectMultiple & MIZSelectTabularMultipleFunction & FeaturesSearchingOption creationAJAX requestChangelist linkInline edit linkFilter against values of another fieldAdd & Edit popup responseOverwrite settingsDevelopment & DemoInstallationInstall:pipinstall-Umizdb-tomselectUsageAdd to installed apps:INSTALLED_APPS=[..."mizdb_tomselect"]Configure an endpoint for autocomplete requests:# urls.pyfromdjango.urlsimportpathfrommizdb_tomselect.viewsimportAutocompleteViewurlpatterns=[...path('autocomplete/',AutocompleteView.as_view(),name='my_autocomplete_view')]Use the widgets in a form.fromdjangoimportformsfrommizdb_tomselect.widgetsimportMIZSelect,MIZSelectTabularfrom.modelsimportCity,PersonclassMyForm(forms.Form):city=forms.ModelChoiceField(City.objects.all(),widget=MIZSelect(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=MIZSelectTabular(Person,url='my_autocomplete_view',search_lookup="full_name__icontains",# for extra columns pass a mapping of model field: column header labelextra_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>MIZDB TomSelect 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 TomSelect element is then initialized
from the attributes in the dataset property.MIZSelectBase autocomplete widget. The arguments of MIZSelect are:ArgumentDefault valueDescriptionmodelrequiredthe model class that provides the choicesurl"autocomplete"view 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_lookupf"{label_field}__icontains"the lookup to use when filtering the resultscreate_fieldmodel field to create new objects with (see below)changelist_urlview name of the changelist view for this model (see below)add_urlview name of the add view for this model(see below)edit_urlview name of the edit view for this model(see below)filter_bya 2-tuple defining an additional filter (see below)can_removeTruewhether to display a remove button next to each itemMIZSelectTabularThis widget 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).MIZSelectTabular has 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 columnAdding more columnsTo add more columns, pass aresult attribute name: column labelmapping to the widget
argumentextra_columns. For example:# models.pyclassPerson(models.Model):name=models.CharField(max_length=100,blank=True)dob=models.DateField(blank=True,null=True)city=models.ForeignKey("City",on_delete=models.SET_NULL,blank=True,null=True)# forms.pyclassTabularForm(forms.Form):person=forms.ModelChoiceField(Person.objects.all(),widget=MIZSelectTabular(Person,extra_columns={"dob":"Date of Birth","city__name":"City"},label_field_label="Name",),required=False,)The column label is the table header label for a given column (here:Date of BirthandCity).The attribute name tells TomSelect what value to look up on a result for the column (here: model fielddoband lookup
expressioncity__nameon the relation fieldcity).Important: that means that the result visible to TomSelect must have an attribute
or property with that name or the column will remain empty.
The results for TomSelect 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.MIZSelectMultiple & MIZSelectTabularMultipleVariants of the above widgets that allow selecting multiple options.Function & FeaturesSearchingThe AutocompleteView filters the result queryset against thesearch_lookuppassed to the widget. The default value for the lookup isname__icontains.
Overwrite theAutocompleteView.searchmethod to modify the search process.classMyAutocompleteView(AutocompleteView):defsearch(self,request,queryset,q):# Filter using your own queryset method:returnqueryset.search(q)Option creationTo enable option creation in the dropdown, pass the view name of the
add view for the given model to the widget. This will add an 'Add' button to the
bottom of the dropdown.# urls.pyurlpatterns=[...path('autocomplete/',AutocompleteView.as_view(),name='my_autocomplete_view'),path('city/add/',CityCreateView.as_view(),name='city_add'),]# forms.pywidget=MIZSelect(City,url='my_autocomplete_view',add_url='city_add')Clicking on that button sends the user to the add page of the model.NOTE: Also seeAdd & Edit popup responseAJAX 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.Changelist linkThe dropdown will include a link to the changelist of the given model if you
pass in the view name for the changelist view.# urls.pyurlpatterns=[...path('autocomplete/',AutocompleteView.as_view(),name='my_autocomplete_view'),path('city/change/',CityChangelistView.as_view(),name='city_changelist'),]# forms.pywidget=MIZSelect(City,url='my_autocomplete_view',changelist_url='city_changelist')Inline edit linkProvide aedit_urlto attach a link to the edit/change page for each selected item.# urls.pyurlpatterns=[...path('person/edit/<path:object_id>/',PersonChangeView.as_view(),name='person_change'),]# forms.pywidget=MIZSelect(Person,edit_url='person_change')NOTE: Also seeAdd & Edit popup responseFilter against values of another fieldUse thefilter_byargument to restrict the available options to the value of
another field. The parameter must be a 2-tuple:(name_of_the_other_form_field, django_field_lookup)# models.pyclassPerson(models.Model):name=models.CharField(max_length=50)pob=models.ForeignKey("Place of Birth",on_delete=models.SET_NULL,blank=True,null=True)classCity(models.Model):name=models.CharField(max_length=50)# forms.pyclassPersonCityForm(forms.Form):city=forms.ModelChoiceField(queryset=City.objects.filter(is_capitol=True))person=forms.ModelChoiceField(queryset=Person.objects.all(),widget=MIZSelect(Person,filter_by=("city","pob_id")))This will result in the Person result queryset to be filtered againstpob_idwith the current value of thecityformfield.NOTE: When usingfilter_by, the declaring element nowrequiresthat the other field
provides a value. If the other field does not have a value, the search will not
return any results.Add & Edit popup responseAfter adding new objects with the 'add' button or after editing selected objects with the
'edit' button, the options of the 'parent' form will still need to be updated.
This can be done by using thePopupResponseMixinview mixin with your CreateViews and UpdateViews.frommizdb_tomselect.viewsimportPopupResponseMixinclassCityCreateView(PopupResponseMixin,CreateView):...classPersonChangeView(PopupResponseMixin,UpdateView):...You also need to modify the template for the Create-/UpdateView by adding a hidden field to the form:<form>...
{% if is_popup %}<inputtype="hidden"name="{{ is_popup_var }}"value="1">{% endif %}
...</form>With all this in place, tabs opened from (left-)clicking an add or edit button will be
treated as a popup. When submitting a popup form, the view redirects to a popup response
template. That template loads some javascript that updates the form of the opener window
that created the popup. The popup window or tab is then closed.This is, roughly, a slimmed down version of how django admin handles popups for related objects.Overwrite settingsTo change MIZSelect orTomSelect settings, you can add a handler for theinitMIZSelectevent.
The event is dispatched before the TomSelect constructor is called.
The target of the event is the element that is about to be initialized.
You can pass your own settings to the init functioninitMIZSelectthat is attached to the target element.
For example, to overwrite the title of the remove buttons:window.addEventListener('initMIZSelect',(e)=>{constelem=e.targetconstmySettings={plugins:{remove_button:{title:'Remove This'}}}elem.initMIZSelect(mySettings)})The settings will be merged with the default MIZSelect settings, and the TomSelect constructor
will be called with the merged settings.Development & Demopython3-mvenvvenvsourcevenv/bin/activate
makeinitSee the demo for a preview: runmake init-demoand then start the demo serverpython demo/manage.py runserver.Run tests withmake testormake tox. To install required browsers for playwright:playwright install.
See the makefile for other commands.