Продвинутые формы — мультиселект с автокомплитом

Наши пользователи следят за спецпредложениями авиакомпаний и дешевыми перелетами, каждому интересно своё направление, пунктов вылета и прилета — тысячи, пользователи хотят одновременно следить за несколькоми городами, странами или регионами. Появилась задача — как предоставить удобный механизм подписки? Без долгого тыканья в мульти селект, без кнопок «Добавить пункт вылета». Ответ выглядит так —

Решением стал контрол из Фейсбука и Контакта — пользователи знакомы с ним, а значит не нужно объяснять как он работает. Осталось скрестить его с django.

Этот контрол подойдет для многих случаев когда нужна выборка из большого списка — сотнями или тысячами элементов.
Живой пример — на форме выбора городов для отcлеживания акций авиакомпаний.

Со стророны юзера используем готовый jquery plug-in, доступный на www.emposha.com/javascript/fcbkcomplete.html, скачиваем его и кладем css и js в папку media ( в моём случае — media/css и media/js )
Добавим код формы в foms.py-
class MultiOriginSelect(forms.SelectMultiple):
    class Media:
        css = {
            'all': ('/media/css/fcbkinput.css',)
        }
        js = ('/media/js/jquery.fcbkcomplete.js')
 
class SubscriptionFilterForm(forms.Form):

    CHOICES =[]
    ........
    orgs = forms.MultipleChoiceField(widget=MultiOriginSelect, choices=CHOICES, required=False, initial=[])
    dsts = forms.MultipleChoiceField(widget=MultiOriginSelect, choices=CHOICES,  required=False, initial=[])
    ........

    def __init__(self*args, **kwargs):
        super(SubscriptionFilterForm, self).__init__(*args, **kwargs)

        fcbkcomplete_fields = [u'orgs'u'dsts']
        for field in fcbkcomplete_fields:
            # check whether we have init parameters
            if args:
                loc_list = args[0].getlist(field)
                .....
                # generate dynamic choices for fcbk fields from args, like [id, name]
                self.fields[field].choices = ([(int(o), name(o)) for o in loc_list] )

Последняя строчка — основная, если у формы есть инициализационные значения — заполним choices.

В html файл с формой добавим инициалиазацию контролов:
<head>
<script>
   $(document).ready(function(){
     $("#id_orgs, #id_dsts").fcbkcomplete({
        json_url:'/subscribe_autocomplete',
        first_selected:false,
        filter_hide : true,
        filter_case:false,
        complete_text:"Enter country, city or airport.",
        maxitems: 100
        });
   });
</script>
</head>

<body>
....
    <form>
    ......
    {{form.orgs}}
    {{form.dsts}} 
    <input type = "submit">
    </form>
   ......
</body>


Для того чтобы работал автопокомплит — добавим вьюху, которая будет генерировать необходимый json В нашем случае он расположен по адресу — /subscribe_autocomplete. Итак добавляем — в urls.py:
.....
url(r'^subscribe_autocomplete', subscribe_autocomplete, name='subscribe_autocomplete'),
.....


во views.py:

def subscribe_autocomplete(request):
    q = request.GET.get('tag','')
    # skip too short requests 
    if len(q)<3return HttpResponse('')

    # filter any instances according to tag
    qr = Objects.objects.filter(Q(....))
    
    #generate json
    #message format - [{"caption":"London", "value":4}]
    s =[...];

    return HttpResponse(s)


теперь у нас работает форма на странице и успешно генериться заполненная форма из пост заспроса (form = SubscriptionFilterForm(request.POST) )

Для генерации формы из модели — использую отдельную функцию ( в моем случае ModelsForm не подошел )

