Commit 5b980897 authored by Simon Charette's avatar Simon Charette
Browse files

Refs #18012 -- Made proxy and concrete model reverse fields consistent.

Prior to this change proxy models reverse fields didn't include the
reverse fields pointing to their concrete model.
parent 211486f3
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -55,7 +55,7 @@ def get_candidate_relations_to_delete(opts):
    # The candidate relations are the ones that come from N-1 and 1-1 relations.
    # N-N  (i.e., many-to-many) relations aren't candidates for deletion.
    return (
        f for f in opts.concrete_model._meta.get_fields(include_hidden=True)
        f for f in opts.get_fields(include_hidden=True)
        if f.auto_created and not f.concrete and (f.one_to_one or f.one_to_many)
    )

+4 −10
Original line number Diff line number Diff line
@@ -552,14 +552,10 @@ 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 opts.abstract:
@@ -570,7 +566,7 @@ class Options(object):
            )
            for f in fields_with_relations:
                if not isinstance(f.remote_field.model, six.string_types):
                    related_objects_graph[f.remote_field.model._meta].append(f)
                    related_objects_graph[f.remote_field.model._meta.concrete_model._meta].append(f)

        for model in all_models:
            # Set the relation_tree using the internal __dict__. In this way
@@ -578,9 +574,7 @@ 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 = list(chain.from_iterable(
                related_objects_graph[opts] for opts in concrete_model_classes[model]
            ))
            related_objects = related_objects_graph[model._meta.concrete_model._meta]
            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().
@@ -674,10 +668,10 @@ class Options(object):
                for obj in parent._meta._get_fields(
                        forward=forward, reverse=reverse, include_parents=include_parents,
                        include_hidden=include_hidden, seen_models=seen_models):
                    if hasattr(obj, 'parent_link') and obj.parent_link:
                    if getattr(obj, 'parent_link', False) and obj.model != self.concrete_model:
                        continue
                    fields.append(obj)
        if reverse:
        if reverse and not self.proxy:
            # Tree is computed once and cached until the app cache is expired.
            # It is composed of a list of fields pointing to the current model
            # from other models.
+9 −0
Original line number Diff line number Diff line
@@ -238,6 +238,15 @@ But it didn't prohibit nested non-relation fields as it does now::
    ...
    FieldError: Non-relational field given in select_related: 'name'

``_meta.get_fields()`` returns consistent reverse fields for proxy models
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Before Django 1.10, the :meth:`~django.db.models.options.Options.get_fields`
method returned different reverse fields when called on a proxy model compared
to its proxied concrete class. This inconsistency was fixed by returning the
full set of fields pointing to a concrete class or one of its proxies in both
cases.

Miscellaneous
~~~~~~~~~~~~~

+4 −0
Original line number Diff line number Diff line
@@ -101,6 +101,10 @@ class ProxyPerson(Person):
        proxy = True


class PersonThroughProxySubclass(ProxyPerson):
    pass


class Relating(models.Model):

    # ForeignKey to BasePerson
+87 −1
Original line number Diff line number Diff line
from .models import AbstractPerson, BasePerson, Person, Relating, Relation
from .models import (
    AbstractPerson, BasePerson, Person, ProxyPerson, Relating, Relation,
)

