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!