Reputation: 191
I have several rather complex forms which rely on model hierarchies to assign ForeignKey
values. I need to limit the choices available to a child field based upon the value of its parent field. The problem is that the value of a parent field can come from initial data (a GET request) or from bound data (a POST request). Thus, I end up replicating this pattern a lot:
class MyForm(forms.Form):
parent = forms.ModelChoiceField(queryset=Parent.objects.all())
child = forms.ModelChoiceField(queryset=Child.objects.all())
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
# Limit 'child' choices based on current value of 'parent'
if self.is_bound and self.data.get('parent'):
self.fields['child'].queryset = Child.objects.filter(parent__pk=self.data['parent'])
elif self.initial.get('parent'):
self.fields['child'].queryset = Child.objects.filter(parent=self.initial['parent'])
else:
self.fields['child'].choices = []
I get the feeling there's a more efficient way to accomplish this. What am I missing?
Upvotes: 3
Views: 1202
Reputation: 2294
For many thousands child field use Ajax.
urls.py
...
url(r'^api/toselect/(?P<parent_id>\d+)/$', views.toselect),
...
in your views.py response json with Childs
...
from django.http import JsonResponse
...
def toselect(request, parent_id=0):
objs = Child.objects.filter(parent_id=parent_id).values('id', 'name')
return JsonResponse(list(objs), safe=False)
With Jquery populate a child select when parent value change
$("#id_parent").change(function(){
var parentId = $("#id_parent").val()
$.get( base_url + "api/toselect/"+parentId, function( data ) {
$('#id_child').find('option').remove();
$(data).each(function(i, v){
$('#id_child').append('<option value="' + v.id + '">' + v.name+ '</option>');
})
});
});
Upvotes: 0
Reputation: 2294
Generate two select field with all values.
<select id="id_country" name="country">
<option value="" selected="selected">---------</option>
<option value="1">Colombia</option>
<option value="2">Rusia</option>
</select>
<select id="id_city" name="city">
<option value="" selected="selected">---------</option>
<option value="1" class="1">Bogotá</option>
<option value="2" class="2">Moscú</option>
<option value="3" class="2">San Petersburgo</option>
<option value="4" class="1">Valledupar</option>
</select>
Using chained Jquery plugin limit the options
$(function() {
$("#id_city").chained("#id_country");
});
Reed the complete post in https://axiacore.com/blog/django-y-selects-encadenados/
Upvotes: -1
Reputation: 7616
You're in luck. Django Smart Selects make this really simple! You must use a ModelForm (as below) or set up your models with a ChainedForeignKey
field type (as seen in the docs).
Please note - this is NOT exact code. The arguments provided relate to your model definitions and structure. However, the first option would look something like this:
from smart_selects.form_fields import ChainedModelChoiceField
class MyForm(forms.ModelForm):
parent = forms.ModelChoiceField(queryset=Parent.objects.all())
child = ChainedModelChoiceField('your_app','ModelName','parent','parent')
class Meta:
model = YourModel
You must include JQuery in your rendered page in order to use smart selects. When the form fields are rendered, smart selects will include the applicable JQuery code to make Ajax requests when parent
is selected, limiting children
to choices related to the selected parent
.
Upvotes: 2