Opened 3 years ago

Last modified 19 months ago

#33137 closed New feature

Decouple Field.unique from select_related — at Version 1

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 Markus Holtermann)

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 (1)

comment:1 by Markus Holtermann, 3 years ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top