Jeremy Stretch
Jeremy Stretch

Reputation: 191

How to limit the choices of a child field based on a parent field's value?

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

Answers (3)

erajuan
erajuan

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

erajuan
erajuan

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

Ian Price
Ian Price

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

Related Questions