Django/jQuery 级联选择框?
Posted
技术标签:
【中文标题】Django/jQuery 级联选择框?【英文标题】:Django/jQuery Cascading Select Boxes? 【发布时间】:2011-03-15 02:44:03 【问题描述】:我想构建一个国家/地区选择器。首先,您选择一个国家,该国家的州显示在第二个选择框中。在 php 和 jQuery 中这样做相当容易,但我发现 Django 表单在这个意义上有点限制。
我可以在页面加载时将 State 字段设置为空,然后用一些 jQuery 填充它,但是如果出现表单错误,它将无法“记住”您选择的 State。我也很确定它会引发验证错误,因为您的选择不是 Python 方面的表单中列出的选项之一。
那么我该如何解决这些问题呢?
【问题讨论】:
试试这个github.com/digi604/django-smart-selects 【参考方案1】:您可以将隐藏字段设置为具有真正的“状态”值,然后使用 jQuery 创建<select>
列表,并在.select()
上将其值复制到隐藏字段。然后,在页面加载时,您的 jQuery 代码可以获取隐藏字段的值,并在填充后使用它在 <select>
元素中选择正确的项目。
这里的关键概念是状态弹出菜单是完全用 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 的表单内容并自己初始化菜单。 创建一个custom Django form widget,我还没有这样做,因此不会评论。我不知道这是否可行,但看起来您需要在MultiWidget
中添加一对Select
s,后者在常规文档中没有记录,因此您必须阅读源代码。李>
【讨论】:
这是一个聪明的主意。看起来有点脏,但我可以忍受。 如果有正确的记录,它就不是脏的。 ^_- 我似乎不需要隐藏元素来解决 Django 的一些怪癖。 Django 并没有真正做变异表单。只要我有一个,我就自己做表格。 也不太喜欢你的编辑,虽然它是可行的:p 我现在会坚持使用隐藏输入/AJAX sol'n。【参考方案2】:根据 Mike 的建议:
// 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')
【讨论】:
嗯...还没有将函数传递给 jQuery,所以我不确定那是什么。通常我看到(function($)...)(jQuery);
是因为您希望$
不是jQuery
。此外,在for (i in provinces)
循环中,我将使用$('<option/>').val(provinces[i][0]).text(provinces[i][1]).appendTo($provSelect);
。我猜我的方式比较慢,但是当你把子句放在一行时更容易阅读。此外,IIRC,您可以使用$('#id_country')
而不是$('.country')
;省份小部件也是如此,它可以让您在表单中转储 class
内容。
@Mike:$(function()
是$(document).ready(function()
的简写。我故意使用一个类,因为一个页面上可能有多个国家/省选择器……并不是说这个脚本可以与多个原样一起使用。【参考方案3】:
这是我的解决方案。它使用未记录的 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://***.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')
【讨论】:
这对我帮助很大。但是 _raw_value() 从 1.9 开始被删除。我正在使用以下内容:link。有人知道捕获未提交值的更好方法吗?非常感谢。以上是关于Django/jQuery 级联选择框?的主要内容,如果未能解决你的问题,请参考以下文章