Choose, and choose quickly: Optimising ModelChoiceField
2020-09-19, 15:50–16:30, Virtual

Ever had a ModelForm, a DRF Serializer, a FilterSet grind to a halt rendering a choice field? Of course you have. Ever given up on it and resorted to raw ids? -- No don't answer that.

We're going to look at how you can get a grip on ModelChoiceField when you're dealing with lots of related objects, and when you need to offer that choice again and again and again, without needing to put the kettle on.


When we talk about performance in Django, the usual example is iterating a QuerySet.
We have our list of authors and we reach across a relationship to fetch each one's blog posts.
Maybe we reach across another one to fetch the comments on each of the blog posts.
All of a sudden, our view, which worked fine with the three object test data in development, grinds to a halt.
For this we have prefetch_related() and select_related(), and there are lots of talks and posts on how to leverage those.

I want to talk about an example I see coming up in the Admin list view, when renderering DRF serializers, a Django Filter FilterSet, or a ModelForm, particularly when using a form templating system like Crispy Forms.

In these cases, when you're rendering a ModelChoiceField with many related objects, or when you´re repeatedly rendering the same choice field, in a FormSet, such as the admin´s list_editable, performance drops off a cliff.
Frequently issues are opened on Django, or DRF, or Django Filter, complaining that the rendering is slow, that something is broken.
Often the solution is along the lines of the admin´s raw_id_fields, but this feels like surrender. We loose all the data validation and UI affordance that ModelChoiceField gives us.

There has to be a better way. Spoiler: there is.

I will show a few examples of slow rendering choice fields: the admin, DRF, Django-Filter, and a FormSet rendered with Crispy Forms.

For each, we´ll build up simple example. That looks like it's fine. Give it a moderate amount of data and we have issues. (Put even a small number of choices in a large FormSet and see how it grinds.)

Then we'll dig in and show where we can re-use work to speed up the choice field generation. We'll see that in all of our cases we can get performant results, and that there's no need to adopt a sub-optimal alternative.

Video: https://www.youtube.com/watch?v=e52S1SjuUeM&ab_channel=DjangoConEurope