Integration with forms

The purpose of this documentation is to describe every element in a chronological manner.

It is complementary with the quick documentation.

Django startup

Registry

The registry module provides tools to maintain a registry of autocompletes.

The first thing that should happen when django starts is registration of autocompletes. It should happen first, because autocompletes are required for widgets. And autocomplete widgets are required for forms. And forms are required for ModelAdmin.

It looks like this:

  • in yourapp/autocomplete_light_registry.py, register your autocompletes with autocomplete_light.register(),
  • in urls.py, do autocomplete_light.autodiscover() before admin.autodiscover().
AutocompleteRegistry
Subclass of Python’s dict type with registration/unregistration methods.
registry
Instance of AutocompleteRegistry.
register
Proxy registry.register.
autodiscover
Find autocompletes and fill registry.
class autocomplete_light.registry.AutocompleteRegistry[source]

Dict with some shortcuts to handle a registry of autocompletes.

autocomplete_for_model(model)[source]

Return the autocomplete class for a given model.

register(*args, **kwargs)[source]

Register an autocomplete.

Two unordered arguments are accepted, at least one should be passed:

  • a model if not a generic autocomplete,
  • an autocomplete class if necessary, else one will be generated.

‘name’ is also an acceptable keyword argument, that can be used to override the default autocomplete name (which is its class name).

In addition, keyword arguments will be set as class attributes. For thread safety reasons, a copy of the autocomplete class is stored in the registry.

unregister(name)[source]

Unregister a autocomplete.

autocomplete_light.registry.register(*args, **kwargs)[source]

Proxy registry.register

autocomplete_light.registry.autodiscover()[source]

Check all apps in INSTALLED_APPS for stuff related to autocomplete_light.

For each app, autodiscover imports app.autocomplete_light_registry if available, resulting in execution of register() statements in that module, filling registry.

Consider a standard app called ‘cities_light’ with such a structure:

cities_light/
    __init__.py
    models.py
    urls.py
    views.py
    autocomplete_light_registry.py

With such a autocomplete_light_registry.py:

from models import City, Country
import autocomplete_light
autocomplete_light.register(City)
autocomplete_light.register(Country)

When autodiscover() imports cities_light.autocomplete_light_registry, both CityAutocomplete and CountryAutocomplete will be registered. For details on how these autocomplete classes are generated, read the documentation of AutocompleteRegistry.register.

Autocomplete basics

Examples

Simple model autocomplete:

import autocomplete_light

from cities_light.models import City

autocomplete_light.register(City, search_fields=('search_names',),
    autocomplete_js_attributes={'placeholder': 'city name ..'})

Slightly advanced autocomplete:

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)

API

class autocomplete_light.autocomplete.base.AutocompleteInterface(request=None, values=None)[source]

This is the minimum to implement in a custom Autocomplete class. It has two attributes:

values
A list of values which validate_values() and choices_for_values() should use.
request
A request object which choices_for_request() and autocomplete_html() should use.

An autocomplete proposes “choices”. A choice has a “value”. When the user selects a “choice”, then it is converted to a “value”.

Class constructor sets the given request and values as instance attributes, casting values to list if necessary.

autocomplete_html()[source]

Return the HTML autocomplete that should be displayed under the text input. Use self.request if set.

choices_for_values()[source]

Return the list of choices corresponding to self.values.

get_absolute_url()[source]

Return the absolute url for this autocomplete, using autocomplete_light_autocomplete url

validate_values()[source]

Return True if self.values are all valid.

class autocomplete_light.autocomplete.base.AutocompleteBase(request=None, values=None)[source]

A basic implementation of AutocompleteInterface that renders HTML and should fit most cases. However, it requires to overload choices_for_request().

Class constructor sets the given request and values as instance attributes, casting values to list if necessary.

autocomplete_html()[source]

Simple rendering of the autocomplete.

choice_html(choice)[source]

Return a choice formated according to self.choice_html_format.

choice_label(choice)[source]

Convert a choice to a label.

choice_value(choice)[source]

Convert a choice to a value.

choices_for_request()[source]

Return the list of choices that are available. Uses self.request if set. Use self.request if set, may be used by autocomplete_html().

