#26320 closed Cleanup/optimization (fixed)
Deprecate implicit OneToOneField parent_link
| Reported by: | Bertrand Bordage | Owned by: | nobody |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | 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
Suppose we have these models:
class Parent(Model): pass class Child(Parent): parent_ptr2 = OneToOneField(Parent, null=True, related_name='+')
Child should have two OneToOneFields, parent_ptr and parent_ptr2.
Unfortunately, Django has a critical issue where it merges these two fields into this:
parent_ptr2 = OneToOneField(Parent, primary_key=True, null=True, related_name='+')
And of course, a nullable primary key is impossible, so we can’t even do a migration: fields.E007 is raised.
But defining without multi-table inheritance exactly the same model – in terms of database definition – works just fine:
class WorkingModel(Model): parent_ptr = OneToOneField(Parent, primary_key=True, related_name='+') parent_ptr2 = OneToOneField(Parent, null=True, related_name='+')
So multi-table inheritance is probably what causes this confusion.
This bug is present on all supported Django versions, from 1.8 to 1.9.3.
I guess this can be seen as a release blocker, there is no regression but this is quite critical (and blocking, in my case).
Change History (13)
comment:1 by , 10 years ago
comment:2 by , 10 years ago
We explicitly document that OneToOneField will only be used as the link back to the parent class iff parent_link=True.
I think the bug lies in ModelBase.__new__ during parent_link collection. The isinstance(field, OneToOneField) check should also make sure that field.remote_field.parent_link is truthy.
comment:3 by , 10 years ago
Yes, this workaround works :)
@charettes: I was about to write this, my investigations lead to the same! That’s the cause.
Thanks for the help, should I do a pull request?
comment:4 by , 10 years ago
The current behavior is tested. Checking field.remote_field.parent_link as suggested breaks that test and a few others.
comment:5 by , 10 years ago
It will raise FieldError: Local field … clashes with field of similar name from base class …, like with any other field.
Isn’t that more consistent, since parent_link == False? I know, it breaks backwards compatibility for this edge case, though.
follow-up: 7 comment:6 by , 10 years ago
I'm okay with requiring parent_link=True but could we let it go through the deprecation cycle?
comment:7 by , 10 years ago
Replying to timgraham:
I'm okay with requiring
parent_link=Truebut could we let it go through the deprecation cycle?
In the light of the existing tests I suppose we should just to be extra safe even if the test model's check() method returns multiple errors and the model is simply unusable with the ORM as field.primary_key is True and field.remote_field.parent_link is False.
comment:8 by , 10 years ago
@timgraham: Sure! Since this is a rare use case, do you think it could be a RemovedInDjango110Warning introduced in 1.9.4? Or maybe it’s too soon?
comment:9 by , 10 years ago
| Summary: | Multi-table inheritance pointer merged with another OneToOneField → Deprecate implicit OneToOneField parent_link |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
| Type: | Bug → Cleanup/optimization |
I don't see this as a critical issue that needs an accelerated deprecation.
comment:11 by , 10 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
The promotion of the
OneToOneFieldto aprimary_keyhappens in options.py. I didn't see a mention of the behavior in the multi-table inheritance docs but it seems to date to merge of the queryset refactor branch in 9c52d56f6f8a9cdafb231adf9f4110473099c9b5.I don't know if we can change the behavior but we could at least document it.
Is there some problem defining your model like this:
class Child(Parent): parent_ptr = models.OneToOneField(Parent, primary_key=True, related_name='+') parent_ptr2 = models.OneToOneField(Parent, null=True, related_name='+')I didn't get any check errors in that case but I didn't test any queries.