Enable an autocomplete in admin forms in two steps: high level API concepts¶
autocomplete_light.register()
shortcut to generate and register Autocomplete classes¶
Register an Autocomplete for your model in
your_app/autocomplete_light_registry.py
, it can look like this:
import autocomplete_light
from models import Person
# This will generate a PersonAutocomplete class
autocomplete_light.register(Person,
# Just like in ModelAdmin.search_fields
search_fields=['^first_name', 'last_name'],
# This will actually html attribute data-placeholder which will set
# javascript attribute widget.autocomplete.placeholder.
autocomplete_js_attributes={'placeholder': 'Other model name ?',},
)
Because PersonAutocomplete
is registered, AutocompleteView.get()
can proxy
PersonAutocomplete.autocomplete_html()
.
This means that openning /autocomplete/PersonAutocomplete/
will call
AutocompleteView.get()
which will in turn call
PersonAutocomplete.autocomplete_html()
.
Also AutocompleteView.post()
would proxy
PersonAutocomplete.post()
if it was defined. It could be useful to build
your own features like on-the-fly object creation using Javascript method
overrides like the remote autocomplete.
Warning
Note that this would make all Person
public. Fine tuning
security is explained later in this tutorial in section Overriding the queryset of a model autocomplete to secure an Autocomplete.
autocomplete_light.register()
works by passing the extra keyword arguments like search_fields
to the
Python type()
function. This means that extra keyword arguments will
be used as class attributes of the generated class. An equivalent version of
the above code would be:
class PersonAutocomplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['^first_name', 'last_name']
autocomplete_js_attributes={'placeholder': 'Other model name ?',}
model = Person
autocomplete_light.register(PersonAutocomplete)
Note
If you wanted, you could override the default
AutocompleteModelBase
used by
autocomplete_light.register()
to generate Autocomplete
classes.
It could look like this (in urls.py):
autocomplete_light.registry.autocomplete_model_base = YourAutocompleteModelBase
autocomplete_light.autodiscover()
modelform_factory()
shortcut to generate ModelForms in the admin¶
Make the admin Order
form that uses PersonAutocomplete
, in
your_app/admin.py
:
from django.contrib import admin
import autocomplete_light
from models import Order
class OrderAdmin(admin.ModelAdmin):
# This will generate a ModelForm
form = autocomplete_light.modelform_factory(Order)
admin.site.register(Order)
There are other ways to generate forms, depending on your needs. If you just wanted to replace selects in the admin then autocomplete_light’s job is done by now !
Making Autocomplete classes¶
Create a basic list-backed autocomplete class¶
Class attributes are thread safe because
autocomplete_light.register()
always create a class copy. So, registering a custom Autocomplete class for
your model in your_app/autocomplete_light_registry.py
could look like this:
import autocomplete_light
class OsAutocomplete(autocomplete_light.AutocompleteListBase):
choices = ['Linux', 'BSD', 'Minix']
autocomplete_light.register(OsAutocomplete)
Using a template to render the autocomplete¶
You could use AutocompleteListTemplate
instead:
import autocomplete_light
class OsAutocomplete(autocomplete_light.AutocompleteListTemplate):
choices = ['Linux', 'BSD', 'Minix']
autocomplete_template = 'your_autocomplete_box.html'
autocomplete_light.register(OsAutocomplete)
Note
In reality, AutocompleteListBase inherits from both AutocompleteList and AutocompleteBase, and AutocompleteListTemplate inherits from both AutocompleteList and AutocompleteTemplate. It is the same for the other Autocomplete: AutocompleteModel + AutocompleteTemplate = AutocompleteModelTemplate and so on.
Create a basic model autocomplete class¶
Registering a custom Autocomplete class for your model in
your_app/autocomplete_light_registry.py
can look like this:
import autocomplete_light
from models import Person
class PersonAutocomplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['^first_name', 'last_name']
autocomplete_light.register(Person, PersonAutocomplete)
Note
An equivalent of this example would be:
autocomplete_light.register(Person,
search_fields=['^first_name', 'last_name'])
Overriding the queryset of a model autocomplete to secure an Autocomplete¶
You can override any method of the Autocomplete class. Filtering choices based on the request user could look like this:
import autocomplete_light
from models import Person
class PersonAutocomplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['^first_name', 'last_name'])
def choices_for_request(self):
choices = super(PersonAutocomplete, self).choices_for_request()
if not self.request.user.is_staff:
choices = choices.filter(private=False)
return choices
autocomplete_light.register(Person, PersonAutocomplete)
Registering the same Autocomplete class for several autocompletes¶
This code registers an autocomplete with name ‘ContactAutocomplete’:
autocomplete_light.register(ContactAutocomplete)
To register two autocompletes with the same class, pass in a name argument:
autocomplete_light.register(ContactAutocomplete, name='Person',
choices=Person.objects.filter(is_company=False))
autocomplete_light.register(ContactAutocomplete, name='Company',
choices=Person.objects.filter(is_company=True))
Your own form classes¶
Working around Django bug #9321: Hold down “Control” ...¶
If any autocomplete widget renders with a message like ‘Hold down “Control” to
select multiple items at once’, it is because of Django bug #9321. A trivial
fix is to use autocomplete_light.FixedModelForm
.
FixedModelForm
inherits from django.forms.ModelForm
and only takes care
or removing this message. It remains compatible and can be used as a drop-in
replacement for ``ModelForm`.`
Of course, FixedModelForm
is not required, but might prove helpful.
Override a default relation select in ModelForm.Meta.widgets
¶
You can override the default relation select as such:
from django import forms
import autocomplete_light
from models import Order, Person
class OrderForm(forms.ModelForm):
class Meta:
model = Order
widgets = autocomplete_light.get_widgets_dict(Order)
Or in a ModelChoiceField
or similar¶
Now use PersonAutocomplete
in a ChoiceWidget
ie. for a ForeignKey
,
it can look like this:
from django import forms
import autocomplete_light
from models import Order, Person
class OrderForm(forms.ModelForm):
person = forms.ModelChoiceField(Person.objects.all(),
widget=autocomplete_light.ChoiceWidget('PersonAutocomplete'))
class Meta:
model = Order
Using your own form in a ModelAdmin
¶
You can use this form in the admin too, it can look like this:
from django.contrib import admin
from forms import OrderForm
from models import Order
class OrderAdmin(admin.ModelAdmin):
form = OrderForm
admin.site.register(Order, OrderAdmin)
Note
Ok, this has nothing to do with django-autocomplete-light
because it is
plain Django, but still it might be useful to someone.
Using autocomplete widgets in non model-forms¶
There are 3 kinds of widgets:
autocomplete_light.ChoiceWidget
has a hidden<select>
which works fordjango.forms.ChoiceField
,autocomplete_light.MultipleChoiceWidget
has a hidden<select multiple="multiple">
which works fordjango.forms.MultipleChoiceField
,autocomplete_light.TextWidget
just enables an autocomplete on its<input>
and works fordjango.forms.CharField
.
For example:
# Using widgets directly in any kind of form.
class NonModelForm(forms.Form):
user = forms.ModelChoiceField(User.objects.all(),
widget=autocomplete_light.ChoiceWidget('UserAutocomplete'))
cities = forms.ModelMultipleChoiceField(City.objects.all(),
widget=autocomplete_light.MultipleChoiceWidget('CityAutocomplete'))
tags = forms.CharField(
widget=autocomplete_light.TextWidget('TagAutocomplete'))
Overriding a JS option in Python¶
Javascript widget options can be set in Python via the widget_js_attributes
keyword argument. And javascript autocomplete options can be set in Python via
the autocomplete_js_attributes
.
Those can be set either on an Autocomplete class, either using the
register()
shortcut, either via the Widget constructor.
Per Autocomplete class¶
class AutocompleteYourModel(autocomplete_light.AutocompleteModelTemplate):
template_name = 'your_app/your_special_choice_template.html'
autocomplete_js_attributes = {
# This will actually data-autocomplete-minimum-characters which
# will set widget.autocomplete.minimumCharacters.
'minimum_characters': 4,
}
widget_js_attributes = {
# That will set data-max-values which will set widget.maxValues
'max_values': 6,
}
Per registered Autocomplete¶
autocomplete_light.register(City,
# Those have priority over the class attributes
autocomplete_js_attributes={
'minimum_characters': 0,
'placeholder': 'City name ?',
}
widget_js_attributes = {
'max_values': 6,
}
)
Per widget¶
class SomeForm(forms.Form):
cities = forms.ModelMultipleChoiceField(City.objects.all(),
widget=autocomplete_light.MultipleChoiceWidget('CityAutocomplete',
# Those attributes have priority over the Autocomplete ones.
autocomplete_js_attributes={'minimum_characters': 0,
'placeholder': 'Choose 3 cities ...'},
widget_js_attributes={'max_values': 3}))
Javascript API concepts¶
django-autocomplete-light provides consistent JS plugins. A concept that you understand for one plugin is likely to be appliable for others.
Using the selectChoice
event to enable keyboard choice selection¶
autocomplete.js
doesn’t do anything but trigger selectChoice
on the
input when a choice is selected either with mouse or keyboard, let’s enable
some action:
$('#yourInput').bind('selectChoice', function(e, choice, autocomplete) {
window.location.href = choice.attr('href');
});
Note
Well, not only doesn’t autocomplete.js generate the autocomplete box HTML, but it can’t even do anything uppon choice selection ! What a stupid script. On the other hand it does allow to plug in radically different behaviours (ie. ModelChoiceWidget, TextWidget, ...) so maybe it’s a good thing.
Override autocomplete JS options in JS¶
The array passed to the plugin function will actually be used to $.extend the autocomplete instance, so you can override any option, ie:
$('#yourInput').yourlabsAutocomplete({
url: '{% url "your_autocomplete_url" %}',
// Hide after 200ms of mouseout
hideAfter: 200,
// Choices are elements with data-url attribute in the autocomplete
choiceSelector: '[data-url]',
// Show the autocomplete after only 1 character in the input.
minimumCharacters: 1,
// Override the placeholder attribute in the input:
placeholder: '{% trans 'Type your search here ...' %}',
// Append the autocomplete HTML somewhere else:
appendAutocomplete: $('#yourElement'),
// Override zindex:
autocompleteZIndex: 1000,
});
Note
The pattern is the same for all plugins provided by django-autocomplete-light.
Override autocomplete JS methods¶
Overriding methods works the same, ie:
$('#yourInput').yourlabsAutocomplete({
url: '{% url "your_autocomplete_url" %}',
choiceSelector: '[data-url]',
getQuery: function() {
return this.input.val() + '&search_all=' + $('#searchAll').val();
},
hasChanged: function() {
return true; // disable cache
},
});
Note
The pattern is the same for all plugins provided by django-autocomplete-light.
Overload autocomplete JS methods¶
Use call to call a parent method. This example automatically selects the choice if there is only one:
$(document).ready(function() {
var autocomplete = $('#id_city_text').yourlabsAutocomplete();
autocomplete.show = function(html) {
yourlabs.Autocomplete.prototype.show.call(this, html)
var choices = this.box.find(this.choiceSelector);
if (choices.length == 1) {
this.input.trigger('selectChoice', [choices, this]);
}
}
});
Get an existing autocomplete object and chain autocompletes¶
You can use the jQuery plugin yourlabsAutocomplete()
to get an existing
autocomplete object. Which makes chaining autocompletes with other form fields
as easy as:
$('#country').change(function() {
$('#yourInput').yourlabsAutocomplete().data = {
'country': $(this).val();
}
});
Overriding widget JS methods¶
The widget js plugin will only bootstrap widgets which have
data-bootstrap="normal"
. Which means that you should first name your new
bootstrapping method to ensure that the default behaviour doesn’t get in the
way.
autocomplete_light.register(City,
widget_js_attributes={'bootstrap': 'your-custom-bootstrap'})
Note
You could do this at various level, by setting the bootstrap
argument
on a widget instance, via register()
or directly on an autocomplete
class. See Overriding JS options in Python for details.
Now, you can instanciate the widget yourself like this:
$(document).bind('yourlabsWidgetReady', function() {
$('.your.autocomplete-light-widget[data-bootstrap=your-custom-bootstrap]').live('initialize', function() {
$(this).yourlabsWidget({
// Override options passed to $.yourlabsAutocomplete() from here
autocompleteOptions: {
url: '{% url "your_autocomplete_url" %}',
// Override any autocomplete option in this array if you want
choiceSelector: '[data-id]',
},
// Override some widget options, allow 3 choices:
maxValues: 3,
// or method:
getValue: function(choice) {
// This is the method that returns the value to use for the
// hidden select option based on the HTML of the selected
// choice.
//
// This is where you could make a non-async post request to
// this.autocomplete.url for example. The default is:
return choice.data('id')
},
})
});
});
You can use the remote autocomplete as an example.
Note
You could of course call $.yourlabsWidget()
directly, but using the
yourlabsWidgetReady
event takes advantage of the built-in
DOMNodeInserted event: your widgets will also work with dynamically created
widgets (ie. admin inlines).