Integration with forms

The purpose of this documentation is to describe every element in a chronological manner. Because you want to know everything about this app and hack like crazy.

It is complementary with the quick documentation.

Django startup

Registry

The registry module provides tools to maintain a registry of channels and static file dependencies.

The first thing that should happen when django starts is registration of channels. It should happen first, because channels are required for autocomplete 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 channels with autocomplete_light.register(),
  • in urls.py, do autocomplete_light.autodiscover() before admin.autodiscover().
ChannelRegistry
Subclass of Python’s dict type with registration/unregistration methods.
registry
Instance of ChannelRegistry.
register
Proxy registry.register.
autodiscover
Find channels and javascript and css, fill registry and static_list.
class autocomplete_light.registry.ChannelRegistry[source]

Dict with some shortcuts to handle a registry of channels.

channel_for_model(model)[source]

Return the channel class for a given model.

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

Proxy registry.register_model_channel() or registry.register_channel() if there is no apparent model for the channel.

Example usages:

# Will create and register SomeModelChannel, if SomeChannel.model
# is None (which is the case by default):
autocomplete_light.register(SomeModel)

# Same but using SomeChannel as base:
autocomplete_light.register(SomeModel, SomeChannel)

# Register a channel without model, ensure that SomeChannel.model
# is None (which is the default):
autocomplete_light.register(SomeChannel)

# As of 0.5, you may also pass attributes*, ie.:
autocomplete_light.register(SomeModel, search_field='search_names',
    result_template='somemodel_result.html')

You may pass attributes via kwargs, only if the registry creates a type:

  • if no channel class is passed,
  • or if the channel class has no model attribute,
  • and if the channel classs is not generic
register_channel(channel)[source]

Register a channel without model, like a generic channel.

register_model_channel(model, channel=None, **kwargs)[source]

Add a model to the registry, optionnaly with a given channel class.

model
The model class to register.
channel
The channel class to register the model with, default to ChannelBase.
kwargs
Extra attributes to set to the channel class, if created by this method.

Three cases are possible:

  • specify model class and ModelNameChannel will be generated extending ChannelBase, with attribute model=model
  • specify a model and a channel class that does not have a model attribute, and a ModelNameChannel will be generated, with attribute model=model
  • specify a model and a channel class with a model attribute, and the channel is directly registered

To keep things simple, the name of a channel is it’s class name.

unregister(name)[source]

Unregister a channel.

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
  • checks for app/static/app/autocomplete_light.js, adds it to static_list
  • checks for app/static/app/autocomplete_light.css, adds it to static_list

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
    static/
        cities_light/
            autocomplete_light.js
            autocomplete_light.css

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 CityChannel and CountryChannel will be registered. For details on how these channel classes are generated, read the documentation of ChannelRegistry.register.

Also, ‘cities_light/autocomplete_light.js’ and ‘cities_light/autocomplete_light.css’ will be appended to static_list. This list is used by the autocomplete_light_static templatetag, read it’s documentation for details.

Channels basics

Example

django-cities-light ships the working example.

API

The channel.base module provides a channel class which you can extend to make your own channel. It also serves as default channel class.

class autocomplete_light.channel.base.ChannelBase[source]

A basic implementation of a channel, which should fit most use cases.

Attributes:

model
The model class this channel serves. If None, a new class will be created in registry.register, and the model attribute will be set in that subclass. So you probably don’t need to worry about it, just know that it’s there for you to use.
result_template
The template to use in result_as_html method, to render a single autocomplete suggestion. By default, it is autocomplete_light/channelname/result.html or autocomplete_light/result.html.
autocomplete_template
The template to use in render_autocomplete method, to render the autocomplete box. By default, it is autocomplete_light/channelname/autocomplete.html or autocomplete_light/autocomplete.html.
search_field
The name of the field that the default implementation of query_filter uses. Default is ‘name’.
limit_results
The number of results that this channel should return. For example, if query_filter returns 50 results and that limit_results is 20, then the first 20 of 50 results will be rendered. Default is 20.
bootstrap
The name of the bootstrap kind. By default, deck.js will only initialize decks for wrappers that have data-bootstrap=”normal”. If you want to implement your own bootstrapping logic in javascript, then you set bootstrap to anything that is not “normal”. Default is ‘normal’.
placeholder
The initial text in the autocomplete text input.
static_list
A list of static files which are necessary for this channel. It is an empty list by default.

Set result_template and autocomplete_template if necessary.

are_valid(values)[source]

Return True if the values are valid.

By default, expect values to be a list of object ids, return True if all the ids are found in the queryset.

