#13781 closed Bug (fixed)
select_related and multiple inheritance
Reported by: | Shaun Cutts | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | shaun@…, Vlastimil Zíma, srmerritt, mike@…, gosia, tomas.ehrlich@… | 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
I have a "Profile" model that inherits both from auth.User and Person -- representing people who are users.
Such people also have accounts -- Account has a OneToOneField relating it to a Profile.
A particular user has an account.
>>> profile = Profile.objects.get( pk = 9819 ) >>> profile <Profile: bob test> >>> profile.account <Account: bob test>
But when I use a select_related, an error occurs:
In [36]: profile = Profile.objects.select_related( 'account' ).get( pk = 9819 ) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) /Users/shauncutts/<ipython console> in <module>() /Users/shauncutts/dev/django/db/models/query.pyc in get(self, *args, **kwargs) 334 if self.query.can_filter(): 335 clone = clone.order_by() --> 336 num = len(clone) 337 if num == 1: 338 return clone._result_cache[0] /Users/shauncutts/dev/django/db/models/query.pyc in __len__(self) 79 self._result_cache = list(self._iter) 80 else: ---> 81 self._result_cache = list(self.iterator()) 82 elif self._iter: 83 self._result_cache.extend(list(self._iter)) /Users/shauncutts/dev/django/db/models/query.pyc in iterator(self) 267 268 compiler = self.query.get_compiler(using=self.db) --> 269 for row in compiler.results_iter(): 270 if fill_cache: 271 obj, _ = get_cached_row(self.model, row, /Users/shauncutts/dev/django/db/models/sql/compiler.pyc in results_iter(self) 670 resolve_columns = hasattr(self, 'resolve_columns') 671 fields = None --> 672 for rows in self.execute_sql(MULTI): 673 for row in rows: 674 if resolve_columns: /Users/shauncutts/dev/django/db/models/sql/compiler.pyc in execute_sql(self, result_type) 715 """ 716 try: --> 717 sql, params = self.as_sql() 718 if not sql: 719 raise EmptyResultSet /Users/shauncutts/dev/django/db/models/sql/compiler.pyc in as_sql(self, with_limits, with_col_aliases) 53 in the query. 54 """ ---> 55 self.pre_sql_setup() 56 out_cols = self.get_columns(with_col_aliases) 57 ordering, ordering_group_by = self.get_ordering() /Users/shauncutts/dev/django/db/models/sql/compiler.pyc in pre_sql_setup(self) 27 self.query.setup_inherited_models() 28 if self.query.select_related and not self.query.related_select_cols: ---> 29 self.fill_related_selections() 30 31 def quote_name_unless_alias(self, name): /Users/shauncutts/dev/django/db/models/sql/compiler.pyc in fill_related_selections(self, opts, root_alias, cur_depth, used, requested, restricted, nullable, dupe_set, avoid_set) 608 alias = root_alias 609 alias_chain = [] --> 610 chain = opts.get_base_chain(f.rel.to) 611 if chain is not None: 612 for int_model in chain: /Users/shauncutts/dev/django/db/models/options.pyc in get_base_chain(self, model) 427 return [model] 428 for parent in self.parents: --> 429 res = parent._meta.get_base_chain(model) 430 if res: 431 res.insert(0, parent) /Users/shauncutts/dev/django/db/models/options.pyc in get_base_chain(self, model) 432 return res 433 raise TypeError('%r is not an ancestor of this model' --> 434 % model._meta.module_name) 435 436 def get_parent_list(self): TypeError: 'profile' is not an ancestor of this model
Examination of the stack trace shows that the problem may be related to the fact that Profile inherits from both User and Person
>>> pdb.pm() > /Users/shauncutts/dev/django/db/models/options.py(434)get_base_chain() -> % model._meta.module_name) (Pdb) u > /Users/shauncutts/dev/django/db/models/options.py(429)get_base_chain() -> res = parent._meta.get_base_chain(model) (Pdb) print parent <class 'cranedata.db.base.models.Person'>
Attachments (5)
Change History (35)
comment:1 by , 14 years ago
Cc: | added |
---|
comment:2 by , 14 years ago
Resolution: | → wontfix |
---|---|
Status: | new → closed |
comment:3 by , 14 years ago
Resolution: | wontfix |
---|---|
Status: | closed → reopened |
Um .. ok: but may I suggest that you have a documentation bug then? See:
http://docs.djangoproject.com/en/1.2/topics/db/models/#multiple-inheritance
It certainly doesn't say its not supported.
comment:4 by , 14 years ago
Huh. Ok... that's interesting. Thanks for setting me straight -- I wasn't aware we explicitly said that in the docs.
In that case, there's definitely a bug here.
comment:5 by , 14 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:6 by , 14 years ago
Cc: | added |
---|
comment:7 by , 14 years ago
Severity: | → Normal |
---|---|
Type: | → Bug |
comment:8 by , 14 years ago
Cc: | added |
---|---|
Easy pickings: | unset |
I am getting a separate error from the same issue (select_related failing to handle multiple inheritance), in v1.2.
My traceback looks like this:
- /django/db/models/query.py", line 335, in get\n num = len(clone)\n',
- /django/db/models/query.py", line 80, in len\n self._result_cache = list(self.iterator())\n',
- /django/db/models/query.py", line 273, in iterator\n only_load=only_load)\n',
- /django/db/models/query.py", line 1279, in get_cached_row\n setattr(rel_obj, rel_field.attname, getattr(obj, rel_field.attname))\n',
And my error is of this format: AttributeError: "'Parent2' object has no attribute 'Parent1Attribute'". This section of get_cached_row clearly assumes it is dealing with single inheritance, whereas in my case 'obj' is not an instance of 'rel_model'.
comment:9 by , 13 years ago
Cc: | added |
---|---|
UI/UX: | unset |
comment:10 by , 13 years ago
I've created a couple tests. The version with select_related fails, and the version without doesn't.
Description: If Child1 has a OneToOneField to Parent1 and if Child1 is a subclass of Parent2, doing Parent1.objects.select_related('child1') will produce this traceback:
====================================================================== ERROR: test_subclass_with_select_related (modeltests.select_related.tests.SelectRelatedTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/david/Projects/django-trunk/tests/modeltests/select_related/tests.py", line 171, in test_subclass_with_select_related p = Parent1.objects.select_related('child1').get(name="Parent1") File "/Users/david/Projects/django-trunk/django/db/models/query.py", line 361, in get num = len(clone) File "/Users/david/Projects/django-trunk/django/db/models/query.py", line 85, in __len__ self._result_cache = list(self.iterator()) File "/Users/david/Projects/django-trunk/django/db/models/query.py", line 294, in iterator offset=len(aggregate_select)) File "/Users/david/Projects/django-trunk/django/db/models/query.py", line 1420, in get_cached_row setattr(rel_obj, rel_field.attname, getattr(obj, rel_field.attname)) AttributeError: 'Parent1' object has no attribute 'subname' ----------------------------------------------------------------------
The same issue occurs if you subclass from both Parent1 and Parent2, but I wanted to demonstrate that there is still a problem with just a single subclass.
comment:11 by , 13 years ago
Easy pickings: | set |
---|---|
Has patch: | set |
I've attached a patch that resolves the issue. Included are updated tests that check for the following:
Child1 is a subclass of both Parent1 and Parent2; Parent1.objects.select_related('child1') should not throw an error (see traceback in comment:10).
Child2 is a subclass of Parent1 and has a OneToOneField to Parent2; doing Parent2.objects.select_related('child2') should not throw an error (same traceback).
comment:12 by , 13 years ago
Patch needs improvement: | set |
---|
I've encountered an issue with my patch. If Child1 is a subclass of Parent1 and Child2 is a subclass of both Parent1 and Parent2, doing a Parent1.objects.select_related('child1', 'child2') will not return any results for Child1 because an INNER JOIN is being used with Parent2.
So, I need to figure out how to force it to a LEFT OUTER join in this instance. At this point I might just try a different approach for my project.
by , 13 years ago
Attachment: | select_related_subclass_patch_1.3.X.diff added |
---|
Tests and patch (1.3.X)
by , 13 years ago
Attachment: | select_related_subclass_patch_1.2.X.diff added |
---|
Tests and patch (1.2.X)
comment:13 by , 13 years ago
Patch needs improvement: | unset |
---|
Ok, I've created an additional test for the last issue I found and also figured out how to resolve it.
This patch should be ready for review.
comment:14 by , 13 years ago
Cc: | added |
---|
comment:15 by , 12 years ago
Cc: | added |
---|
comment:16 by , 12 years ago
Cc: | added |
---|---|
Version: | 1.2 → master |
I've applied the select_related_subclass_patch.diff and resolved two merge conflicts against latest master branch. Tests passed.
comment:17 by , 12 years ago
Easy pickings: | unset |
---|---|
Triage Stage: | Accepted → Ready for checkin |
Updated patch at: https://github.com/akaariai/django/commit/7ed2c5f16caa9c413002c6d9c120c657bd37fc96
I think this one is now ready for commit.
I added reference to the model from options, that is SomeM == SomeM._meta.model. This seems useful and is innocent as far as cyclic references go. There is already a cyclic reference by SomeM._meta.local_fields[0].model == SomeM.
For the record, I don't think this one was exactly easy pickings...
comment:18 by , 12 years ago
I updated the work, and fixed a couple of issues affecting longer inheritance chains. Work available here: https://github.com/akaariai/django/commits/ticket_13781
The problem here is that when you fix one issue, you find two more broken issues. See added @expectedFailure tests. I think I will just go with the approach in the above branch and be done with this ticket. Fixing the @expectedFailure issues will lead to larger refactorings than what I am ready to do currently.
comment:19 by , 12 years ago
I rebased the ticket_13781 branch for final commit. As the commit is somewhat complex there is also somewhat big risk of regressions so I will not backpatch this to 1.5, at least not initially. The cases where this patch is needed aren't that common.
comment:20 by , 12 years ago
Resolution: | → fixed |
---|---|
Status: | reopened → closed |
comment:21 by , 12 years ago
The commit above didn't contain some important lines in get_cached_row(), fixed in 1194a9699932088385f9f88869be28a251597f45.
comment:23 by , 12 years ago
FWIW f51e409a5fb34020e170494320a421503689aea0 also fixed #16572 for me.
comment:24 by , 12 years ago
Cc: | added |
---|
comment:25 by , 12 years ago
Cc: | removed |
---|
OK, I will see about adding the tests of #16572. I am planning of committing a couple more ORM fixes and then go through all the ORM tickets and close those which aren't relevant any more.
I am not yet sure if I should backpatch this to 1.5. The problem is that the ORM is somewhat fragile due to all the different query combinations and thus I don't feel too comfortable about backpatching...
comment:26 by , 12 years ago
Alright, didn't want to push you to backpatch, just wanted to know if you were planing to.
Good work on those patches!
comment:28 by , 12 years ago
Cc: | removed |
---|
Multiple inheritance of models isn't officially supported (see #12002, #10808, amongst others). It *may* work under certain conditions, but it's certainly not guaranteed to work.
The closest you'll be able to get is to do single inheritance, then use a OneToOne field to point at the second class.
Closing wontfix since it's a bug in a configuration that we don't support.