#28549 closed Bug (fixed)
Can't defer() fields from super- and sub-class at the same time
| Reported by: | Jeremy Kerr | Owned by: | Jeremy Kerr |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | Jeremy Kerr, Simon Charette | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Using the models:
from django.db import models class Base(models.Model): f1 = models.CharField(max_length=10) class Sub(Base): f2 = models.CharField(max_length=10)
it seems that I can't defer() both f1 and f2 in a single query:
>>> Sub.objects.defer('f1', 'f2').query.sql_with_params()[0]
u'SELECT "defer_base"."id", "defer_base"."f1", "defer_sub"."base_ptr_id" FROM "defer_sub" INNER JOIN "defer_base" ON ("defer_sub"."base_ptr_id" = "defer_base"."id")'
(we're seeing f1 in the SELECT value list).
I seem to be able to defer f1 or f2 separately though.
I'm no django hacker, but: it seems as though django.db.models.sql.query.Query.deferred_to_data() is iterating both models in the loop:
#640: for model, values in six.iteritems(seen): for field in model._meta.fields: if field in values: continue
- and so is discovering f1 twice: once as Base.f1 and again as Sub.f1. Since the field in values test only skips Base.f1, we're still left with Sub.f1 in the workset dict.
Change History (10)
comment:1 by , 8 years ago
| Cc: | added |
|---|---|
| Component: | Uncategorized → Database layer (models, ORM) |
| Type: | Uncategorized → Bug |
follow-up: 3 comment:2 by , 8 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|---|
| Version: | 1.11 → master |
comment:3 by , 8 years ago
Replying to Simon Charette:
I think your intuition is right and that the bug lies in
deferred_to_data(). Could you try replacingfor field in model._meta.fieldsbyfor field in model._meta.local_fieldsand see if it helps?
Yep, that seems to fix the issue:
>>> Sub.objects.defer('f1', 'f2').query.sql_with_params()[0]
u'SELECT "defer_base"."id", "defer_sub"."base_ptr_id" FROM "defer_sub" INNER JOIN "defer_base" ON ("defer_sub"."base_ptr_id" = "defer_base"."id")'
I was concerned that using local_fields would omit inherited fields from the query, but looks like that's not the case; inherited (but not deferred) fields work fine with that change:
class Base(models.Model): f1 = models.CharField(max_length=10) f3 = models.CharField(max_length=10) class Sub(Base): f2 = models.CharField(max_length=10)
>>> Sub.objects.defer('f1', 'f2').query.sql_with_params()[0]
u'SELECT "defer_base"."id", "defer_base"."f3", "defer_sub"."base_ptr_id" FROM "defer_sub" INNER JOIN "defer_base" ON ("defer_sub"."base_ptr_id" = "defer_base"."id")'
- correctly includes f3 in the query. Behaviour also look to remain correct when only deferring fields from one class too.
comment:5 by , 8 years ago
| Has patch: | set |
|---|
Pull request sent: PR
Since this is a trivial patch (and my first), I've not added to AUTHORS or submitted a CLA. Let me know if I need to do either of those.
comment:8 by , 8 years ago
Curious why this didn't reach into the 1.11.6 bugfix release.? We are carrying this patch in Debian after a user request:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=876816
(It was the cause of performance regressions in his application so seemed sufficiently important to carry)
follow-up: 10 comment:9 by , 8 years ago
Chris, probably because it was never reported as a regression in the first place. Can you confirm this was working in a previous version of Django?
comment:10 by , 7 years ago
Replying to Simon Charette:
Chris, probably because it was never reported as a regression in the first place. Can you confirm this was working in a previous version of Django?
Yep. If I understand your question correctly, from: https://bugs.debian.org/876816#5:
This bug caused significant performance degradation when we upgraded a
Django [1.x] application to a new version that relied on model inheritance.
I think your intuition is right and that the bug lies in
deferred_to_data(). Could you try replacingfor field in model._meta.fieldsbyfor field in model._meta.local_fieldsand see if it helps?