Django / jQuery级联select框?

我想build立一个国家/州select器。 首先select一个国家,第二个select框显示该国的国家。 在PHP和jQuery中这样做是相当容易的,但是我觉得Django在这个意义上是有限制的。

我可以设置状态字段在页面加载时为空,然后用一些jQuery填充它,但是如果有表单错误,它将不能“记住”你select的状态。 我也很确定,它会抛出一个validation错误,因为你的select不是在Python的一面的forms列出的东西之一。

那么如何解决这些问题?

您可以设置一个隐藏的字段来获得真实的“状态”值,然后使用jQuery创build<select>列表,并在.select()其值复制到隐藏字段。 然后,在页面加载时,您的jQuery代码可以获取隐藏字段的值,并在填充后使用它在<select>元素中select正确的项目。

这里的关键概念是状态popup菜单是一个完全由jQuery创build的小说,不属于Djangoforms。 这使您可以完全控制它,同时让所有其他字段正常工作。

编辑:还有另一种方法来做到这一点,但它不使用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来重新加载状态select器。

我还没有testing过,所以可能会有一些漏洞,但是它应该能够实现。

所以你的select是:

  • 在Django表单中使用一个隐藏的字段作为真实值,并通过AJAX创build客户端的select菜单
  • 沟渠Django的表格的东西,并自己初始化菜单。
  • 创build一个自定义的Django表单小部件 ,我没有做过,因此不会评论。 我不知道这是否可行,但看起来你需要一对夫妇在MultiWidget Select s,后者在常规文档中没有logging,所以你必须阅读源代码。

这是我的解决scheme。 它使用未logging的Form方法_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'); }) }); 

callback代码,返回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') 

根据迈克的build议:

 // 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')