Kaydet (Commit) 6c9f37ea authored tarafından Simon Charette's avatar Simon Charette

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

Thanks to Anssi for the review.
üst c8f091f5
......@@ -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.
......
......@@ -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,
......
......@@ -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().
......
......@@ -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
^^^^^^^^^^^^^^^^^^^^^^
......
......@@ -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')
......
......@@ -331,6 +331,8 @@ TEST_RESULTS = {
('friends_inherited_rel_+', None),
('relating_people', None),
('relating_person', None),
('relating_proxyperson', None),
('relating_proxyperson_hidden+', None),
),
BasePerson: (
('+', None),
......@@ -413,6 +415,8 @@ TEST_RESULTS = {
('relating_baseperson', BasePerson),
('relating_people', None),
('relating_person', None),
('relating_proxyperson', None),
('relating_proxyperson_hidden+', None),
),
BasePerson: (
('+', None),
......@@ -465,6 +469,7 @@ TEST_RESULTS = {
('followers_concrete', None),
('relating_person', None),
('relating_people', None),
('relating_proxyperson', None),
),
BasePerson: (
('followers_abstract', None),
......@@ -494,6 +499,7 @@ TEST_RESULTS = {
('followers_concrete', None),
('relating_person', None),
('relating_people', None),
('relating_proxyperson', None),
),
BasePerson: (
('followers_abstract', None),
......
......@@ -158,7 +158,7 @@ class ProxyTrackerUser(TrackerUser):
@python_2_unicode_compatible
class Issue(models.Model):
summary = models.CharField(max_length=255)
assignee = models.ForeignKey(ProxyTrackerUser, models.CASCADE)
assignee = models.ForeignKey(ProxyTrackerUser, models.CASCADE, related_name='issues')
def __str__(self):
return ':'.join((self.__class__.__name__, self.summary,))
......
......@@ -6,7 +6,7 @@ from django.apps import apps
from django.contrib import admin
from django.contrib.auth.models import User as AuthUser
from django.contrib.contenttypes.models import ContentType
from django.core import checks, exceptions, management
from django.core import checks, management
from django.core.urlresolvers import reverse
from django.db import DEFAULT_DB_ALIAS, models
from django.db.models import signals
......@@ -332,14 +332,19 @@ class ProxyModelTests(TestCase):
self.assertEqual(resp.name, 'New South Wales')
def test_filter_proxy_relation_reverse(self):
tu = TrackerUser.objects.create(
name='Contributor', status='contrib')
with self.assertRaises(exceptions.FieldError):
TrackerUser.objects.filter(issue=None),
tu = TrackerUser.objects.create(name='Contributor', status='contrib')
ptu = ProxyTrackerUser.objects.get()
issue = Issue.objects.create(assignee=tu)
self.assertEqual(tu.issues.get(), issue)
self.assertEqual(ptu.issues.get(), issue)
self.assertQuerysetEqual(
ProxyTrackerUser.objects.filter(issue=None),
TrackerUser.objects.filter(issues=issue),
[tu], lambda x: x
)
self.assertQuerysetEqual(
ProxyTrackerUser.objects.filter(issues=issue),
[ptu], lambda x: x
)
def test_proxy_bug(self):
contributor = ProxyTrackerUser.objects.create(name='Contributor',
......
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