# widgets.py from django.forms.widgets import TextInput, flatatt from django.forms.util import force_unicode from django.utils.html import escape from django.utils import simplejson from django.utils.safestring import mark_safe class AutocompleteInput(TextInput): """ A form text input that gets rendered as an autocomplete widget using jQuery UI's autocomplete. http://api.jqueryui.com/autocomplete/ The only required option is choices: class FooForm(forms.Form): selections = forms.CharField(max_length=10, widget=AutocompleteInput(choices=MY_SELECTIONS)) """ def __init__(self, choices, options={}, attrs={}, *args, **kwargs): super(TextInput, self).__init__(attrs=attrs, *args, **kwargs) self.choices = choices self.options = simplejson.dumps(options) if len(options) > 0 else None self.attrs = attrs def render(self, name, value=None, attrs={}): final_attrs = self.build_attrs(attrs, name=name) # Handles different types of choices if isinstance(self.choices, list): source = simplejson.dumps(self.choices) elif isinstance(self.choices, str): source = "'{0}'".format(escape(self.choices)) elif isinstance(self.choices, tuple): # assumes tuple will be 2-item choice tuples (as in the docs) try: ## This sets the displayed values # If you have (for example) "fruit" tuples like (('apples', 'apples'),), then you can # just use item[0] for the "label" # The below set up is useful for displaying the human-readable format, and inserting # the value that will go into the database. For instance, (('FL', 'Florida'),) will # display "Florida" but insert "FL" into the field. source = simplejson.dumps([{"label": "{0}".format(item[1]), "value": "{0}".format(item[0])} for item in self.choices]) except IndexError: raise ValueError("choices tuple is not valid") else: raise ValueError("choices type is not valid") options = '' if self.options: options += ",{0}".format(self.options) # for passing add'l autocomplete options if value: value = force_unicode(value) final_attrs['value'] = escape(value) if not self.attrs.has_key('id'): final_attrs['id'] = 'id_{0}'.format(name) return mark_safe(u''' '''.format(name, flatatt(final_attrs), final_attrs['id'], source, options)) ## EXAMPLE # # forms.py # from django.contrib.localflavor.us.us_states import US_STATES, US_TERRITORIES from .widgets import AutocompleteInput class ResidentForm(forms.ModelForm): ALL_STATES = tuple(sorted(US_STATES + US_TERRITORIES, key=lambda obj: obj[1])) ... state = forms.CharField(max_length=2, widget=AutocompleteInput(choices=ALL_STATES) # just use {{ form.state }} in template ...