Каскадные поля выбора Django/jQuery?

Я хочу создать селектор страны/штата. Сначала вы выбираете страну, и штаты для этой страны отображаются во втором поле выбора. Сделать это в PHP и jQuery довольно просто, но я считаю формы Django немного ограничивающими в этом смысле.

Я мог бы сделать поле State пустым при загрузке страницы, а затем заполнить его каким-нибудь jQuery, но тогда, если есть ошибки формы, он не сможет «запомнить», какое состояние вы выбрали. Я также почти уверен, что это вызовет ошибку проверки, потому что ваш выбор не был одним из перечисленных в форме на стороне Python.

Итак, как мне обойти эти проблемы?


person mpen    schedule 13.07.2010    source источник
comment
попробуйте это github.com/digi604/django-smart-selects   -  person Love Sharma    schedule 14.03.2015


Ответы (3)


Вы можете установить скрытое поле, чтобы оно имело реальное значение «состояние», затем использовать jQuery для создания списка <select> и, в .select(), скопировать его значение в скрытое поле. Затем при загрузке страницы ваш код jQuery может получить значение скрытого поля и использовать его для выбора правильного элемента в элементе <select> после его заполнения.

Ключевой концепцией здесь является то, что всплывающее меню State — это фикция, полностью созданная на jQuery, а не часть формы Django. Это дает вам полный контроль над ним, в то время как все остальные поля работают нормально.

РЕДАКТИРОВАТЬ: есть еще один способ сделать это, но он не использует классы формы Django.

В представлении:

context = {'state': None, 'countries': Country.objects.all().order_by('name')}
if 'country' in request.POST:
    context['country'] = request.POST['country']
    context['states'] = State.objects.filter(
        country=context['country']).order_by('name')
    if 'state' in request.POST:
        context['state'] = request.POST['state']
else:
    context['states'] = []
    context['country'] = None
# ...Set the rest of the Context here...
return render_to_response("addressform.html", context)

Затем в шаблоне:

<select name="country" id="select_country">
    {% for c in countries %}
    <option value="{{ c.val }}"{% ifequal c.val country %} selected="selected"{% endifequal %}>{{ c.name }}</option>
    {% endfor %}
</select>

<select name="state" id="select_state">
    {% for s in states %}
    <option value="{{ s.val }}"{% ifequal s.val state %} selected="selected"{% endifequal %}>{{ s.name }}</option>
    {% endfor %}
</select>

Вам также понадобится обычный JavaScript для перезагрузки селектора штатов при изменении страны.

Я не проверял это, так что, вероятно, в нем есть пара дыр, но это должно передать идею.

Итак, ваш выбор:

  • Используйте скрытое поле в форме Django для реального значения и создайте меню выбора на стороне клиента через AJAX или
  • Откажитесь от Django Form и инициализируйте меню самостоятельно.
  • Создайте виджет пользовательской формы Django, чего я еще не делал, и при этом не буду комментировать. Я понятия не имею, выполнимо ли это, но похоже, что вам понадобится пара Select в MultiWidget, последний недокументирован в обычных документах, поэтому вам придется прочитать исходный код.
person Mike DeSimone    schedule 13.07.2010
comment
Это умная идея. Кажется немного грязным, но я могу жить с этим. - person mpen; 13.07.2010
comment
Это не грязно, если это правильно задокументировано. ^_- - person Mike DeSimone; 13.07.2010
comment
Просто не похоже, что мне нужен скрытый элемент, чтобы обойти некоторые причуды Джанго. - person mpen; 14.07.2010
comment
Django на самом деле не мутирует формы. Всякий раз, когда он у меня есть, я просто делаю форму сам. - person Mike DeSimone; 15.07.2010
comment
Не слишком люблю ваше редактирование, хотя оно работоспособно: p Пока я буду придерживаться скрытого ввода/решения AJAX. - person mpen; 15.07.2010
comment
Я знаю, что этот комментарий немного запоздал, и может быть лучший способ сделать что-то, но, согласно documentation, можно добавить в формы актив Media (который, по сути, просто включает Javascript). - person AmagicalFishy; 09.05.2018

Вот мое решение. Он использует недокументированный метод формы _raw_value() для просмотра данных запроса. Это работает и для форм, у которых есть префикс.

