#33137 closed New feature (needsinfo)
Decouple Field.unique from select_related
Reported by: | Markus Holtermann | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | |
Severity: | Normal | Keywords: | |
Cc: | Simon Charette | Triage Stage: | Unreviewed |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
When inheriting from a OneToOneField
that automatically adds additional constraints to a model, the object-level relation might be a one-to-one relation, while the underlying implementation is still a many-to-one.
Let's say you have two models A
and B
where A.rel = MyOneToOneField(B)
and A.deleted = BooleanField()
. In a regular OneToOneField
there would now be a unique index on A.rel
. However, by adding a unique constraint to A._meta.constraints
over rel
with a condition on deleted=False
, rel
only needs to be unique among the "undeleted" objects. Doing so is possible by forcing MyOneToOneField.unique=False
(otherwise migrations create a constraint). However, this will mean, SQLCompiler.get_related_selections()
fails when trying to do B.objects.select_related("a")
, since a
is not a valid field. That is because SQLCompiler.get_related_selections._get_field_choices()
uses f.field.unique
instead of (f.field.many_to_one or f.field.one_to_one)
, I think. Because, essentially, opts.related_objects
is build based on those (many|one)_to_(many|one)
field attributes.
I think, what would fix the behavior, could be something like this:
class SQLCompiler: def get_related_selections(self, select, opts=None, root_alias=None, cur_depth=1, requested=None, restricted=None): def _get_field_choices(): direct_choices = (f.name for f in opts.fields if f.is_relation) reverse_choices = ( f.field.related_query_name() for f in opts.related_objects if (f.field.many_to_one or f.field.one_to_one) ) return chain(direct_choices, reverse_choices, self.query._filtered_relations)
Change History (4)
comment:1 by , 3 years ago
Description: | modified (diff) |
---|
comment:2 by , 3 years ago
Cc: | added |
---|---|
Triage Stage: | Unreviewed → Accepted |
comment:3 by , 2 years ago
Resolution: | → needsinfo |
---|---|
Status: | new → closed |
I'm not sure how actionable this ticket is anymore. The proposed change to _get_field_choices
won't actually solve anything as this function is only used to provide a more helpful error message when passing an invalid value to select_related
.
Without providing an implementation that is known to be faulty it's hard to understand which adjustments should even be made as there are examples in the wild of other to one fields working properly by subclassing ForeignObject
directly.
Closing as needsinfo pending a demonstration of an implementation that is problematic. Hopefully documenting ForeignObject
will make it easier to write proper implementation for this (#27617).
comment:4 by , 2 years ago
Triage Stage: | Accepted → Unreviewed |
---|
This use case looks very similar to the django-reverse-unique project by Anssi which didn't require any adjustments to the ORM and properly supported
select_related
.I assume you're trying to create a subclass of
OneToOneField
that allows specifying acondition
and avoids having to add all of this boilerplate?In all cases I'm not against changing the code to use the documented
(many|one)_to_(many|one)
attributes instead ofunique
assuming we write a proper regression tests for them.