def subs_form_from_model(s):
    src_d = {}
    src_d['subscriptionemail'= s.email
    ,,,,

    qd = django.http.QueryDict('')
    qd = qd.copy() # to make muttable
    qd.update(src_d)

    # fill form fields
    qd.setlist('orgs', [unicode(o.id) for o in s.orgs.all()])
    qd.setlist('dsts', [unicode(d.id) for d in s.dsts.all()])

   # create form
    form = SubscriptionFilterForm(qd)
    .......... 

    return form</code>


Большинство пользователей видит поле уже заполненным ( по гео айпи ) и понимает что к чему.
Такую же форму можно ( и нужно ) использовать в админке, для больших мени-ту-мени полей.

PS И чтобы два раза не вставать — если вам понравились буруки (или вы уже пользуетесь нашем поиском и рассылкой) — с радостью пообщаемся с умелым верстальщиком и акробатом JSа.
+55
1 ноября 2010, 20:49
201
Barman 32,4

комментарии (41)

+3
krysanov #
Похожую реализацию мы сделали у себя — cinemate.cc/movie/filter/ (см. вкладки актеры и режиссеры)
Как говорится, все в конечном итоге приходят к одному наиболее удобному и приятно выглядящему варианту.
0
Barman #
с таким контролом особых альтернатив нет, есть еще ajax_selects, поддерживающий мультиселект, но у него немного другая идея — выбранные элементы «откладываются» отдельно от окна ввода
0
krysanov #
ajax_select мы тоже используем, но в админке :) По некоторым причинам мы пока не можем отказаться от него в пользу описываемого варианта.
+4
gvsmirnov #
+13
krysanov #
Я вижу вы очень любите Джорджа Клуни :)
0
gvsmirnov #
Первый, кто выпал в autocomplete на первую нажавшуюся букву. Ну и на вторую тоже. Наверное, я очень люблю букву G)
0
Bobos #
GGG?
НЛО прилетело и опубликовало эту надпись здесь
0
tripiz #
А что за параноидальная мода в последнее время коммерчески белые проекты в домене на кокосовых островах (.cc) делать? Вы же даже не трекер, а база типа кинопоиск.ру — удобная, классная спору нет — оценил… но для ру-аудитории в зоне .cc поди нашлось бы поудобнее для запоминания чем окончаени открытого слога… не ну я понимаю в свое время carderplanet в .cc перехал… но каталог фильмов… это уже диагноз…
0
krysanov #
Вы не поверите, но пользователям западает именно эта отличительная особенность сайта, немало человек приходят с поисковиков, забивая в строке www.google.ru/search?q=поиск+по+торрентам+.cc (реальный запрос)

А если серьезно, то не смотря на то, что вы понимаете, мы понимаем и пользователи понимают, что сайт по сути аггрегатор ссылок на торрент-трекеры, сам ничего не хранит, являясь в некотором роде поисковиком, выдающим ссылки на иные сайты, тем не менее даже его можно закрыть при должном желании. Достаточно вспомнить недавние публикации на тему удаления из Яндекса и/или других поисковиков ссылок на трекеры.
0
djakomo #
cinemate.cc — ого!
Как вы собрали столько фильмов и ссылок — вручную?! Кажется это просто титанический по объему труд.
0
krysanov #
Ссылки и информацию о фильмах собирает робот с трекеров, а мы лишь вручную проверяем данные фильмов перед их публикацией на сайте. Вручную ссылки никто не добавляет, нам бы понадобились тысячи китайцев :)
0
djakomo #
робот так же на питоне написан?
0
krysanov #
Да. Уж больно язык приятный :)
0
roller #
ого, а рутрекер мониторите?
0
krysanov #
Да. Полный список сайтов можно посмотреть здесь — cinemate.cc/site/location/
Список периодически пополняется. В ближайшее время добавим kinozal.tv.
0
Screatch #
Первая что приходит на ум глядя на скриншот — Facebook.
+3
freeek #
Может потому, что «решением стал контрол из Фейсбука и Контакта — пользователи знакомы с ним, а значит не нужно объяснять как он работает»? :)
0
Barman #
и мы только за! контрол называется — fcbkcomplete, стилизацией займемся под общий редизайн
+11
fledgling #
Чёрт, смешной код.

def __init__(self, *args, **kwargs):
    ...
    args[0]


Если кто-то сделает не Form(initial_data), а Form(initial=initial_data), то будет вам эксепшн.

А еще повеселил момент, когда собираете json. Про simplejson (который в >py2.6 import json) не слышали?

Ой, да вы потом еще не application/json (или хотя бы text/javascript) отдаёте клиенту, а text/html.

