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.
The registry module provides tools to maintain a registry of channels.
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:
Dict with some shortcuts to handle a registry of channels.
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:
Add a model to the registry, optionnaly with a given channel class.
Three cases are possible:
To keep things simple, the name of a channel is it’s class name, which is usually generated. In case of conflicts, you may override the default channel name with the channel_name keyword argument.
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 CityChannel and CountryChannel will be registered. For details on how these channel classes are generated, read the documentation of ChannelRegistry.register.
django-cities-light ships the working example.
The channel.base module provides a channel class which you can extend to make your own channel. It also serves as default channel class.
A basic implementation of a channel, which should fit most use cases.
Attributes:
Set result_template and autocomplete_template if necessary.
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.
Return the absolute url for this channel, using autocomplete_light_channel url
Return an iterable of result to display in the autocomplete box.
By default, it will:
Set self.request, self.args and self.kwargs, useful in query_filter.
Return the result list after ordering.
By default, it expects results to be a queryset and order it by search_field.
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 the autocomplete suggestion box.
By default, render self.autocomplete_template with the channel in the context.
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.
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')
A couple of helper functions to help enabling AutocompleteWidget in ModelForms.
Return a dict of field_name: widget_instance for model that is compatible with Django.
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.
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.
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:
<script type="text/javascript" src="{{ STATIC_URL }}autocomplete_light/autocomplete.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}autocomplete_light/deck.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}autocomplete_light/remote.js"></script>
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}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>
For AutocompleteWidget to be enabled in the admin, you should create your own admin/base_site.html template as demonstrated in test_project/templates/admin/base_site.html:
{% extends "admin/base.html" %}
{% load i18n %}
{% block footer %}
{{ block.super }}
<script src="{{ STATIC_URL }}jquery.js" type="text/javascript"></script>
{% include 'autocomplete_light/static.html' %}
{% comment %}
Load additionnal script or style dependencies here. For instance, the
double country/city autocomplete widget requires the countrycity deck
bootstrap so we'll load it. But you don't need this one if you don't use
the countrycity widget of the cities_light app.
{% endcomment %}
<script src="{{ STATIC_URL }}cities_light/autocomplete_light.js" type="text/javascript"></script>
{% endblock %}
The first thing that happens is the definition of an AutocompleteWidget in a form.
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:
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.
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>
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:
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.
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.
Simple view that routes the request to the appropriate channel.
Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things.
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.
Render the autocomplete suggestion box.
By default, render self.autocomplete_template with the channel in the context.
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>