#23870: Sliced QuerySets in ModelChoiceField
-------------------------+-------------------------------------------------
     Reporter:  cameel   |      Owner:  nobody
         Type:  Bug      |     Status:  new
    Component:  Forms    |    Version:  1.6
     Severity:  Normal   |   Keywords:  modelchoicefield limit queryset
 Triage Stage:           |  slicing
  Unreviewed             |  Has patch:  0
Easy pickings:  0        |      UI/UX:  0
-------------------------+-------------------------------------------------
 == 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:

 {{{#!python
 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:

 {{{#!python
 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
 
[[https://github.com/django/django/blob/bfb11b95626f39e2f5e18d97d7761c6f93dcc1a9/django/forms/models.py#L1195-L1203|`ModelChoiceField.to_python()`]]
 validating the existence of the selected item by calling `get()` on the
 queryset:

 {{{#!python
 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:

 {{{#!python
 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
 
[[https://docs.djangoproject.com/en/dev/ref/forms/fields/#django.forms.ModelChoiceField.queryset|the
 docs]].

 == Discussion ==

 Here's the discussion thread regarding the issue on django-developers
 mailing list: [[https://groups.google.com/forum/#!topic/django-
 developers/ELqU2xt_Qo0|Why doesn't ModelChoiceField.queryset support
 slicing?]]

--
Ticket URL: <https://code.djangoproject.com/ticket/23870>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-updates+unsubscr...@googlegroups.com.
To post to this group, send email to django-updates@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/049.5c459bcbba55dd057591c068a11c17a2%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to