Кхм, ошибок хоть жопой жуй. Плохая статья, негодная.
0
fledgling #
Ух, я ошибся. Там «if args» — ну вы тут, определенно, решили проблему например.
+1
Barman #
Это повод для обсуждения, спасибо
0
fledgling #
Я думаю, это повод даже не для обсуждения, а для прохождения туториала.
–1
Barman #
про аргсы и типы, спасибо
про джон — проще собрать руками, убрал чтобы не смущать
+1
fledgling #
Проще? Ну ок.

А, ну да, ну да. Explicit is better than implicit.
+2
smartly #
На каком языке заголовок новости?
+1
Eol #
Абсолютно согласен. Мне тоже не нравится, когда так коверкают язык :(
Можно же сказать по-русски — что-нибудь вроде «выбор вариантов с автодополнением» или около того.
0
Barman #
на нашем
+3
svartalf #
self.fields[field].choices = ([(int(o), Location.objects.get(id = int(o)).complete_name) for o in loc_list] )


Но это же ужасно, для loc_list в 1000 элементов будет сделано 1000 запросов в базу данных. Как минимум, замените на
Location.objects.filter(id__in=loc_list)


Про точки с запятой в конце строк я вообще молчу.
–1
Barman #
скорее всего вы правы, в этом конкрентном месте тысяче кратного выигрыша не будет, элементов столько не бывает. точка с запятой осталась от правки раскраски кода — кстати, какая есть нормальная( чтобы можно было редактировать раскрашенный код) подсветка питона, работающая на хабре?
+2
svartalf #
Даже если столько локаций у вас не будет, пример кода вы все равно подаете плохой.

Ну и заметку: django.http.QueryDict можно сделать «мутабельным», передав ему параметр mutable=True при создании экземпляра.
0
kmike #
насчет раскраски — хабр поддерживает тег source с параметром lang='python', гляньте в его справку по html-тегам
0
AlienZzzz #
у меня в опере нихрена не работает!
0
z_z #
Скажем нет многословию в формах!

Так должно выглядеть определение тру-декларативного филда для m2m:

field = AutoSuggestSelectMultiple(attrs={url:'whatever'})

:)
0
Barman #
доведем до такого состояния, на очередном рефакторинге, сейчас главное чтобы работало и приносило пользу, остальное никого не волнует, с той стороны экрана
0
40ins #
При использовании FCBKcomplete столкнулся с двумя багами (первый — критичный):

1. Если заполнить поле каким-либо значением, затем удалить его и снова выбрать это значение, то при отправке формы это значение уже не отправится (проверить можно с помощью демки: www.emposha.com/demo/fcbkcomplete_2/). Этот баг уже известен 4 месяца: github.com/emposha/FCBKcomplete/issues#issue/17, тем не менее, автор не спешит его исправить, как и остальные баги (видимо, проект более не развивается).

2. Глушится TAB, из-за чего мы не можем перейти к следующему полю формы. TAB используется при выборе значения из списка, однако пока мы не начали ничего вводить в поле — глушить TAB незачем.
0
glader #
«Последняя строчка — основная, если у формы есть инициализационные значения — заполним choices.»
Где-то вы не договариваете. Если просто заполнить choices, элементы SelectMultiple выведутся, но не будут selected. Из-за этого скрипт их не отрисовывает. У вас на странице предзаполненные города имеют параметр selected=«selected», а это значит, что вы заполняете еще и поле initial. Но код этого заполнения не показываете. Почему?
0
denplis #
А как вы сделали, что автокомплит для городов находит соответствия и для кириллицы и для латыницы?
0
Krokodile #
с этим уже сервер разбирается, можно и ошибки типа «vjcrdf» == «москва» исправлять
0
macik #
а может кто то подсказать как использую FCBKcomplete вводить не только то что есть в базе (autocomplete) но и новые пункты. Что то подобное есть в Wordpress с добавлением тегов cl.ly/2B01090h2A413n2w1T21
0
Antonio051 #
[{«caption»:«London», «value»:4}]

В версии 2.7.5 fcbkcomplete формат json поменялся:

[{«key»:«London», «value»:4}]

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.