Opened 3 years ago

Last modified 3 years ago

#23870 assigned Bug

Sliced QuerySets in ModelChoiceField

Reported by: Kamil Śliwak Owned by: nishkr
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?

Change History (2)

comment:1 Changed 3 years ago by Thomas Chaumeny

Triage Stage: UnreviewedAccepted

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.

comment:2 Changed 3 years ago by nishkr

Owner: changed from nobody to nishkr
Status: newassigned
Note: See TracTickets for help on using tickets.
Back to Top