#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 , 4 years ago
| Description: | modified (diff) |
|---|
comment:2 by , 4 years ago
| Cc: | added |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
comment:3 by , 3 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 , 3 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
OneToOneFieldthat allows specifying aconditionand 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 ofuniqueassuming we write a proper regression tests for them.