validate_values()[source]

Return True if all the values are available in choices_for_values().

There are many autocompletes you can use, just to name a few:

  • AutocompleteRestModelBase,
  • AutocompleteGenericTemplate,
  • AutocompleteModelTemplate,
  • AutocompleteChoiceListBase ...

Each of them should have tests in autocomplete_light/tests and at least one example app in test_project/.

Forms

Note

Due to Django’s issue #9321, you may have to use autocomplete_light.FixedModelForm instead of django.forms.ModelForm. Otherwise, you might see help text like ‘Hold down “Control” key ...’ for MultipleChoiceWidgets.

API

A more high level API is also available:

A couple of helper functions to help enabling Widget in ModelForms.

autocomplete_light.forms.get_widgets_dict(model, autocomplete_exclude=None, registry=None)[source]

Return a dict of field_name: widget_instance for model that is compatible with Django.

autocomplete_exclude
List of model field names to ignore
registry
Registry to use.

Inspect the model’s field and many to many fields, calls registry.autocomplete_for_model to get the autocomplete for the related model. If a autocomplete is returned, then an Widget will be spawned using this autocomplete.

The dict is usable by ModelForm.Meta.widgets. In django 1.4, with modelform_factory too.

autocomplete_light.forms.modelform_factory(model, autocomplete_exclude=None, registry=None, **kwargs)[source]

Wraps around Django’s django_modelform_factory, using get_widgets_dict.

autocomplete_exclude
List of model field names to ignore
registry
Registry to use.

Basically, it will use the dict returned by get_widgets_dict in order and pass it to django’s modelform_factory, and return the resulting modelform.

class autocomplete_light.forms.FixedModelForm(*args, **kwargs)[source]

Simple child of FixedModelForm that removes the ‘Hold down “Control” ...’ message that is enforced in select multiple fields. Added in 1.0.23.

See https://code.djangoproject.com/ticket/9321

Page rendering

It is important to load jQuery first, and then autocomplete_light and application specific javascript, it can look like this:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
{% include 'autocomplete_light/static.html' %}

However, autocomplete_light/static.html also includes “remote.js” which is required only by remote channels. If you don’t need it, you could either load the static dependencies directly in your template, or override autocomplete_light/static.html:

{% load static %}

{% include 'autocomplete_light/_ajax_csrf.html' %}

<script type="text/javascript" src="{% static 'autocomplete_light/autocomplete.js' %}"></script>
<script type="text/javascript" src="{% static 'autocomplete_light/widget.js' %}"></script>
<script type="text/javascript" src="{% static 'autocomplete_light/addanother.js' %}"></script>
<script type="text/javascript" src="{% static 'autocomplete_light/text_widget.js' %}"></script>
<script type="text/javascript" src="{% static 'autocomplete_light/remote.js' %}"></script>
<link rel="stylesheet" type="text/css" href="{% static 'autocomplete_light/style.css' %}" />

Or, if you only want to make a global navigation autocomplete, you only need:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script src="{{ STATIC_URL }}autocomplete_light/autocomplete.js" type="text/javascript"></script>

To enable autocomplete form widgets, you need to load:

  • jQuery
  • autocomplete_light/autocomplete.js
  • autocomplete_light/widget.js

Optionally:

  • autocomplete_light/style.css
  • autocomplete_light/remote.js

A quick way to enable all this in the admin, is to replace template admin/base_site.html, ie.:

{% extends "admin/base.html" %}

{% block extrahead %}
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
    {% include 'autocomplete_light/static.html' %}
{% endblock %}

Widget in action

Widget definition

The first thing that happens is the definition of an AutocompleteWidget in a form.

ChoiceWidget is intended to work as a replacement for django’s Select widget, and MultipleChoiceWidget for django’s SelectMultiple.

Constructing a widget needs an Autocomplete class or registered autocomplete name.

The choice autocomplete widget renders from autocomplete_light/widget.html template.

class autocomplete_light.widgets.WidgetBase(autocomplete, widget_js_attributes=None, autocomplete_js_attributes=None, extra_context=None)[source]

Base widget for autocompletes.

