filtering forms.ModelChoiceField queryset when form.is_valid

Hello everyone,

I have a simple form:

class CertificateSearchForm(forms.Form):
    code = forms.ModelChoiceField(queryset=Code.objects.all(),
                                  required=False,
                                  widget=forms.Select(
                                      attrs={
                                          'onchange': 'load_sub_codes();',
                                      }
                                  )
                                  )
    sub_code = forms.ModelChoiceField(queryset=SubCode.objects.all(), required=False)

When the form is submited, and the form.is_valid I would like to know if there is a possibility to add a filter to the queryset of the sub_code field i.e SubCode.objects.all().filter(abc=xyz)?

If the form has been submitted, and has already passed all validity tests, what’s your objective for doing this? What are you trying to achieve?

Hello Ken, thank you for your time.

On the html form I have two selects. I update de option list from the second select with the choice of the first and a ajax call.

When I submit the form, if the form is valid, the page will be reloaded with the data.

when I choose a value from the first select, the ajax call is made and le the select is updated with the pertinent choices, this works fine.

My issue is that when I submit the form, the page will be reloaded (with the cleaned data) but the options available in the second select are ‘reset’ to the full list.

I probably can manage this with javascript but I was wondering if there was a way to ‘manipulate’ the CertificateSearchForm object within the view after it was submitted and before rendering back?

Specifically here I would have get the code from the cleaned data then return a filtered sub_code with only the pertinent choices (Subcode.objects.fileter(code=code)). And also mark selected the option from the second select if it was set.

I hope it’s clear.

I believe you’re asking how to render the form on the view with the filtered sub_codes for a POST.

You should be able to achieve this by utilizing the code value and adjusting the sub_code field’s queryset similarly to however the AJAX view does (looks like Subcode.objects.filter(code=code))

So if you have something like:

def post_view(request):
    if request.method == "GET":
        # return 403
    form = CertificateSearchForm(request.POST)
    if form.is_valid():
        # Save data

    return render(request, context={"form": form})

You could adjust the queryset on the field in the view as follows:

def post_view(request):
    if request.method == "GET":
        # return 403
    form = CertificateSearchForm(request.POST)
    if form.is_valid():
        # Save data
        # Since we know we have a valid code due to `is_valid`, let's filter the subcodes down
        # to the relevant code to render roughly the same view.
        form.fields['sub_code'].queryset = Subcode.objects.filter(code=form.cleaned_data['code'])

    return render(request, context={"form": form})

You could also potentially do this in your form, but it gets a little trickier because you’d be handling data that hasn’t been cleaned yet.

class CertificateSearchForm(forms.Form):
    code = forms.ModelChoiceField(queryset=Code.objects.all(),
                                  required=False,
                                  widget=forms.Select(
                                      attrs={
                                          'onchange': 'load_sub_codes();',
                                      }
                                  )
                                  )
    sub_code = forms.ModelChoiceField(queryset=SubCode.objects.all(), required=False)
    
    def __init__(*args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.data and self.data.get('code'):
            self.fields['sub_code'].queryset = Subcode.objects.filter(code=self.data['code'])
2 Likes

Thank you very much Tim.

I think it’s exactly what I was looking for…

I’ll try that ASAP.

If someone read this and has a similar issue, I added the selected options to be rendered in the template:

if form.is_valid():

if form.cleaned_data[‘sub_code’]:
form.fields[‘sub_code’].initial = [form.cleaned_data[‘sub_code’]]
return …

Thanks again to Tim and Ken for their time, much appreciated