class CascadeForm(forms.Form):
    parent=forms.ModelChoiceField(Parent.objects.all())
    child=forms.ModelChoiceField(Child.objects.none())

    def __init__(self, *args, **kwargs):
        forms.Form.__init__(self, *args, **kwargs)
        parents=Parent.objects.all()
        if len(parents)==1:
            self.fields['parent'].initial=parents[0].pk

        parent_id=self.fields['parent'].initial or self.initial.get('parent') \
                  or self._raw_value('parent')
        if parent_id:
            # parent is known. Now I can display the matching children.
            children=Child.objects.filter(parent__id=parent_id)
            self.fields['children'].queryset=children
            if len(children)==1:
                self.fields['children'].initial=children[0].pk

jquery-код:

function json_to_select(url, select_selector) {
/*
 Fill a select input field with data from a getJSON call
 Inspired by: http://stackoverflow.com/questions/1388302/create-option-on-the-fly-with-jquery
*/
    $.getJSON(url, function(data) {
    var opt=$(select_selector);
    var old_val=opt.val();
        opt.html('');
        $.each(data, function () {
            opt.append($('<option/>').val(this.id).text(this.value));
        });
        opt.val(old_val);
        opt.change();
    })
}


   $(function(){
     $('#id_parent').change(function(){
       json_to_select('PATH_TO/parent-to-children/?parent=' + $(this).val(), '#id_child');
     })  
    });

Код обратного вызова, который возвращает JSON:

def parent_to_children(request):
    parent=request.GET.get('parent')
    ret=[]
    if parent:
        for children in Child.objects.filter(parent__id=parent):
            ret.append(dict(id=child.id, value=unicode(child)))
    if len(ret)!=1:
        ret.insert(0, dict(id='', value='---'))
    return django.http.HttpResponse(simplejson.dumps(ret), 
              content_type='application/json')
person Community    schedule 20.12.2011
comment
Это мне очень помогло. Но _raw_value() удален с версии 1.9. Я использую следующее: ссылка. Кто-нибудь знает лучший способ поймать неотправленное значение? Большое спасибо. - person Maxime VAST; 08.08.2017

По предложению Майка:

// the jQuery
$(function () {
        var $country = $('.country');
        var $provInput = $('.province');
        var $provSelect = $('<select/>').insertBefore($provInput).change(function() {
                $provInput.val($provSelect.val());      
        });
        $country.change(function() {
                $provSelect.empty().addClass('loading');
                $.getJSON('/get-provinces.json', {'country':$(this).val()}, function(provinces) {
                        $provSelect.removeClass('loading');
                        for(i in provinces) {
                                $provSelect.append('<option value="'+provinces[i][0]+'">'+provinces[i][1]+'</option>');
                        }
                        $provSelect.val($provInput.val()).trigger('change');
                });
        }).trigger('change');
});

# the form
country = CharField(initial='CA', widget=Select(choices=COUNTRIES, attrs={'class':'country'}))
province = CharField(initial='BC', widget=HiddenInput(attrs={'class':'province'}))

# the view
def get_provinces(request):
    from django.utils import simplejson
    data = {
        'CA': CA_PROVINCES,
        'US': US_STATES
    }.get(request.GET.get('country', None), None)
    return HttpResponse(simplejson.dumps(data), mimetype='application/json')
person mpen    schedule 13.07.2010
comment
Хм... еще не передал функцию jQuery, поэтому я не уверен, что это делает. Обычно я вижу (function($){...})(jQuery);, когда вы хотите, чтобы $ было чем-то другим, кроме jQuery. Кроме того, в цикле for (i in provinces) я бы использовал $('<option/>').val(provinces[i][0]).text(provinces[i][1]).appendTo($provSelect);. Я предполагаю, что мой способ медленнее, но его легче читать, когда вы помещаете предложения в одну строку каждое. Кроме того, IIRC, вы можете использовать $('#id_country') вместо $('.country'); то же самое для виджета провинции, который может позволить вам сбрасывать class материал в форму. - person Mike DeSimone; 13.07.2010
comment
@Mike: $(function() — это сокращение от $(document).ready(function(). Я намеренно использую класс, потому что на одной странице может быть несколько селекторов страны/провинции... не то чтобы этот скрипт работал с несколькими селекторами как есть. - person mpen; 14.07.2010