Mainly handles passing arguments from Python to HTML data-* attributes, via widget_js_attributes and autocomplete_js_attributes. Javascript will parse these data-* attributes.

This widget also renders the widget template.

class autocomplete_light.widgets.ChoiceWidget(autocomplete, widget_js_attributes=None, autocomplete_js_attributes=None, extra_context=None, *args, **kwargs)[source]

Widget that provides an autocomplete for zero to one choice.

class autocomplete_light.widgets.MultipleChoiceWidget(autocomplete=None, widget_js_attributes=None, autocomplete_js_attributes=None, extra_context=None, *args, **kwargs)[source]

Widget that provides an autocomplete for zero to n choices.

class autocomplete_light.widgets.TextWidget(autocomplete, widget_js_attributes=None, autocomplete_js_attributes=None, *args, **kwargs)[source]

Widget that just adds an autocomplete to fill a text input

Example, overriding some widget.js and autocomplete.js options directly from Python:

from django import forms
from django.contrib.auth.models import User
from cities_light.models import City
import autocomplete_light

from models import Profile


class ProfileForm(forms.ModelForm):
    user = forms.ModelChoiceField(User.objects.all(),
        widget=autocomplete_light.ChoiceWidget('UserAutocomplete'))

    cities = forms.ModelMultipleChoiceField(City.objects.all(),
        widget=autocomplete_light.MultipleChoiceWidget('CityAutocomplete',
            # optionnal: override an autocomplete.js option
            autocomplete_js_attributes={'minimum_characters': 0,
                                        'placeholder': 'Choose 3 cities ...'},
            # optionnal: override a widget.js option
            widget_js_attributes={'max_values': 3}))

    # Note that defining *_js_attributes on Autocomplete classes or instances
    # also work.

    class Meta:
        model = Profile

Note that those have priority over those that are defined at the autocomplete level:

from django.contrib.auth.models import User
import autocomplete_light


class UserAutocomplete(autocomplete_light.AutocompleteModelBase):
    search_fields = ('username', 'email', 'first_name', 'last_name')

    # Note that defining *_js_attributes in a Widget also works. Widget has
    # priority since it's the most specific.
    autocomplete_js_attributes = {
        'placeholder': 'type a user name ...',
    }


autocomplete_light.register(User, UserAutocomplete)

Widget rendering

This is what the default widget template looks like:

{% load i18n %}
{% load staticfiles %}
{% load autocomplete_light_tags %}
{% load url from future %}

{% block widget_open %}
<span class="autocomplete-light-widget {{ name }}
    {% if widget.widget_js_attributes.max_values == 1 %}single{% else %}multiple{% endif %}"
    id="{{ widget.html_id }}-wrapper"
    {{ widget.widget_js_attributes|autocomplete_light_data_attributes }}
    {{ widget.autocomplete_js_attributes|autocomplete_light_data_attributes:'autocomplete-' }}
    >
{% endblock %}

