Kaydet (Commit) 5b980897 authored tarafından Simon Charette's avatar Simon Charette

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.
üst 211486f3
......@@ -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)
)
......
......@@ -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.
......
......@@ -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
~~~~~~~~~~~~~
......
......@@ -101,6 +101,10 @@ class ProxyPerson(Person):
proxy = True
class PersonThroughProxySubclass(ProxyPerson):
pass
class Relating(models.Model):
# ForeignKey to BasePerson
......
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),
......@@ -358,6 +379,9 @@ TEST_RESULTS = {
('relating_baseperson', None),
),
Relation: (
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', 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),
......@@ -442,6 +505,9 @@ TEST_RESULTS = {
('relating_baseperson', None),
),
Relation: (
('+', None),
('+', None),
('+', None),
('+', None),
('+', None),
('+', 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),
......
......@@ -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'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment