Tutorial¶
Note
For demo links to work, you need to run the test project on localhost.
Overview¶
dal_alight is a DAL autocomplete frontend built on native
Web Components.
No jQuery or third-party JS library is required.
Key differences from the Select2 frontend:
No jQuery / Select2 required. The component is a pure web component (
<autocomplete-select>) with no external library dependency.The view returns HTML fragments instead of JSON. Each result is a
<div data-value="…">…</div>element; the JS component reads those divs to build the dropdown.Static files served are
dal_alight/autocomplete-light.css,dal_alight/autocomplete-light.js, anddal_alight/dal-django.js.
Install¶
Add dal_alight to INSTALLED_APPS before django.contrib.admin:
INSTALLED_APPS = [
'dal',
'dal_alight',
# 'grappelli',
'django.contrib.admin',
...
]
For Generic Foreign Key support also add dal_queryset_sequence:
INSTALLED_APPS = [
'dal',
'dal_alight',
'dal_queryset_sequence',
'dal_alight_queryset_sequence', # bridges the two
...
]
Create an autocomplete view¶
Example source: test_project/alight_foreign_key
Use AlightQuerySetView:
from dal import autocomplete
from your_countries_app.models import Country
class CountryAutocomplete(autocomplete.AlightQuerySetView):
def get_queryset(self):
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
Register the view¶
from django.urls import path
from your_countries_app.views import CountryAutocomplete
urlpatterns = [
path(
'country-autocomplete/',
CountryAutocomplete.as_view(),
name='country-autocomplete',
),
]
The simplest registration passes the model directly without a custom view class:
from dal import autocomplete
from your_countries_app.models import Country
urlpatterns = [
path(
'country-autocomplete/',
autocomplete.AlightQuerySetView.as_view(model=Country),
name='country-autocomplete',
),
]
Danger
As with all DAL views, the URL is public by default. Always check
permissions in get_queryset().
Use the view in a Form widget¶
ForeignKey (single select)¶
Use ModelAlight for a ForeignKey field:
from dal import autocomplete
from django import forms
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ('__all__',)
widgets = {
'birth_country': autocomplete.ModelAlight(url='country-autocomplete')
}
ManyToManyField (multi select)¶
Use ModelAlightMultiple for a
ManyToManyField:
widgets = {
'visited_countries': autocomplete.ModelAlightMultiple(url='country-autocomplete')
}
Initial values on edit forms¶
ModelAlight and
ModelAlightMultiple inject the currently
selected object(s) into the <select> options at render time so they
appear pre-selected without an extra AJAX call.
Automation with djhacker¶
Example source: test_project/alight_djhacker_formfield
Live demo: /admin/alight_djhacker_formfield/tmodel/add/
import djhacker # pip install djhacker
from django import forms
djhacker.formfield(
Person.birth_country,
forms.ModelChoiceField,
widget=autocomplete.ModelAlight(url='country-autocomplete')
)
Using autocompletes in the admin¶
Register a ModelAdmin with your custom form:
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)
Inlines work the same way:
class PersonInline(admin.TabularInline):
model = Person
form = PersonForm
Using autocompletes outside the admin¶
Example source: test_project/alight_outside_admin
Live demo: /alight_outside_admin/
Include {{ form.media }} — the widget’s media property loads
autocomplete-light.js and dal-django.js automatically:
{% extends 'base.html' %}
{# Don't forget that one ! #}
{% load static %}
{% block content %}
<div>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<div style="display: none" class="formset-empty">
{{ view.formset.empty_form }}
</div>
<div class="formset-rows">
{{ view.formset }}
</div>
<span id="add-form" class="button">Add form</span>
<input type="submit" />
</form>
</div>
{% endblock %}
{% block footer %}
<script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
{{ form.media }}
<script>
(function($) {
$('#add-form').click(function() {
var index = $('#id_inline_test_models-TOTAL_FORMS').val()
var newForm = $('#id_inline_test_models-__prefix__-DELETE').closest('.formset-empty').clone()
newForm.find(':input').each(function() {
for (attr of ['name', 'id'])
$(this).attr(
attr,
$(this).attr(attr).replace('__prefix__', index)
)
})
newForm.removeAttr('style').removeClass('formset-empty')
newForm.insertBefore($(this))
$('#id_inline_test_models-TOTAL_FORMS').val(
parseInt($('#id_inline_test_models-TOTAL_FORMS').val()) + 1
)
newForm.slideDown()
})
})($)
</script>
{% endblock %}
Creation of new choices¶
Example source: test_project/alight_one_to_one
Live demo: /admin/alight_one_to_one/tmodel/add/
Set create_field on the view to enable on-the-fly object creation:
urlpatterns = [
path(
'country-autocomplete/',
CountryAutocomplete.as_view(create_field='name'),
name='country-autocomplete',
),
]
When no exact match exists the view appends a
<div data-create data-value="…">Create "…"</div> element to its response.
Selecting it triggers a POST to the same URL; the view creates the object and
returns the rendered HTML label directly (a <div data-value="…">…</div>
fragment), which the component inserts into the selection deck.
Add validate_create=True to run full_clean() before saving:
CountryAutocomplete.as_view(create_field='name', validate_create=True)
Filtering results based on other form fields (forwarding)¶
Example source: test_project/alight_linked_data
Live demo: Admin / Alight Linked Data / Add
The forward widget argument works the same way as in any DAL frontend:
class PersonForm(forms.ModelForm):
continent = forms.ChoiceField(choices=CONTINENT_CHOICES)
class Meta:
model = Person
fields = ('__all__',)
widgets = {
'birth_country': autocomplete.ModelAlight(
url='country-autocomplete',
forward=['continent'],
)
}
In the view, read the forwarded value from self.forwarded:
class CountryAutocomplete(autocomplete.AlightQuerySetView):
def get_queryset(self):
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
All forwarding features (forward.Field,
forward.Const, forward.Self, forward.JavaScript, renaming) work
the same — they live in dal.forward and are frontend-agnostic.
Autocompleting from a list of strings¶
Example source: test_project/alight_list
Use AlightListView when results come from a
plain Python list rather than a QuerySet:
class FruitAutocomplete(autocomplete.AlightListView):
def get_list(self):
return ['apple', 'mango', 'apricot', 'orange']
Register it as a named URL, then use ListAlight
in your form:
widget = autocomplete.ListAlight(url='fruit-autocomplete')
To allow creating values that are not in the list, define a create method
on the view:
class FruitAutocomplete(autocomplete.AlightListView):
def get_list(self):
return ['apple', 'mango', 'apricot', 'orange']
def create(self, text):
return text # return the stored value
And use AlightListCreateChoiceField in the form
so the submitted value passes validation:
from dal import autocomplete
class FruitForm(forms.ModelForm):
fruit = autocomplete.AlightListCreateChoiceField(
choice_list=['apple', 'mango', 'apricot', 'orange'],
widget=autocomplete.ListAlight(url='fruit-autocomplete'),
)
For a static list that only accepts existing values, use
AlightListChoiceField instead.
Grouped results¶
QuerySet-backed groups¶
Use AlightGroupQuerySetView to render results
grouped by a related field. Set group_by_related to the name of the
ForeignKey whose target model provides the group label, and optionally
related_field_name (default 'name') for the label attribute:
class CountryAutocomplete(autocomplete.AlightGroupQuerySetView):
group_by_related = 'continent'
related_field_name = 'name'
def get_queryset(self):
return Country.objects.all()
List-based groups¶
Use AlightGroupListView for grouped string lists.
Return (group_name, item) pairs from get_list():
class FruitAutocomplete(autocomplete.AlightGroupListView):
def get_list(self):
return [
('Tropical', 'mango'),
('Tropical', 'papaya'),
('Temperate', 'apple'),
('Temperate', 'pear'),
]
Items with group=None are rendered without a group header.
Generic Foreign Key support¶
Example source: test_project/alight_generic_foreign_key
See GenericForeignKey for the model setup.
Automatic view using AlightGenericForeignKeyModelField:
from dal import autocomplete
from django.contrib.auth.models import Group
class TestForm(autocomplete.FutureModelForm):
location = autocomplete.AlightGenericForeignKeyModelField(
model_choice=[
(Country, 'name'),
(City, 'name', [('language', 'spoken_language')]),
],
)
class Meta:
model = TestModel
Register the auto-generated URL in urls.py:
from .forms import TestForm
urlpatterns += TestForm.as_urls()
Manual view using GenericForeignKeyModelField with explicit widget and view:
from dal import autocomplete
from dal_alight_queryset_sequence.views import AlightQuerySetSequenceView
from dal_alight_queryset_sequence.widgets import QuerySetSequenceAlight
class TestForm(autocomplete.FutureModelForm):
location = autocomplete.GenericForeignKeyModelField(
model_choice=[(Country,), (City,)],
widget=QuerySetSequenceAlight,
view=AlightQuerySetSequenceView,
)
class Meta:
model = TestModel
Class reference¶
Class |
Description |
|---|---|
QuerySet-backed autocomplete, returns HTML fragments |
|
QuerySet-backed, results grouped by a related field |
|
Autocomplete from a Python list |
|
Grouped autocomplete from a Python list |
|
Multi-model Generic FK view ( |
Class |
Description |
|---|---|
Single select, QuerySet-backed (ForeignKey) |
|
Multi select, QuerySet-backed (ManyToManyField) |
|
Single select, arbitrary choices |
|
Multi select, arbitrary choices |
|
Single select, list-backed |
|
Free-text tag widget (comma-separated) |
|
django-taggit integration |
|
Single select, multi-model GFK ( |
|
Multi select, multi-model GFK ( |
Class |
Description |
|---|---|
ChoiceField validated against a list or callable |
|
Like above, allows on-the-fly created values |
|
Auto-wired GFK field ( |