{% block deck %}
    {# a deck that should contain the list of selected options #}
    {{ choices }}
    <span id="{{ widget.html_id }}-deck" class="deck div" >
        {% for choice in autocomplete.choices_for_values %}
            {{ choice|autocomplete_light_choice_html:autocomplete }}
        {% endfor %}
    </span>
{% endblock %}

{% block input %}
    {# a text input, that is the 'autocomplete input' #}
    <input type="text" class="autocomplete" name="{{ name }}-autocomplete" id="{{ widget.html_id }}_text" value="" {{ extra_attrs }} />
{% endblock %}

{% block add_another %}
    {# A link to add a new choice using a popup #}
    {% if autocomplete.add_another_url_name %}
    <a href="{% block add_another_href %}{% url autocomplete.add_another_url_name %}?_popup=1{% endblock %}" class="autocomplete-add-another" id="add_{{ widget.html_id }}" style="display:none;">
        <img src="{% static 'admin/img/icon_addlink.gif' %}" width="10" height="10" alt="{% trans 'Add another' %}" />
    </a>
    {% endif %}
{% endblock %}

{% block select %}
    {# a hidden select, that contains the actual selected values #}
    <select style="display:none" class="value-select" name="{{ name }}" id="{{ widget.html_id }}" multiple="multiple">
        {% for value in values %}
            <option value="{{ value }}" selected="selected">{{ value }}</option>
        {% endfor %}
    </select>
{% endblock %}

{% block remove_template %}
    {# a hidden div that serves as template for the 'remove from deck' button #}
    <span style="display:none" class="remove div">
        {# This will be appended to choices on the deck, it's the remove button #}
        X
    </span>
{% endblock %}

{% block choice_template %}
    <span style="display:none" class="choice-template div">
        {% comment %}
        the contained element will be used to render options that are added to the select
        via javascript, for example in django admin with the + sign

        The text of the option will be inserted in the html of this tag
        {% endcomment %}
        <span class="choice div prepend-remove append-option-html">
        </span>
    </span>
{% endblock %}

{% block widget_close %}
</span>
{% endblock %}

Javascript initialization

widget.js initializes all widgets that have bootstrap=’normal’ (the default), as you can see:

$('.autocomplete-light-widget[data-bootstrap=normal]').each(function() {
    $(this).autocompleteWidget();
});

If you want to initialize the widget yourself, set the widget or channel bootstrap to something else, say ‘yourinit’. Then, add to yourapp/static/yourapp/autocomplete_light.js something like:

$('.autocomplete-light-widget[data-bootstrap=yourinit]').each(function() {
    $(this).yourlabs_widget({
        getValue: function(choice) {
            // your own logic to get the value from an html choice
            return ...;
        }
    });
});

Also, load yourapp/autocomplete_light.js in your override of autocomplete_light/static.html.

You should take a look at the docs of autocomplete.js and widget.js, as it lets you override everything.

One interresting note is that the plugins (yourlabsAutocomplete and autocompleteWidget) hold a registry. Which means that:

  • calling someElement.autocompleteWidget() will instanciate a widget with the passed overrides
  • calling someElement.autocompleteWidget() again will return the widget instance for someElement

This is exactly what you need to use to make autocompletes that depend on each other.

Javascript cron

widget.js includes a javascript function that is executed every two seconds. It checks each widget’s hidden select for a value that is not in the widget, and adds it to the widget if any.

This is useful for example, when an item was added to the hidden select via the ‘+’ button in django admin. But if you create items yourself in javascript and add them to the select it would work too.

The reason for that is that adding and selecting an option from a select doesn’t trigger the javascript change event, which is a hudge pity.

Javascript events

When the autocomplete input is focused, autocomplete.js checks if there are enought caracters in the input to display an autocomplete box. If minimumCharacters is 0, then it would open even if the input is empty, like a normal select box.

If the autocomplete box is empty, it will fetch the autocomplete view. That view delegates the rendering of the autocomplete box to the registered autocomplete.

class autocomplete_light.views.AutocompleteView(**kwargs)[source]

Simple view that routes the request to the appropriate autocomplete.

Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things.

get(request, *args, **kwargs)[source]

Return an HttpResponse with the return value of autocomplete.render_autocomplete().

This view is called by the autocomplete script, it is expected to return the rendered autocomplete box contents.

To do so, it gets the autocomplete class from the registry, given the url keyword argument autocomplete, that should be the autocomplete name.

Then, it instanciates the autocomplete with no argument as usual, and calls autocomplete.init_for_request, passing all arguments it recieved.

Finnaly, it makes an HttpResponse with the result of autocomplete.render_autocomplete(). The javascript will use that to fill the autocomplete suggestion box.

post(request, *args, **kwargs)[source]

Just proxy autocomplete.post().

This is the key to communication between the autocomplete and the widget in javascript. You can use it to create results and such.

AutocompleteBase.autocomplete_html()[source]

Simple rendering of the autocomplete.

AutocompleteBase.choice_html(choice)[source]

Return a choice formated according to self.choice_html_format.

Then, autocomplete.js recognizes options with a selector. By default, it is ‘[data-value]’. This means that any element with a data-value attribute in the autocomplete html is considered a selectable choice.

When an option is selected, widget.js calls it’s method getValue() and adds this value to the hidden select. Also, it will copy the choice html to the widget.

When an option is removed from the widget, widget.js also removes it from the hidden select.