as_dict()[source]

Return a dict of variables for this channel, it is used by javascript.

get_absolute_url()[source]

Return the absolute url for this channel, using autocomplete_light_channel url

get_queryset()[source]

Return a queryset for the channel model.

get_results(values=None)[source]

Return an iterable of result to display in the autocomplete box.

By default, it will:

  • call self.get_queryset(),
  • call values_filter() if values is not None,
  • call query_filter() if self.request is set,
  • call order_results(),
  • return a slice from offset 0 to self.limit_results.
init_for_request(request, *args, **kwargs)[source]

Set self.request, self.args and self.kwargs, useful in query_filter.

order_results(results)[source]

Return the result list after ordering.

By default, it expects results to be a queryset and order it by search_field.

query_filter(results)[source]

Filter results using the request.

By default this will expect results to be a queryset, and will filter it with self.search_field + ‘__icontains’=self.request[‘q’].

render_autocomplete()[source]

Render the autocomplete suggestion box.

By default, render self.autocomplete_template with the channel in the context.

result_as_html(result, extra_context=None)[source]

Return the html representation of a result for display in the deck and autocomplete box.

By default, render result_template with channel and result in the context.

result_as_value(result)[source]

Return the value that should be set to the widget field for a result.

By default, return result.pk.

values_filter(results, values)[source]

Filter results based on a list of values.

By default this will expect values to be an iterable of model ids, and results to be a queryset. Thus, it will return a queryset where pks are in values.

Forms

Example

A simple example from test_project:

from django import forms

import autocomplete_light
from cities_light.models import City
from cities_light.contrib.autocomplete_light_widgets import \
    CityAutocompleteWidget

from models import Address
from generic_form_example import TaggedItemForm


class AddressForm(forms.ModelForm):
    city = forms.ModelChoiceField(City.objects.all(),
        widget=CityAutocompleteWidget('CityChannel', max_items=1))

    class Meta:
        model = Address
        widgets = autocomplete_light.get_widgets_dict(Address,
            autocomplete_exclude='city')

API

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

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

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

autocomplete_exclude
the list of model field names to ignore

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

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, **kwargs)[source]

Wraps around Django’s django_modelform_factory, using get_widgets_dict.

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.

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>
{% load autocomplete_light_tags %}
{% autocomplete_light_static %}
autocomplete_light.templatetags.autocomplete_light_tags.autocomplete_light_static(include_autocomplete=True, include_deck=True, include_style=True)[source]

Render static_files that were autodiscovered, as well as javascript and css dependencies (optionnal).

That said, 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>

For AutocompleteWidget to be enabled in the admin, you should create your own base_site.html template as demonstrated in test_project/templates/admin/base_site.html:

{% extends "admin/base.html" %}
{% load i18n %}
{% load autocomplete_light_tags %}

{% block footer %}
    {{ block.super }}

    <script src="{{ STATIC_URL }}jquery.js" type="text/javascript"></script>
    {% autocomplete_light_static %}
{% endblock %}

Widget in action

Widget definition

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

class autocomplete_light.widgets.AutocompleteWidget(channel_name, *args, **kwargs)[source]

Widget suitable for ModelChoiceField and ModelMultipleChoiceField.

Example usage:

from django import forms

import autocomplete_light

from models import Author

class AuthorsForm(forms.Form):
    lead_author = forms.ModelChoiceField(Author.objects.all(), widget=
        autocomplete_light.AutocompleteWidget(
            'AuthorChannel', max_items=1))

    contributors = forms.ModelMultipleChoiceField(Author.objects.all(),
        widget=autocomplete_light.AutocompleteWidget('AuthorChannel'))

AutocompleteWidget constructor decorates SelectMultiple constructor

Arguments: channel_name – the name of the channel that this widget should use.

Keyword arguments are passed to javascript via data attributes of the autocomplete wrapper element:

max_items
The number of items that this autocomplete allows. If set to 0, then it allows any number of selected items like a multiple select, well suited for ManyToMany relations or any kind of ModelMultipleChoiceField. If set to 3 for example, then it will only allow 3 selected items. It should be set to 1 if the widget is for a ModelChoiceField or ForeignKey, in that case it would be like a normal select. Default is 0.
min_characters
The minimum number of characters before the autocomplete box shows up. If set to 2 for example, then the autocomplete box will show up when the input receives the second character, for example ‘ae’. If set to 0, then the autocomplete box will show up as soon as the input is focused, even if it’s empty, behaving like a normal select. Default is 0.
bootstrap
The name of the bootstrap kind. By default, deck.js will only initialize decks for wrappers that have data-bootstrap=”normal”. If you want to implement your own bootstrapping logic in javascript, then you set bootstrap to anything that is not “normal”. By default, its value is copied from channel.bootstrap.
placeholder
The initial value of the autocomplete input field. It can be something like ‘type your search here’. By default, it is copied from channel.placeholder.
payload
A dict of data that will be exported to JSON, and parsed into the Deck instance in javascript. It allows to pass variables from Python to Javascript.
render(name, value, attrs=None)[source]

