Commit 6c9f37ea authored by Simon Charette's avatar Simon Charette
Browse files

Fixed #18012 -- Propagated reverse foreign keys from proxy to concrete models.

Thanks to Anssi for the review.
parent c8f091f5
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -684,7 +684,7 @@ class ForeignObject(RelatedField):
        # Internal FK's - i.e., those with a related name ending with '+' -
        # and swapped models don't get a related descriptor.
        if not self.remote_field.is_hidden() and not related.related_model._meta.swapped:
            setattr(cls, related.get_accessor_name(), self.related_accessor_class(related))
            setattr(cls._meta.concrete_model, related.get_accessor_name(), self.related_accessor_class(related))
            # While 'limit_choices_to' might be a callable, simply pass
            # it along for later - this is too early because it's still
            # model load time.
+1 −1
Original line number Diff line number Diff line
@@ -198,7 +198,7 @@ class ForwardManyToOneDescriptor(object):
                'Cannot assign None: "%s.%s" does not allow null values.' %
                (instance._meta.object_name, self.field.name)
            )
        elif value is not None and not isinstance(value, self.field.remote_field.model):
        elif value is not None and not isinstance(value, self.field.remote_field.model._meta.concrete_model):
            raise ValueError(
                'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
                    value,
+10 −3
Original line number Diff line number Diff line
@@ -552,15 +552,20 @@ class Options(object):
        is set as a property on every model.
        """
        related_objects_graph = defaultdict(list)
        # Map of concrete models to all options of models it represents.
        # Including its options and all its proxy model ones.
        concrete_model_classes = defaultdict(list)

        all_models = self.apps.get_models(include_auto_created=True)
        for model in all_models:
            opts = model._meta
            concrete_model_classes[opts.concrete_model].append(opts)
            # Abstract model's fields are copied to child models, hence we will
            # see the fields from the child models.
            if model._meta.abstract:
            if opts.abstract:
                continue
            fields_with_relations = (
                f for f in model._meta._get_fields(reverse=False, include_parents=False)
                f for f in opts._get_fields(reverse=False, include_parents=False)
                if f.is_relation and f.related_model is not None
            )
            for f in fields_with_relations:
@@ -573,7 +578,9 @@ class Options(object):
            # __dict__ takes precedence over a data descriptor (such as
            # @cached_property). This means that the _meta._relation_tree is
            # only called if related_objects is not in __dict__.
            related_objects = related_objects_graph[model._meta]
            related_objects = list(chain.from_iterable(
                related_objects_graph[opts] for opts in concrete_model_classes[model]
            ))
            model._meta.__dict__['_relation_tree'] = related_objects
        # It seems it is possible that self is not in all_models, so guard
        # against that with default for get().
+5 −1
Original line number Diff line number Diff line
@@ -163,7 +163,11 @@ Migrations
Models
^^^^^^

* ...
* Reverse foreign keys from proxy models are now propagated to their
  concrete class. The reverse relation attached by a
  :class:`~django.db.models.ForeignKey` pointing to a proxy model is now
  accessible as a descriptor on the proxied model class and may be referenced in
  queryset filtering.

Requests and Responses
^^^^^^^^^^^^^^^^^^^^^^
+1 −1
Original line number Diff line number Diff line
@@ -113,7 +113,7 @@ class Relating(models.Model):

    # ForeignKey to ProxyPerson
    proxyperson = models.ForeignKey(ProxyPerson, models.CASCADE, related_name='relating_proxyperson')
    proxyperson_hidden = models.ForeignKey(ProxyPerson, models.CASCADE, related_name='+')
    proxyperson_hidden = models.ForeignKey(ProxyPerson, models.CASCADE, related_name='relating_proxyperson_hidden+')

    # ManyToManyField to BasePerson
    basepeople = models.ManyToManyField(BasePerson, related_name='relating_basepeople')
Loading