Browse Source

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.
Simon Charette 9 years ago
parent
commit
5b980897f2

+ 1 - 1
django/db/models/deletion.py

@@ -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
django/db/models/options.py

@@ -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
docs/releases/1.10.txt

@@ -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
tests/model_meta/models.py

@@ -101,6 +101,10 @@ class ProxyPerson(Person):
         proxy = True
 
 
+class PersonThroughProxySubclass(ProxyPerson):
+    pass
+
+
 class Relating(models.Model):
 
     # ForeignKey to BasePerson

+ 87 - 1
tests/model_meta/results.py

@@ -1,4 +1,6 @@
-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),

+ 8 - 2
tests/model_meta/tests.py

@@ -111,7 +111,10 @@ class RelatedObjectsTests(OptionsBaseTests):
                 for field in model._meta.get_fields()
                 if field.auto_created and not field.concrete
             ]
-            self.assertEqual(self._map_related_query_names(objects), expected)
+            self.assertEqual(
+                sorted(self._map_related_query_names(objects), key=self.key_name),
+                sorted(expected, key=self.key_name),
+            )
 
     def test_related_objects_local(self):
         result_key = 'get_all_related_objects_with_model_local'
@@ -121,7 +124,10 @@ class RelatedObjectsTests(OptionsBaseTests):
                 for field in model._meta.get_fields(include_parents=False)
                 if field.auto_created and not field.concrete
             ]
-            self.assertEqual(self._map_related_query_names(objects), expected)
+            self.assertEqual(
+                sorted(self._map_related_query_names(objects), key=self.key_name),
+                sorted(expected, key=self.key_name),
+            )
 
     def test_related_objects_include_hidden(self):
         result_key = 'get_all_related_objects_with_model_hidden'