Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django forms prefetch on cleaned data

I am working a improving the amount of database hits my Django application makes, and one of my gripes is with the Django forms.

When I GET a page with a form, it will load objects from the database in order to fill in ModelChoiceFields, which is great.

When I POST some form data, the form will clean the data. Now, in my clean_foo method of the form, I want to access one of the foo objects relations: foo.bar. This will hit the database to get the bar object.

Is there any way for me to prefetch bar? What I mean is that when the form uses the pk to find the foo object, can I have it prefetch the bar as well? Where might I do that?

Looking at the Django source code, it seems that the chosen object is fetched directly with .get() and not as a queryset with .filter()

def to_python(self, value):
    if value in self.empty_values:
        return None
    try:
        key = self.to_field_name or 'pk'
        value = self.queryset.get(**{key: value})    # <-- Right here
    except (ValueError, TypeError, self.queryset.model.DoesNotExist):
        raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
    return value

So, what that tells me is that I am not supposed to try anything there. Best thing I can do is

def clean_foo(self):
    foo = Foo.objects.filter(pk=self.cleaned_data['foo'].pk).select_related('bar')
    [...]

There I can prefetch what I need for the rest of the logic. So it won't be 1 query, but I can make it at most 2 queries.

I realise this is starting to sound like a statement and not a question, so please just prove me wrong, if possible

like image 551
Eldamir Avatar asked Mar 13 '23 20:03

Eldamir


1 Answers

It seems like you could use select_related directly in the definition of the queryset for your field:

class MyForm(forms.ModelForm):
    my_field = forms.ModelChoiceField(queryset=Foo.objects.select_related('bar'))
like image 102
Daniel Roseman Avatar answered Mar 20 '23 08:03

Daniel Roseman