#25496 closed Bug (fixed)
ModelChoiceField generates extra queries by not respecting prefetch_related
| Reported by: | Matt d'Entremont | Owned by: | nobody |
|---|---|---|---|
| Component: | Forms | Version: | 1.8 |
| Severity: | Release blocker | Keywords: | |
| Cc: | marti@… | Triage Stage: | Ready for checkin |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Note: could be worked around if this were resolved: https://code.djangoproject.com/ticket/22841
When a ModelChoiceField's choices are generated, the queryset's prefetch_related calls are not evaluated, which can lead to 1 query per choice if the model's unicode accesses a related field.
Example models:
from django.db import models class RelatedObj(models.Model): name = models.CharField(max_length=255) class ObjWithRelated(models.Model): name = models.CharField(max_length=255) related = models.ManyToManyField(RelatedObj) def __unicode__(self): return '{}: {}'.format(self.name, self.related.count())
With many models, we can see the following results:
from django.db import connection from django.forms import ModelChoiceField from django.test.utils import CaptureQueriesContext queryset = ObjWithRelated.objects.prefetch_related('related') field = ModelChoiceField(queryset) with CaptureQueriesContext(db.connection) as queries: list(field.choices) print 'ModelChoiceField query count: {}'.format(len(queries)) with CaptureQueriesContext(db.connection) as queries: [str(obj) for obj in queryset] print 'Regular query count: {}'.format(len(queries))
This will have the output of:
There are ways to work around this, but ideally there would be a solution which wouldn't require a workaround. We're using django-parler to translate models, without the prefetching we get 1 query per dropdown choice, per dropdown when a translated model is displayed in the django admin.
Change History (14)
comment:1 by , 10 years ago
| Resolution: | → invalid |
|---|---|
| Status: | new → closed |
comment:2 by , 10 years ago
Hello charettes,
Sorry, I used .count() in the example to try and simplify things a little. In actuality we are accessing a field on a related object in our unicode, in which case we definitely would want to use prefetch_related to avoid 1 query per __unicode__.
comment:3 by , 10 years ago
| Resolution: | invalid → duplicate |
|---|
Then I guess this is simply a duplicate of #22841?
comment:4 by , 10 years ago
Potentially?
My thinking was that #22841 is for passing in a queryset which will not be revaluated, where I want the re-evaluation to a avoid stale content, I just want the prefetch to be respected to avoid too many queries.
Also it took us a ton of investigation to determine why the prefetch wasn't performed, it is not obvious at all. If I pass a queryset along, I expect that it will be used entirely, not just certain pieces of it.
comment:5 by , 10 years ago
| Resolution: | duplicate |
|---|---|
| Status: | closed → new |
| Type: | Uncategorized → Bug |
Hmm it looks like it might be a regression in 1.8 cause by the fix for #23623 (fa534b92dda0771661a98a1ca302ced264d0a6da) which introduced the use of iterator() to reduce memory usage.
From the prefetch_related docs
Note that if you use
iterator()to run the query,prefetch_related()calls will be ignored since these two optimizations do not make sense together.
comment:6 by , 10 years ago
| Has patch: | set |
|---|---|
| Severity: | Normal → Release blocker |
| Triage Stage: | Unreviewed → Accepted |
| Version: | → 1.8 |
comment:7 by , 10 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
comment:11 by , 10 years ago
| Cc: | added |
|---|
It appears that this fix caused a regression. I have submitted pull requests with fixes, see ticket #25683.
Hi mdentremont,
I think you misunderstood how
prefetch_relatedandcount()interact. Thecount()method never takes prefetched data into consideration and always result in a new query.If you want to avoid an extra query I suggest you
annotate()your queryset with its count of related objects and adapt your models__unicode__to lookup the annotated attribute first and fallback to callingself.related.count():Please use support channels before filling a ticket wiki:TicketClosingReasons/UseSupportChannels