TEST_RESULTS = {
    'get_all_field_names': {
@@ -329,11 +331,30 @@ TEST_RESULTS = {
            ('Relating_people_hidden+', None),
            ('followers_concrete', None),
            ('friends_inherited_rel_+', None),
            ('personthroughproxysubclass', None),
            ('relating_people', None),
            ('relating_person', None),
            ('relating_proxyperson', None),
            ('relating_proxyperson_hidden+', None),
        ),
        ProxyPerson: (
            ('+', Person),
            ('_relating_people_hidden_+', Person),
            ('Person_following_inherited+', Person),
            ('Person_following_inherited+', Person),
            ('Person_friends_inherited+', Person),
            ('Person_friends_inherited+', Person),
            ('Person_m2m_inherited+', Person),
            ('Relating_people+', Person),
            ('Relating_people_hidden+', Person),
            ('followers_concrete', Person),
            ('friends_inherited_rel_+', Person),
            ('personthroughproxysubclass', Person),
            ('relating_people', Person),
            ('relating_person', Person),
            ('relating_proxyperson', Person),
            ('relating_proxyperson_hidden+', Person),
        ),
        BasePerson: (
            ('+', None),
            ('_relating_basepeople_hidden_+', None),
@@ -366,6 +387,9 @@ TEST_RESULTS = {
            ('+', None),
            ('+', None),
            ('+', None),
            ('+', None),
            ('+', None),
            ('+', None),
            ('BasePerson_m2m_abstract+', None),
            ('BasePerson_m2m_base+', None),
            ('Person_m2m_inherited+', None),
@@ -411,6 +435,7 @@ TEST_RESULTS = {
            ('friends_abstract_rel_+', BasePerson),
            ('friends_base_rel_+', BasePerson),
            ('friends_inherited_rel_+', None),
            ('personthroughproxysubclass', None),
            ('relating_basepeople', BasePerson),
            ('relating_baseperson', BasePerson),
            ('relating_people', None),
@@ -418,6 +443,44 @@ TEST_RESULTS = {
            ('relating_proxyperson', None),
            ('relating_proxyperson_hidden+', None),
        ),
        ProxyPerson: (
            ('+', BasePerson),
            ('+', Person),
            ('_relating_basepeople_hidden_+', BasePerson),
            ('_relating_people_hidden_+', Person),
            ('BasePerson_following_abstract+', BasePerson),
            ('BasePerson_following_abstract+', BasePerson),
            ('BasePerson_following_base+', BasePerson),
            ('BasePerson_following_base+', BasePerson),
            ('BasePerson_friends_abstract+', BasePerson),
            ('BasePerson_friends_abstract+', BasePerson),
            ('BasePerson_friends_base+', BasePerson),
            ('BasePerson_friends_base+', BasePerson),
            ('BasePerson_m2m_abstract+', BasePerson),
            ('BasePerson_m2m_base+', BasePerson),
            ('Person_following_inherited+', Person),
            ('Person_following_inherited+', Person),
            ('Person_friends_inherited+', Person),
            ('Person_friends_inherited+', Person),
            ('Person_m2m_inherited+', Person),
            ('Relating_basepeople+', BasePerson),
            ('Relating_basepeople_hidden+', BasePerson),
            ('Relating_people+', Person),
            ('Relating_people_hidden+', Person),
            ('followers_abstract', BasePerson),
            ('followers_base', BasePerson),
            ('followers_concrete', Person),
            ('friends_abstract_rel_+', BasePerson),
            ('friends_base_rel_+', BasePerson),
            ('friends_inherited_rel_+', Person),
            ('personthroughproxysubclass', Person),
            ('relating_basepeople', BasePerson),
            ('relating_baseperson', BasePerson),
            ('relating_people', Person),
            ('relating_person', Person),
            ('relating_proxyperson', Person),
            ('relating_proxyperson_hidden+', Person),
        ),
        BasePerson: (
            ('+', None),
            ('_relating_basepeople_hidden_+', None),
@@ -450,6 +513,9 @@ TEST_RESULTS = {
            ('+', None),
            ('+', None),
            ('+', None),
            ('+', None),
            ('+', None),
            ('+', None),
            ('BasePerson_m2m_abstract+', None),
            ('BasePerson_m2m_base+', None),
            ('Person_m2m_inherited+', None),
@@ -467,10 +533,18 @@ TEST_RESULTS = {
    'get_all_related_objects_with_model_local': {
        Person: (
            ('followers_concrete', None),
            ('personthroughproxysubclass', None),
            ('relating_person', None),
            ('relating_people', None),
            ('relating_proxyperson', None),
        ),
        ProxyPerson: (
            ('followers_concrete', Person),
            ('personthroughproxysubclass', Person),
            ('relating_person', Person),
            ('relating_people', Person),
            ('relating_proxyperson', Person),
        ),
        BasePerson: (
            ('followers_abstract', None),
            ('followers_base', None),
@@ -497,10 +571,22 @@ TEST_RESULTS = {
            ('relating_baseperson', BasePerson),
            ('relating_basepeople', BasePerson),
            ('followers_concrete', None),
            ('personthroughproxysubclass', None),
            ('relating_person', None),
            ('relating_people', None),
            ('relating_proxyperson', None),
        ),
        ProxyPerson: (
            ('followers_abstract', BasePerson),
            ('followers_base', BasePerson),
            ('relating_baseperson', BasePerson),
            ('relating_basepeople', BasePerson),
            ('followers_concrete', Person),
            ('personthroughproxysubclass', Person),
            ('relating_person', Person),
            ('relating_people', Person),
            ('relating_proxyperson', Person),
        ),
        BasePerson: (
            ('followers_abstract', None),
            ('followers_base', None),
Loading