django-autocomplete-light tutorial¶
Overview¶
Autocompletes are based on 3 moving parts:
- widget compatible with the model field, does the initial rendering,
- javascript widget initialization code, to trigger the autocomplete,
- and a view used by the widget script to get results from.
Create an autocomplete view¶
- Example source code: test_project/select2_foreign_key
- Live demo: /select2_foreign_key/test-autocomplete/?q=test
The only purpose of the autocomplete view is to serve relevant suggestions for the widget to propose to the user. DAL leverages Django’s class based views and Mixins to for code reuse.
Note
Do not miss the Classy Class-Based Views website which helps a lot to work with class-based views in general.
In this tutorial, we’ll learn to make autocompletes backed by a QuerySet. Suppose we have a Country Model which we want to provide a Select2 autocomplete widget for in a form. If a users types an “f” it would propose “Fiji”, “Finland” and “France”, to authenticated users only:
The base view for this is Select2QuerySetView
.
from dal import autocomplete
from your_countries_app.models import Country
class CountryAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated():
return Country.objects.none()
qs = Country.objects.all()
if self.q:
qs = qs.filter(name__istartswith=self.q)
return qs
Note
For more complex filtering, refer to official documentation for
the QuerySet API
.
Register the autocomplete view¶
Create a named url
for the view, ie:
from your_countries_app.views import CountryAutocomplete
urlpatterns = [
url(
r'^country-autocomplete/$',
CountryAutocomplete.as_view(),
name='country-autocomplete',
),
]
Ensure that the url can be reversed, ie:
./manage.py shell
In [1]: from django.core.urlresolvers import reverse
In [2]: reverse('country-autocomplete')
Out[2]: u'/country-autocomplete/'
Danger
As you might have noticed, we have just exposed data through a public URL. Please don’t forget to do proper permission checks in get_queryset.
Use the view in a Form widget¶
You should be able to open the view at this point:
We can now use the autocomplete view our Person form, for its birth_country
field that’s a ForeignKey
. So, we’re going to override the
default ModelForm fields
, to use a
widget to select a Model with Select2, in our case by passing the name of the
url we have just registered to ModelSelect2
.
One way to do it is by overriding the form field, ie:
from dal import autocomplete
from django import forms
class PersonForm(forms.ModelForm):
birth_country = forms.ModelChoiceField(
queryset=Country.objects.all(),
widget=autocomplete.ModelSelect2(url='country-autocomplete')
)
class Meta:
model = Person
fields = ('__all__')
Another way to do this is directly in the Form.Meta.widgets
dict, if
overriding the field is not needed:
from dal import autocomplete
from django import forms
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ('__all__')
widgets = {
'birth_country': autocomplete.ModelSelect2(url='country-autocomplete')
}
If we need the country autocomplete view for a widget used for a ManyToMany relation instead of a ForeignKey, with a model like that:
class Person(models.Model):
visited_countries = models.ManyToManyField('your_countries_app.country')
Then we would use the ModelSelect2Multiple
widget, ie.:
widgets = {
'visited_countries': autocomplete.ModelSelect2Multiple(url='country-autocomplete')
}
Using autocompletes in the admin¶
We can make ModelAdmin to use our
form
, ie:
from django.contrib import admin
from your_person_app.models import Person
from your_person_app.forms import PersonForm
class PersonAdmin(admin.ModelAdmin):
form = PersonForm
admin.site.register(Person, PersonAdmin)
Note that this also works with inlines, ie:
class PersonInline(admin.TabularInline):
model = Person
form = PersonForm
Using autocompletes outside the admin¶
- Example source code: test_project/select2_outside_admin,
- Live demo: /select2_outside_admin/.
Ensure that jquery is loaded before {{ form.media }}
:
{% extends 'base.html' %}
{% block content %}
<div>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" />
</form>
</div>
{% endblock %}
{% block footer %}
<script type="text/javascript" src="/static/collected/admin/js/vendor/jquery/jquery.js"></script>
{{ form.media }}
{% endblock %}
Creation of new choices in the autocomplete form¶
- Example source code: test_project/select2_one_to_one,
- Live demo: /admin/select2_one_to_one/testmodel/add/,
The view may provide an extra option when it can’t find any result matching the
user input. That option would have the label Create "query"
, where
query
is the content of the input and corresponds to what the user typed
in. As such:
This allows the user to create objects on the fly from within the AJAX widget. When the user selects that option, the autocomplete script will make a POST request to the view. It should create the object and return the pk, so the item will then be added just as if it already had a PK:
To enable this, first the view must know how to create an object given only
self.q
, which is the variable containing the user input in the view. Set
the create_field
view option to enable creation of new objects from within
the autocomplete user interface, ie:
urlpatterns = [
url(
r'^country-autocomplete/$',
CountryAutocomplete.as_view(create_field='name'),
name='country-autocomplete',
),
]
This way, the option ‘Create “Tibet”’ will be available if a user inputs
“Tibet” for example. When the user clicks it, it will make the post request to
the view which will do Country.objects.create(name='Tibet')
. It will be
included in the server response so that the script can add it to the widget.
Note that creating objects is only allowed to staff users with add permission by default.
Filtering results based on the value of other fields in the form¶
- Example source code: test_project/select2_linked_data.
- Live demo: Admin / Linked Data / Add.
In the live demo, create a TestModel with owner=None
, and another with
owner=test
(test being the user you log in with). Then, in in a new form,
you’ll see both options if you leave the owner select empty:
But if you select test
as an owner, and open the autocomplete again, you’ll
only see the option with owner=test
:
Let’s say we want to add a “Continent” choice field in the form, and filter the
countries based on the value on this field. We then need the widget to pass the
value of the continent field to the view when it fetches data. We can use the
forward
widget argument to do this:
class PersonForm(forms.ModelForm):
continent = forms.ChoiceField(choices=CONTINENT_CHOICES)
class Meta:
model = Person
fields = ('__all__')
widgets = {
'birth_country': autocomplete.ModelSelect2(url='country-autocomplete'
forward=['continent'])
}
DAL’s Select2 configuration script will get the value fo the form field named
'continent'
and add it to the autocomplete HTTP query. This will pass the
value for the “continent” form field in the AJAX request, and we can then
filter as such in the view:
class CountryAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.is_authenticated():
return Country.objects.none()
qs = Country.objects.all()
continent = self.forwarded.get('continent', None)
if continent:
qs = qs.filter(continent=continent)
if self.q:
qs = qs.filter(name__istartswith=self.q)
return qs