Welcome to django-autocomplete-light’s documentation!¶
django-autocomplete-light’s purpose is to enable autocompletes quickly and properly in a django project: it is the fruit of years of R&D. It was designed for Django so that every part overridable or reusable independently. It is stable, tested, documented and fully supported: it tries to be a good neighbour in Django ecosystem.
Features¶
Features include:
- charfield, foreign key, many to many autocomplete widgets,
- generic foreign key, generic many to many autocomplete widgets,
- remote API backed-autocompletes,
- django template engine support for autocompletes, enabling you to include images etc ...
- 100% overridable HTML, CSS, Python and Javascript: there is no variable hidden far down in the scope anywhere.
- add-another popup supported outside the admin too.
- keyboard is supported with enter, tab and arrows by default.
Each feature has a live example and is fully documented. It is also designed and documented so that you create your own awesome features too.
Resources¶
Resources include:
Demo¶
You can run test projects for a local demo in a temporary virtualenv.
test_project: basic features and examples¶
Virtualenv is a great solution to isolate python environments. If necessary, you can install it from your package manager or the python package manager, ie.:
sudo easy_install virtualenv
Install last release¶
Install packages from PyPi and the test project from Github:
rm -rf django-autocomplete-light autocomplete_light_env/
virtualenv autocomplete_light_env
source autocomplete_light_env/bin/activate
git clone https://jpic@github.com/yourlabs/django-autocomplete-light.git
cd django-autocomplete-light/test_project
pip install -r requirements.txt
./manage.py runserver
Or install the development version¶
Install directly from github:
AUTOCOMPLETE_LIGHT_VERSION="master"
CITIES_LIGHT_VERSION="master"
rm -rf autocomplete_light_env/
virtualenv autocomplete_light_env
source autocomplete_light_env/bin/activate
pip install -e git+git://github.com/yourlabs/django-cities-light.git@$CITIES_LIGHT_VERSION#egg=cities_light
pip install -e git+git://github.com/yourlabs/django-autocomplete-light.git@$AUTOCOMPLETE_LIGHT_VERSION#egg=autocomplete_light
cd autocomplete_light_env/src/autocomplete-light/test_project
pip install -r requirements.txt
./manage.py runserver
Usage¶
- Run the server,
- Connect to /admin/, ie. http://localhost:8000/admin/,
- Login with user “test” and password “test”,
- Try the many example applications,
Database¶
A working SQLite database is shipped, but you can make your own ie.:
cd test_project
rm -rf db.sqlite
./manage.py syncdb --noinput
./manage.py migrate
./manage.py cities_light
Note that test_project/project_specific/models.py filters cities from certain countries.
test_remote_project: advanced features and examples¶
The autocomplete can suggest results from a remote API - objects that do not exist in the local database.
This project demonstrates how test_remote_project can provide autocomplete suggestions using the database from test_project.
Usage¶
In one console:
cd test_project
./manage.py runserver
In another:
cd test_remote_project
./manage.py runserver 127.0.0.1:8001
Now, note that there are no or few countries in test_api_project database.
Then, connect to http://localhost:8001/admin/remote_autocomplete/address/add/ and the city autocomplete should propose cities from both projects.
If you’re not going to use localhost:8000 for test_project, then you should update source urls in test_remote_project/remote_autocomplete/autocomplete_light_registry.py.
Install¶
Click on any instruction step for details.
Install the django-autocomplete-light
package with pip¶
Install the stable release, preferably in a virtualenv:
pip install django-autocomplete-light
Or the development version:
pip install -e git+https://github.com/yourlabs/django-autocomplete-light#egg=autocomplete_light
Append 'autocomplete_light'
to settings.INSTALLED_APPS
¶
Enable templates and static files by adding autocomplete_light
to
settings.INSTALLED_APPS which is
editable in settings.py
, it can look like this:
INSTALLED_APPS = [
# [...] your list of app packages is here, add this:
'autocomplete_light',
]
Call autocomplete_light.autodiscover()
before admin.autodiscover()
¶
In urls.py
, call autocomplete_light.autodiscover()
before
admin.autodiscover()
, it can look like this:
import autocomplete_light
# import every app/autocomplete_light_registry.py
autocomplete_light.autodiscover()
import admin
admin.autodiscover()
Include autocomplete_light.urls
¶
Install the autocomplete view and staff debug view in urls.py
using the include function,
it can look like this:
# Django 1.5:
from django.conf.urls import patterns, url, include
# In Django 1.4:
# from django.conf.urls.default import patterns, url, include
urlpatterns = patterns('',
# [...] your url patterns are here
url(r'^autocomplete/', include('autocomplete_light.urls')),
)
Ensure understanding of django.contrib.staticfiles
¶
Ensure that you understand django-staticfiles, if you don’t try this article or refer to official howto and topic.
Include autocomplete_light/static.html
after loading jquery.js (>=1.7)¶
Load the javascript scripts after loading jquery.js
, it can look like this:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.js" type="text/javascript"></script>
{% include 'autocomplete_light/static.html' %}
Optionaly include it in admin/base_site.html
too¶
For admin support, override
admin/base_site.html
. It could look like this:
{% extends "admin/base.html" %}
{% block extrahead %}
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.js" type="text/javascript"></script>
{% include 'autocomplete_light/static.html' %}
{% endblock %}
Note
There is nothing magic in how the javascript loads. This means that you can use django-compressor or anything.
If you didn’t click any, and this is your first install: bravo !
Upgrade¶
Run pip install -U django-autocomplete-light
. Check the CHANGELOG for BC
(Backward Compatibility) breaks. There should be none for minor version
upgrades ie. from 1.1.3 to 1.1.22.
Tutorial¶
Learn the concepts by doing useful things.
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).
You should now fully understand the concepts and be able to do literally what you want.
Topics¶
Using just the concepts you’ve learned in the tutorial, we’ve built-in several really cool things, backed by live examples.
Templating autocompletes¶
This documentation drives through the example app
test_project/template_autocomplete
.
You can use AutocompleteTemplate
as a mixin
just like us:
class AutocompleteModelTemplate(AutocompleteModel, AutocompleteTemplate):
pass
You could also directly inherit from AutocompleteModelTemplate
.
Anyway, this enable two new attributes: choice_template
and autocomplete_template
Example¶
In this case, all you have to do, is use AutocompleteModelTemplate
instead
of AutocompleteModelBase
. For example, in
test_project/template_autocomplete/autocomplete_light_registry.py
:
import autocomplete_light
from models import TemplatedChoice
autocomplete_light.register(TemplatedChoice,
autocomplete_light.AutocompleteModelTemplate,
choice_template='template_autocomplete/templated_choice.html')
This example template makes choices clickable, it is
test_project/template_autocomplete/templates/template_autocomplete/templated_choice.html
:
<div data-value="{{ choice.pk }}">
<a href="{% url admin:template_autocomplete_templatedchoice_change choice.pk %}?_popup=1" target="_blank">
{{ choice }}
</a>
</div>
Alternative¶
FTR, here’s another way to do it, assuming your models have a get_absolute_update_url method defined:
class AutocompleteEditableModelBase(autocomplete_light.AutocompleteModelBase):
choice_html_format = u'''
<span class="div" data-value="%s">%s</span>
<a href="%s" title="%s"><img src="%s%s" /></a>
'''
def choice_html(self, choice):
"""
Return a choice formated according to self.choice_html_format.
"""
return self.choice_html_format % (
self.choice_value(choice), self.choice_label(choice),
choice.get_absolute_update_url(), _(u'Update'),
settings.STATIC_URL, 'admin/img/icon_changelink.gif')
autocomplete_light.register(AppCategory, AutocompleteEditableModelBase,
add_another_url_name='appstore_appcategory_create')
autocomplete_light.register(AppFeature, AutocompleteEditableModelBase,
add_another_url_name='appstore_appfeature_create')
CharField autocompletes¶
django-tagging and derivates like django-tagging-ng provide a TagField
,
which is a CharField
expecting comma separated tags. Behind the scenes,
this field is parsed and Tag
model instances are created and/or linked.
A stripped variant of widget.js
, text_widget.js
, enables autocompletion
for such a field. To make it even easier, a stripped variant of Widget
,
TextWidget
, automates configuration of text_widget.js
.
Needless to say, TextWidget
and text_widget.js
have a structure that
is consistent with Widget
and widget.js
.
It doesn’t have many features for now, but feel free to participate to the project on GitHub.
As usual, a working example lives in test_project. in app
charfield_autocomplete
.
Warning
Note that this feature was added in version 1.0.16, if you have overloaded
autocomplete_light/static.html
from a previous version then you should
make it load autocomplete_light/text_widget.js
to get this new feature.
Example¶
This demonstrates a working usage of TextWidget:
from django import forms
import autocomplete_light
from models import Taggable
class TaggableForm(forms.ModelForm):
class Meta:
model = Taggable
widgets = {
'tags': autocomplete_light.TextWidget('TagAutocomplete'),
}
FTR, using the form in the admin is still as easy:
from django.contrib import admin
from forms import TaggableForm
from models import Taggable
class TaggableInline(admin.TabularInline):
form = TaggableForm
model = Taggable
class TaggableAdmin(admin.ModelAdmin):
form = TaggableForm
list_display = ['name', 'tags']
inlines = [TaggableInline]
admin.site.register(Taggable, TaggableAdmin)
So is registering an Autocomplete for Tag:
from tagging.models import Tag
import autocomplete_light
autocomplete_light.register(Tag)
Django-tagging¶
This demonstrates the models setup used for the above example, using django-taggit, which provides a normal CharField behaviour:
from django.db import models
from tagging.fields import TagField
import tagging
class Taggable(models.Model):
name = models.CharField(max_length=50)
tags = TagField(null=True, blank=True)
parent = models.ForeignKey('self', null=True, blank=True)
def __unicode__(self):
return self.name
tagging.register(Taggable, tag_descriptor_attr='etags')
Django-taggit¶
For django-taggit, you need autocomplete_light.contrib.taggit_tagfield.
AutocompleteGeneric, for GenericForeignKey or GenericManyToMany¶
Generic relation support comes in two flavors:
- for django’s generic foreign keys,
- and for django-generic-m2m’s generic many to many in autocomplete_light.contrib.generic_m2m,
AutocompleteGeneric¶
Example using AutocompleteGeneric
as shown in
test_project/gfk_autocomplete/autocomplete_light_registry.py
:
import autocomplete_light
from cities_light.models import Country, City
from django.contrib.auth.models import User, Group
class AutocompleteTaggableItems(autocomplete_light.AutocompleteGenericBase):
choices = (
User.objects.all(),
Group.objects.all(),
City.objects.all(),
Country.objects.all(),
)
search_fields = (
('username', 'email'),
('name',),
('search_names',),
('name_ascii',),
)
autocomplete_light.register(AutocompleteTaggableItems)
GenericModelForm and GenericModelChoiceField¶
Example using GenericModelForm
and
GenericModelChoiceField
as shown in
test_project/gfk_autocomplete/forms.py
:
from django import forms
import autocomplete_light
from models import TaggedItem
class TaggedItemForm(autocomplete_light.GenericModelForm):
content_object = autocomplete_light.GenericModelChoiceField(
widget=autocomplete_light.ChoiceWidget(
autocomplete='AutocompleteTaggableItems',
autocomplete_js_attributes={'minimum_characters': 0}))
class Meta:
model = TaggedItem
exclude = ('content_type', 'object_id')
GenericManyToMany¶
Example¶
Consider this example model with a generic many-to-many relation descriptor
related
as in test_project/generic_m2m_autocomplete/models.py
:
from django.db import models
from django.db.models import signals
from django.contrib.contenttypes import generic
from genericm2m.models import RelatedObjectsDescriptor
class ModelGroup(models.Model):
name = models.CharField(max_length=100)
related = RelatedObjectsDescriptor()
def __unicode__(self):
return self.name
Example GenericModelForm
and
GenericModelMultipleChoiceField
usage
as per test_project/generic_m2m_autocomplete/forms.py
:
import autocomplete_light
from autocomplete_light.contrib.generic_m2m import GenericModelForm, \
GenericModelMultipleChoiceField
from models import ModelGroup
class ModelGroupForm(GenericModelForm):
"""
Use AutocompleteTaggableItems defined in
gfk_autocomplete.autocomplete_light_registry.
"""
related = GenericModelMultipleChoiceField(
widget=autocomplete_light.MultipleChoiceWidget(
'AutocompleteTaggableItems'))
class Meta:
model = ModelGroup
The form defined above can directly be using in the admin:
from django.contrib import admin
from models import ModelGroup
from forms import ModelGroupForm
class ModelGroupAdmin(admin.ModelAdmin):
form = ModelGroupForm
admin.site.register(ModelGroup, ModelGroupAdmin)
Dependencies between autocompletes¶
This means that the selected value in an autocomplete widget is used to filter choices from another autocomplete widget.
This page drives through the example in test_project/dependant_autocomplete/.
Specifications¶
Consider such a model:
from django.db import models
class Dummy(models.Model):
parent = models.ForeignKey('self', null=True, blank=True)
country = models.ForeignKey('cities_light.country')
region = models.ForeignKey('cities_light.region')
def __unicode__(self):
return u'%s %s' % (self.country, self.region)
And we want two autocompletes in the form, and make the “region” autocomplete to be filtered using the value of the “country” autocomplete.
Autocompletes¶
Register an Autocomplete for Region that is able to use ‘country_id’ GET parameter to filter choices:
import autocomplete_light
from cities_light.models import Country, Region
autocomplete_light.register(Country, search_fields=('name', 'name_ascii',),
autocomplete_js_attributes={'placeholder': 'country name ..'})
class AutocompleteRegion(autocomplete_light.AutocompleteModelBase):
autocomplete_js_attributes={'placeholder': 'region name ..'}
def choices_for_request(self):
q = self.request.GET.get('q', '')
country_id = self.request.GET.get('country_id', None)
choices = self.choices.all()
if q:
choices = choices.filter(name_ascii__icontains=q)
if country_id:
choices = choices.filter(country_id=country_id)
return self.order_choices(choices)[0:self.limit_choices]
autocomplete_light.register(Region, AutocompleteRegion)
Javascript¶
Actually, a normal modelform is sufficient. But it was decided to use Form.Media to load the extra javascript:
from django import forms
import autocomplete_light
from models import Dummy
class DummyForm(forms.ModelForm):
class Media:
"""
We're currently using Media here, but that forced to move the
javascript from the footer to the extrahead block ...
So that example might change when this situation annoys someone a lot.
"""
js = ('dependant_autocomplete.js',)
class Meta:
model = Dummy
widgets = autocomplete_light.get_widgets_dict(Dummy)
That’s the piece of javascript that ties the two autocompletes:
$(document).ready(function() {
$('body').on('change', '.autocomplete-light-widget select[name$=country]', function() {
var countrySelectElement = $(this);
var regionSelectElement = $('#' + $(this).attr('id').replace('country', 'region'));
var regionWidgetElement = regionSelectElement.parents('.autocomplete-light-widget');
// When the country select changes
value = $(this).val();
if (value) {
// If value is contains something, add it to autocomplete.data
regionWidgetElement.yourlabsWidget().autocomplete.data = {
'country_id': value[0],
};
} else {
// If value is empty, empty autocomplete.data
regionWidgetElement.yourlabsWidget().autocomplete.data = {}
}
// example debug statements, that does not replace using breakbpoints and a proper debugger but can hel
// console.log($(this), 'changed to', value);
// console.log(regionWidgetElement, 'data is', regionWidgetElement.yourlabsWidget().autocomplete.data)
})
});
Conclusion¶
Again, there are many ways to acheive this. It’s just a working example you can test in the demo, you may copy it and adapt it to your needs.
Add another popup outside the admin¶
This documentation drives throught the example app non_admin_add_another
which lives in
test_project
.
Implementing this feature is utterly simple and can be done in two steps:
- make your create view to return some script if called with
_popup=1
, - add
add_another_url_name
attribute to your Autocomplete,
Warning
Note that this feature was added in version 1.0.21, if you have overloaded
autocomplete_light/static.html
from a previous version then you should
make it load autocomplete_light/addanother.js
to get this new feature.
Specifications¶
Consider such a model:
from django.db import models
from django.core import urlresolvers
class Widget(models.Model):
name = models.CharField(max_length=100)
widget = models.ForeignKey('self', null=True, blank=True,
related_name='widget_fk')
widgets = models.ManyToManyField('self', blank=True,
related_name='widget_m2m')
def get_absolute_url(self):
return urlresolvers.reverse(
'non_admin_add_another:widget_update', args=(self.pk,))
def __unicode__(self):
return self.name
And we want to have add/update views outside the admin, with autocompletes for
relations as well as a +
/add-another button just like in the admin.
Technical details come from a blog post written by me a couple years ago, Howto: javascript popup form returning value for select like Django admin for foreign keys.
Create view¶
A create view opened via the add-another button should return such a body:
<script type="text/javascript">
opener.dismissAddAnotherPopup(
window,
"name of created model",
"id of created model"
);
</script>
Note that you could also use autocomplete_light.CreateView
which simply
wraps around django.views.generic.edit.CreateView.form_valid()
to do that,
example usage:
from django.conf.urls import patterns, url
from django.views import generic
import autocomplete_light
from forms import WidgetForm
from models import Widget
urlpatterns = patterns('',
url(r'widget/add/$', autocomplete_light.CreateView.as_view(
model=Widget, form_class=WidgetForm), name='widget_create'),
url(r'widget/(?P<pk>\d+)/update/$', generic.UpdateView.as_view(
model=Widget, form_class=WidgetForm), name='widget_update'),
)
Note
It is not mandatory to use url namespaces.
Autocompletes¶
Simply register an Autocomplete for widget, with an add_another_url_name
argument, for example:
from django.core import urlresolvers
import autocomplete_light
from models import Widget
autocomplete_light.register(Widget, add_another_url_name='non_admin_add_another:widget_create')
Proposing results from a remote API¶
This documentation is optionnal, but it is complementary with all other documentation. It aims advanced users.
Consider a social network about music. In order to propose all songs in the world in its autocomplete, it should either:
- have a database with all songs of the world,
- use a simple REST API to query a database with all songs world
The purpose of this documentation is to describe every elements involved. Note that a living demonstration is available in test_remote_project, where one project serves a full database of cities via an API to another.
Example¶
In test_remote_project/remote_autocomplete, of course you should not hardcode urls like that in actual projects:
from cities_light.contrib.autocompletes import *
import autocomplete_light
autocomplete_light.register(Country, CountryRestAutocomplete,
source_url='http://localhost:8000/cities_light/country/')
autocomplete_light.register(Region, RegionRestAutocomplete,
source_url='http://localhost:8000/cities_light/region/')
autocomplete_light.register(City, CityRestAutocomplete,
source_url='http://localhost:8000/cities_light/city/')
Check out the documentation of RemoteCountryChannel and RemoteCityChannel for more.
API¶
Javascript fun¶
Channels with bootstrap=’remote’ get a deck using RemoteChannelDeck’s getValue() rather than the default getValue() function.
var RemoteAutocompleteWidget = {
/*
The default deck getValue() implementation just returns the PK from the
choice HTML. RemoteAutocompleteWidget.getValue's implementation checks for
a url too. If a url is found, it will post to that url and expect the pk to
be in the response.
This is how autocomplete-light supports proposing values that are not there
in the database until user selection.
*/
getValue: function(choice) {
var value = choice.data('value');
if (typeof(value)=='string' && isNaN(value) && value.match(/^https?:/)) {
$.ajax(this.autocompleteOptions.url, {
async: false,
type: 'post',
data: {
'value': value
},
success: function(text, jqXHR, textStatus) {
value = text;
}
});
choice.data('value', value);
}
return value;
}
}
$(document).bind('yourlabsWidgetReady', function() {
// Instanciate decks with RemoteAutocompleteWidget as override for all widgets with
// autocomplete 'remote'.
$('body').on('initialize', '.autocomplete-light-widget[data-bootstrap=rest_model]', function() {
$(this).yourlabsWidget(RemoteAutocompleteWidget);
});
});
Django 1.3 support workarounds¶
The app is was developed for Django 1.4. However, there are workarounds to get it to work with Django 1.3 too. This document attemps to provide an exhaustive list of notes that should be taken in account when using the app with django-autocomplete-light.
modelform_factory¶
The provided autocomplete_light.modelform_factory relies on Django 1.4’s modelform_factory that accepts a ‘widgets’ dict.
Django 1.3 does not allow such an argument. You may however define your form as such:
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
widgets = autocomplete_light.get_widgets_dict(Author)
When things go wrong¶
There is a convenience view to visualize the registry, login as staff, and open the autocomplete url, for example: /autocomplete_light/.
Ensure that:
- jquery is loaded,
autocomplete_light/static.html
is included once, it should loadautocomplete.js
,widget.js
andstyle.css
,- your form uses autocomplete_light widgets,
- your channels are properly defined see
/autocomplete/
if you includedautocomplete_light.urls
with prefix/autocomplete/
.
If you don’t know how to debug, you should learn to use:
- Firebug javascript debugger
- Open the script tab, select a script, click on the left of the code to place a breakpoint
- Ipdb python debugger
- Install ipdb with pip, and place in your python code: import ipdb; ipdb.set_trace()
If you are able to do that, then you are a professional, enjoy autocomplete_light !!!
If you need help, open an issue on the github issues page.
But make sure you’ve read how to report bugs effectively and how to ask smart questions.
Also, don’t hesitate to do pull requests !
Voodoo black magic¶
This cookbook is a work in progress. Please report any error or things that could be explained better ! And make pull requests heh ...
High level Basics¶
Various cooking recipes your_app/autocomplete_light_registry.py
:
# This actually creates a thread safe subclass of AutocompleteModelBase.
autocomplete_light.register(SomeModel)
# If NewModel.get_absolute_url or get_absolute_update_url is defined, this
# will look more fancy
autocomplete_light.register(NewModel,
autocomplete_light.AutocompleteModelTemplate)
# Extra **kwargs are used as class properties in the subclass.
autocomplete_light.register(SomeModel,
# SomeModel is already registered, re-register with custom name
name='AutocompleteSomeModelNew',
# Filter the queryset
choices=SomeModel.objects.filter(new=True))
# It is possible to override javascript options from Python.
autocomplete_light.register(OtherModel,
autocomplete_js_attributes={
# This will actually data-minimum-characters which
# will set widget.autocomplete.minimumCharacters.
'minimum_characters': 0,
'placeholder': 'Other model name ?',
}
)
# But you can make your subclass yourself and override methods.
class YourModelAutocomplete(autocomplete_light.AutocompleteModelTemplate):
template_name = 'your_app/your_special_choice_template.html'
autocomplete_js_attributes = {
'minimum_characters': 4,
}
widget_js_attributes = {
# That will set data-max-values which will set widget.maxValues
'max_values': 6,
}
def choices_for_request(self):
""" Return choices for a particular request """
return super(YourModelAutocomplete, self).choices_for_request(
).exclude(extra=self.request.GET['extra'])
# Just pass the class to register and it'll subclass it to be thread safe.
autocomplete_light.register(YourModel, YourModelAutocomplete)
# This will subclass the subclass, using extra kwargs as class attributes.
autocomplete_light.register(YourModel, YourModelAutocomplete,
# Again, registering another autocomplete for the same model, requires
# registration under a different name
name='YourModelOtherAutocomplete',
# Extra **kwargs passed to register have priority.
choice_template='your_app/other_template.html')
Various cooking recipes for your_app/forms.py
:
# Use as much registered autocompletes as possible.
SomeModelForm = autocomplete_light.modelform_factory(SomeModel,
exclude=('some_field'))
# Same with a custom modelform, using Meta.get_widgets_dict().
class CustomModelForm(forms.ModelForm):
some_extra_field = forms.CharField()
class Meta:
model = SomeModel
widgets = autocomplete_light.get_widgets_dict(SomeModel)
# 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',
# Those attributes have priority over the Autocomplete ones.
autocomplete_js_attributes={'minimum_characters': 0,
'placeholder': 'Choose 3 cities ...'},
widget_js_attributes={'max_values': 3}))
tags = autocomplete_light.TextWidget('TagAutocomplete')
Low level basics¶
This is something you probably won’t need in the mean time. But it can turn out to be useful so here it is.
Various cooking recipes for autocomplete.js
, useful if you want to use it
manually for example to make a navigation autocomplete like facebook:
// Use default options, element id attribute and url options are required:
var autocomplete = $('#yourInput').yourlabsAutocomplete({
url: '{% url "your_autocomplete_url" %}'
});
// Because the jQuery plugin uses a registry, you can get the autocomplete
// instance again by calling yourlabsAutocomplete() again, and patch it:
$('#country').change(function() {
$('#yourInput').yourlabsAutocomplete().data = {
'country': $(this).val();
}
});
// And that's actually how to do chained autocompletes.
// The array passed to the plugin will actually be used to $.extend the
// autocomplete instance, so you can override any option:
$('#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,
});
// Or any method:
$('#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
},
});
// autocomplete.js doesn't do anything but trigger selectChoice when
// an option is selected, let's enable some action:
$('#yourInput').bind('selectChoice', function(e, choice, autocomplete) {
window.location.href = choice.attr('href');
});
// For a simple navigation autocomplete, it could look like:
$('#yourInput').yourlabsAutocomplete({
url: '{% url "your_autocomplete_url" %}',
choiceSelector: 'a',
}).bind('selectChoice', function(e, choice, autocomplete) {
window.location.href = choice.attr('href');
});
Using widget.js is pretty much the same:
$('#yourWidget').yourlabsWidget({
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) {
return choice.data('id'),
},
});
// Supporting dynamically added widgets (ie. inlines) is
// possible by using "solid initialization":
$(document).bind('yourlabsWidgetReady', function() {
$('.your.autocomplete-light-widget[data-bootstrap=your-custom-bootstrap]').live('initialize', function() {
$(this).yourlabsWidget({
// your options ...
})
});
});
// This method takes advantage of the default DOMNodeInserted observer
// served by widget.js
There are some differences with autocomplete.js:
- widget expect a certain HTML structure by default,
- widget options can be overridden from HTML too,
- widget can be instanciated automatically via the default bootstrap
Hence the widget.js HTML cookbook:
<!--
- class=autocomplete-light-widget: get picked up by widget.js defaults,
- data-bootstrap=normal: Rely on automatic bootstrap because
if don't need to override any method, but you could change
that and make your own bootstrap, enabling you to make
chained autocomplete, create options, whatever ...
- data-max-values: override a widget option
- data-minimum-characters: override an autocomplete option,
-->
<span
class="autocomplete-light-widget"
data-bootstrap="normal"
data-max-values="3"
data-minimum-characters="0"
>
<!-- Expected structure: have an input -->
<input type="text" id="some-unique-id" />
<!--
Default expected structure: have a .deck element to append selected
choices too:
-->
<span class="deck">
<!-- Suppose a choice was already selected: -->
<span class="choice" data-value="1234">Option #1234</span>
</span>
<!--
Default expected structure: have a multiple select.value-select:
-->
<select style="display:none" class="value-select" name="your_input" multiple="multiple">
<!-- If option 1234 was already selected: -->
<option value="1234">Option #1234</option>
</select>
<!--
Default expected structure: a .remove element that will be appended to
choices, and that will de-select them on click:
-->
<span style="display:none" class="remove">Remove this choice</span>
<!--
Finally, supporting new options to be created directly in the select in
javascript (ie. add another) is possible with a .choice-template. Of
course, you can't take this very far, since all you have is the new
option's value and html.
-->
<span style="display:none" class="choice-template">
<span class="choice">
</span>
</span>
</span>
Read everything about the registry and widgets.
Integration with external apps¶
Support for django-generic-m2m¶
Support for django-hvad¶
Support for django-taggit¶
django-taggit does it slightly differently. It is supported by autocomplete_light as of 1.0.25, using the autocomplete_light.contrib.taggit_tagfield module.
FAQ¶
Why not use Widget.Media ?¶
In the early versions (0.1) of django-autocomplete-light, we had widgets defining the Media class like this:
class AutocompleteWidget(forms.SelectMultiple):
class Media:
js = ('autocomplete_light/autocomplete.js',)
This caused a problem if you want to load jquery and autocomplete.js globally anyway and anywhere in the admin to have a global navigation autocomplete: it would double load the scripts.
Also, this didn’t work well with django-compressor and other cool ways of deploying the JS.
So, in the next version, I added a dependency management system. Which sucked and was removed right away to finally keep it simple and stupid as we have it today.
How to ask for help ?¶
The best way to ask for help is:
- fork the repo,
- add a simple way to reproduce your problem in a new app of test_project, try to keep it minimal,
- open an issue on github and mention your fork.
Really, it takes quite some time for me to clean pasted code and put up an example app it would be really cool if you could help me with that !
If you don’t want to do the fork and the reproduce case, then you should better ask on StackOverflow and you might be lucky (just tag your question with django-autocomplete-light to ensure that I find it).
API reference¶
Python API reference¶
Registry¶
import autocomplete_light
¶
See everything available in autocomplete_light/__init__.py
:
"""
Provide tools to enable nice autocompletes in your Django project.
"""
from .registry import AutocompleteRegistry, registry, register, autodiscover
from .autocomplete import *
from .widgets import ChoiceWidget, MultipleChoiceWidget, TextWidget
from .forms import get_widgets_dict, modelform_factory, FixedModelForm
from .generic import GenericModelForm, GenericModelChoiceField
from .views import CreateView
from .exceptions import AutocompleteNotRegistered
ModelForm = FixedModelForm