Render the autocomplete widget.

It will try two templates, like django admin: - autocomplete_light/channelname/widget.html - autocomplete_light/widget.html

Note that it will not pass ‘value’ to the template, because ‘value’ might be a list of model ids in the case of ModelMultipleChoiceField, or a model id in the case of ModelChoiceField. To keep things simple, it will just pass a list, ‘values’, to the template context.

value_from_datadict(data, files, name)[source]

Route to Select if max_items is 1, else route to SelectMultiple

Widget rendering

This is what the default widget template looks like:

{% load i18n %}
{% load autocomplete_light_tags %}

{% comment %}
The outer element is called the 'widget wrapper'. It contains some data
attributes to communicate between Python and JavaScript. And of course, it
wraps around everything the widget needs.
{% endcomment %}
<span class="autocomplete_light_widget {{ name }}" id="{{ widget.html_id }}_wrapper" data-bootstrap="{{ widget.bootstrap }}">
    
    {# a deck that should contain the list of selected options #}
    <ul id="{{ html_id }}_deck" class="deck" >
        {% for result in results %}
            {{ result|autocomplete_light_result_as_html:channel }}
        {% endfor %}
    </ul>

    {# a text input, that is the 'autocomplete input' #}
    <input type="text" class="autocomplete" name="{{ name }}_autocomplete" id="{{ widget.html_id }}_text" value="" {{ extra_attrs }} />
    
    {# a hidden select, that contains the actual selected values #}
    <select style="display:none" class="valueSelect" name="{{ name }}" id="{{ widget.html_id }}" {% if widget.max_items != 1 %}multiple="multiple"{% endif %}>
        {% for value in values %}
            <option value="{{ value }}" selected="selected">{{ value }}</option>
        {% endfor %}
    </select>

    {# a hidden textarea that contains some json about the widget #}
    <textarea class="json_payload" style="display:none">
        {{ json_payload }}
    </textarea>
    
    {# a hidden div that serves as template for the 'remove from deck' button #}
    <div style="display:none" class="remove">
        {# This will be appended to results on the deck, it's the remove button #}
        X
    </div>

    <ul style="display:none" class="add_template">
        {% 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 %}
        <li class="result">
        </li>
    </ul>
</span>

Javascript initialization

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

$('.autocomplete_light_widget[data-bootstrap=normal]').each(function() {
    $(this).yourlabs_deck();
});

If you want to initialize the deck 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_deck({
        getValue: function(result) {
            // your own logic to get the value from an html result
            return ...;
        }
    });
});

yourapp/static/yourapp/autocomplete_light.js will be automatically collected by by autodiscover, and the script tag generated by {% autocomplete_light_static %}.

In django-cities-light source, you can see a more interresting example where two autocompletes depend on each other.

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

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

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

Javascript cron

deck.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 deck, and adds it to the deck 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.

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 minCharacters 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 channel view. The channel view will delegate the rendering of the autocomplete box to the actual channel. So that you can override anything you want directly in the channel.

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

Simple view that routes the request to the appropriate channel.

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 channel.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 channel class from the registry, given the url keyword argument channel, that should be the channel name.

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

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

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

Just proxy channel.post().

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

ChannelBase.render_autocomplete()[source]

Render the autocomplete suggestion box.

By default, render self.autocomplete_template with the channel in the context.

ChannelBase.result_as_html(result, extra_context=None)[source]

Return the html representation of a result for display in the deck and autocomplete box.

By default, render result_template with channel and result in the context.

Then, autocomplete.js recognizes options with a selector. By default, it is ‘.result’. This means that any element with the ‘.result’ class in the autocomplete box is considered as an option.

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

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

This is the default HTML template for the autocomplete:

{% load autocomplete_light_tags %}

<ul>
{% for result in channel.get_results %}
    {{ result|autocomplete_light_result_as_html:channel }}
{% endfor %}
</ul>

This is the default HTML template for results:

<li class="result" data-value="{{ value|safe }}">
    {{ result }} {{ extra_html|safe }}
</li>