Opened 10 years ago
Last modified 4 years ago
#23870 new Bug
Sliced QuerySets in ModelChoiceField
Reported by: | Kamil Śliwak | Owned by: | nobody |
---|---|---|---|
Component: | Forms | Version: | 1.6 |
Severity: | Normal | Keywords: | modelchoicefield limit queryset slicing |
Cc: | Triage Stage: | Accepted | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
The issue
ModelChoiceField
raises an exception if its queryset
is a query that has been sliced (i.e. resolves to a SQL SELECT
with LIMIT
). The failure occurs only after form submission - during validation - so it's not obvious to the user if it's unsupported or if he's just using it incorrectly.
This should either be fixed or documented as an unsupported use case. In the latter case the error should appear earlier (in field constructor?) and the message should tell the user explicitly that it's not supported.
Example
Let's say you have a model called Book:
class Book(models.Model): rating = models.IntegerField()
You want to create a form that lets user select one of the top rated books. So you try:
class BookForm(forms.Form): book = forms.ModelChoiceField(queryset = Book.objects.order_by('-rating')[:100])
It appears to work - the form can be rendered and you can choose one of a hundered top-rated books. But when you submit, you get the following error:
AssertionError: Cannot filter a query once a slice has been taken.
The error is caused by `ModelChoiceField.to_python()` validating the existence of the selected item by calling get()
on the queryset:
value = self.queryset.get(**{key: value})
And this is not supported for sliced querysets as the error above states.
Workarounds
To work around the problem one can make the sliced query a subquery:
class BookForm(forms.Form): book = forms.ModelChoiceField(queryset = Book.objects.filter(pk__in = Book.objects.order_by('-rating')[:100].values_list('pk')))
On my machine this is about 4 times slower than a simple query with LIMIT
(see the discussion thread linked below) but seems to work without any adverse effects.
One nice feature of this workaround is that the outer query can have different ordering than the one used for slicing which might be useful in some cases. E.g. select top rated books but sort them by title.
I think that it would be a good idea to mention this workaround in the docs.
Discussion
Here's the discussion thread regarding the issue on django-developers mailing list: Why doesn't ModelChoiceField.queryset support slicing?
I'm marking this ticket as accepted as I believe the limitation should at least be documented.
Even if I came up with the workaround (
pk__in=...
), I don't think it should be documented as it won't scale well. Also, that problem is related to the fact that a sliced queryset cannot be filtered, see #22503.