По моему скромному мнению, сейчас найти правильный сниппет, показывающий как реализовать автозаполнение на том или ином языке программирования с использованием того или иного JavaScript фреймворка - пустяковое дело.
Так что будем считать, что сегодня просто сама по себе настала очередь связки jQuery + Django. Реализация виджета автозаполнения в этой связке - проста до ужаса. Особенно, когда используется великолепный jQuery Autocomplete plugin.
Вся идея реализации состоит в следующем:
- Определяем какие данные будут использоваться в автозаполнении (локальные или удаленные)
- Если данные удаленные, пишем view и добавляем этот view в urlpatterns
- Получаем автозаполняемое поле
Но чтобы потом в дальнейшем по сто раз не дописывать этот виджет я чуточку его модифицировал. И посему Вы можете указать какие поля являются зависимыми от текущего (это например, очень полезно в связке страна - город, ведь незачем выбрав страну Украина, автодополнять город Амстердам). Это раз, а два - Вы можете указать какие именно опции плагина вам необходимы для этого поля.
Ну что ж, перейдем к программному коду. Для начала сам виджет:
from django.core.urlresolvers import reverse, NoReverseMatch from django.newforms import HiddenInput, TextInput from django.utils import simplejson from django.utils.safestring import mark_safe class AutocompleteWidget(TextInput): """ Autocomplete widget to use with jquery-autocomplete plugin. Widget can use for static and dynamic (AJAX-liked) data. Also you can relate some fields and it's values'll posted to autocomplete view. Widget support all jquery-autocomplete options that dumped to JavaScript via django.utils.simplejson. **Note** You must init one of ``choices`` or ``choices_url`` attribute. Else widget raises TypeError when rendering. """ def __init__(self, attrs=None, choices=None, choices_url=None, options=None, related_fields=None): """ Optional arguments: ------------------- * ``choices`` - Static autocomplete choices (similar to choices used in Select widget). * ``choices_url`` - Path to autocomplete view or autocomplete url name. * ``options`` - jQuery autocomplete plugin options. Auto dumped to JavaScript via SimpleJSON * ``related_fields`` - Fields that relates to current (value of this field will sended to autocomplete view via POST) """ self.attrs = attrs or {} self.choice, self.choices, self.choices_url = None, choices, choices_url self.options = options or {} if related_fields: extra = {} if isinstance(related_fields, str): related_fields = list(related_fields) for field in related_fields: extra[field] = "%s_value" % field self.extra = extra else: self.extra = {} def render(self, name, value=None, attrs=None): if not self.choices and not self.choices_url: raise TypeError('One of "choices" or "choices_url" keyword argument must be supplied obligatory.') if self.choices and self.choices_url: raise TypeError('Only one of "choices" or "choices_url" keyword argument can be supplied.') choices = '' if self.choices: self.set_current_choice(value) choices = simplejson.dumps([unicode(v) for k, v in self.choices], ensure_ascii=False) html_code = HiddenInput().render(name, value=value) name += '_autocomplete' else: html_code = '' if self.choices_url: try: choices = simplejson.dumps(reverse(str(self.choices_url))) except NoReverseMatch: choices = simplejson.dumps(self.choices_url) if self.options or self.extra: if 'extraParams' in self.options: self.options['extraParams'].update(self.extra) else: self.options['extraParams'] = self.extra options = ', ' + simplejson.dumps(self.options, indent=4, sort_keys=True) extra = [] for k, v in self.extra.items(): options = options.replace(simplejson.dumps(v), v) extra.append(u"function %s() { return $('#id_%s').val(); }\n" % (v, k)) extra = u''.join(extra) else: extra, options = '', '' final_attrs = self.build_attrs(attrs) html_code += super(AutocompleteWidget, self).render(name, self.choice or value, attrs) html_code += u""" <script type="text/javascript"><!-- %s$('#%s').autocomplete(%s%s); --></script> """ % (extra, final_attrs['id'], choices, options) return mark_safe(html_code) def set_current_choice(self, data): if not self.choices: raise ValueError('"choices" attribute was not defined yet.') for k, v in self.choices: if k == data: self.choice = v break def value_from_datadict(self, data, files, name): if not self.choices: return super(AutocompleteWidget, self).value_from_datadict(data, files, name) autocomplete_name = name + '_autocomplete' if not autocomplete_name in data: self.set_current_choice(data[name]) return data[name] for k, v in self.choices: if v == data[autocomplete_name]: self.set_current_choice(k) return k
Теперь примеры его использования:
forms.py from django import newforms as forms from django.utils.translation import ugettext as _ from myproject.widgets import AutocompleteWidget SPORTS_CHOICES = ( ('basketball', _('Basketball')), ('football', _('Football')), ('hockey', _('Hockey')), ) class SampleForm(forms.Form): name = forms.CharField(label=_('Name')) country = forms.CharField(label=_('Country'), widget=AutocompleteWidget(choices_url='autocomplete_countries', related_fields=('city',))) city = forms.CharField(label=_('City), widget=AutocompleteWidget(choices_url='autocomplete_cities', related_fields=('country',))) sports = forms.ChoiceField(label=_('Sports'), choices=SPORTS_CHOICES, widget=AutocompleteWidget(options={'minChars': 0, 'autoFill': True, 'mustMatch': True})) urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', url('/path/to/cities/autocomplete/', 'views.autocomplete_cities', name='autocomplete_cities'), url('/path/to/countries/autocomplete/', 'views.autocomplete_countries', name='autocomplete_countries'), ) views.py from myproject.models import City def autocomplete_cities(request): def results_to_string(results): if results: for r in results: yield '%s|%s\n' % (r.name, r.pk) city = request.REQUEST.get('q', None) country = request.REQUEST.get('country', None) if city: cities = City.objects.filter(city__istartswith=city) else: cities = City.objects.all() if country: cities = cities.filter(country__name=country) cities = cities[:int(request.REQUEST.get('limit', 15))] return results_to_string(cities, mimetype='text/plain')
А вот как это выглядит ВРЛ:
- Автозаполнение поля "City" (удаленные данные)
- Автозаполнение поля "Country" (удаленные данные)
- Связь при автозаполнении между полями "Country" и "Сity"
- Автозаполнение поля "Sports" (локальные данные)
За сим прощаюсь и поздравляю всех с наступающим долгожданным летом!
7 комментариев:
таки все-же последнюю строчку нада бы заменить на
return HttpResponse(results_to_string(cities), mimetype='text/plain')
Кстати, да, согласен с Вами на 100 процентов, причем в рабочей версии так именно и сделано ;)
В примере пердаётся стринг и id через |
Но в возвращаемом значении которое обрабатывается forms id нету :(
Не подскажете как его получить?
на мой взгляд много манипуляций, предлагаю свой вариант,
виджет для админки он заменяет поведение для related_search_fields,
скачать можно здесь
http://code.google.com/p/django-autocomplete/ описание там-же
забыл главное написать :)
виджет работает как с ForeignKey полями , так и с ManyToManyField, так что пользуйтесь
Спасибо конечно, полезная статья в качестве теории. Но на практике нам нужно получить в итоге id, а не саму строку.
dimasbka, это только для админки? Тогда это плохой вариант.
В качестве пакета оформить не думали? скажем для pip?
Отправить комментарий