ModelForm Select field validation internals

Version: Django 2.2

Query:
I was going to implement clean_FIELD() on FooForm to mitigate POST tampering, but it looks like Django already does that inside forms/models.py apply_limit_choices_to_to_formfield(), can anyone confirm?

def apply_limit_choices_to_to_formfield(formfield):
    """Apply limit_choices_to to the formfield's queryset if needed."""
    if hasattr(formfield, 'queryset') and hasattr(formfield, 'get_limit_choices_to'):
        limit_choices_to = formfield.get_limit_choices_to()
        if limit_choices_to is not None:
            formfield.queryset = formfield.queryset.complex_filter(limit_choices_to)

Use case:

Foo Model has a ForeignKey on Category Model. Category Model stores a reference to the owner in created_by. Foo object creation is limited to use only Category instances belonging to the current logged in user. The limitation is implemented using a custom queryset on the Select Field in the ModelForm

Minimal reproducer:

Posting a tampered value (e.g. “C”) against a given queryset (e.g. “A,B”) will result in error, without any need for custom validation code.

unit_tests.py

# ... omitted for brevity
self.client.post(foo_creation_url, data={ 'category': tampered_value, ... }) #  the tampered "C" value is posted
self.assertContains(response, 'Select a valid choice. That choice is not one of the available choices') # Error is display as expected

forms.py

class FooForm(forms.models.ModelForm):

    def __init__(self, user, *args, **kwargs):
        super(FooForm, self).__init__(*args, **kwargs)
        # Prevent displaying other users' Categories in the Select
        self.fields['category'].queryset = models.Category.objects.filter(
                                           created_by=user) # This query returns "A,B"

    # No need to check against tampering , as Django seems to be  already doing it
    # def clean_category(self):
    #    """Implement me: prevent using other users' categories"""
    #    pass

views.py

@method_decorator(login_required, name='dispatch')
class FooCreateView(CreateView):
    model = m.Foo
    form_class = f.FooForm

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['user'] = self.request.user
        return kwargs

    def form_valid(self, form):
        form.instance.created_by = self.request.user
        return super(FooCreateView, self).form_valid(form)

Any hint is welcome, thank you for your time!

If you’re just looking for confirmation on this, then yes.

From the docs for ModelChoiceField:

  • Validates that the given id exists in the queryset.
1 Like

The documentations indeed states that quite clearly. Thank you very much for the confirmation!