mirror of
https://github.com/django/django.git
synced 2025-07-19 17:19:12 +00:00
[1.9.x] Fixed #25858 -- Bound abstract model application relative relationships.
Thanks to Karl Hobley for the report and Markus, Shai, Aymeric for their input and Tim for the review. Backport of bc7d201bdbaeac14a49f51a9ef292d6312b4c45e from master
This commit is contained in:
parent
76b4015ea9
commit
27ef6403c8
@ -39,7 +39,7 @@ from .reverse_related import (
|
||||
RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
|
||||
|
||||
|
||||
def resolve_relation(scope_model, relation):
|
||||
def resolve_relation(scope_model, relation, resolve_recursive_relationship=True):
|
||||
"""
|
||||
Transform relation into a model or fully-qualified model string of the form
|
||||
"app_label.ModelName", relative to scope_model.
|
||||
@ -54,12 +54,11 @@ def resolve_relation(scope_model, relation):
|
||||
"""
|
||||
# Check for recursive relations
|
||||
if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
|
||||
relation = scope_model
|
||||
|
||||
if resolve_recursive_relationship:
|
||||
relation = scope_model
|
||||
# Look for an "app.Model" relation
|
||||
if isinstance(relation, six.string_types):
|
||||
if "." not in relation:
|
||||
relation = "%s.%s" % (scope_model._meta.app_label, relation)
|
||||
elif isinstance(relation, six.string_types) and '.' not in relation:
|
||||
relation = "%s.%s" % (scope_model._meta.app_label, relation)
|
||||
|
||||
return relation
|
||||
|
||||
@ -306,6 +305,11 @@ class RelatedField(Field):
|
||||
field.remote_field.model = related
|
||||
field.do_related_class(related, model)
|
||||
lazy_related_operation(resolve_related_class, cls, self.remote_field.model, field=self)
|
||||
else:
|
||||
# Bind a lazy reference to the app in which the model is defined.
|
||||
self.remote_field.model = resolve_relation(
|
||||
cls, self.remote_field.model, resolve_recursive_relationship=False
|
||||
)
|
||||
|
||||
def get_forward_related_filter(self, obj):
|
||||
"""
|
||||
@ -1577,6 +1581,11 @@ class ManyToManyField(RelatedField):
|
||||
lazy_related_operation(resolve_through_model, cls, self.remote_field.through, field=self)
|
||||
elif not cls._meta.swapped:
|
||||
self.remote_field.through = create_many_to_many_intermediary_model(self, cls)
|
||||
else:
|
||||
# Bind a lazy reference to the app in which the model is defined.
|
||||
self.remote_field.through = resolve_relation(
|
||||
cls, self.remote_field.through, resolve_recursive_relationship=False
|
||||
)
|
||||
|
||||
# Add the descriptor for the m2m relation.
|
||||
setattr(cls, self.name, ManyToManyDescriptor(self.remote_field, reverse=False))
|
||||
|
@ -30,3 +30,8 @@ Bugfixes
|
||||
``db_index=True`` or ``unique=True`` to a ``CharField`` or ``TextField`` that
|
||||
already had the other specified, or when removing one of them from a field
|
||||
that had both (:ticket:`26034`).
|
||||
|
||||
* Fixed a regression where defining a relation on an abstract model's field
|
||||
using a string model name without an app_label no longer resolved that
|
||||
reference to the abstract model's app if using that model in another
|
||||
application (:ticket`25858`).
|
@ -7,6 +7,7 @@ from decimal import Decimal
|
||||
|
||||
from django import forms, test
|
||||
from django.apps import apps
|
||||
from django.apps.registry import Apps
|
||||
from django.core import checks, validators
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError, connection, models, transaction
|
||||
@ -247,6 +248,28 @@ class ForeignKeyTests(test.TestCase):
|
||||
"Pending lookup added for a foreign key on an abstract model"
|
||||
)
|
||||
|
||||
def test_abstract_model_app_relative_foreign_key(self):
|
||||
test_apps = Apps(['model_fields', 'model_fields.tests'])
|
||||
|
||||
class Refered(models.Model):
|
||||
class Meta:
|
||||
apps = test_apps
|
||||
app_label = 'model_fields'
|
||||
|
||||
class AbstractReferent(models.Model):
|
||||
reference = models.ForeignKey('Refered', on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
app_label = 'model_fields'
|
||||
abstract = True
|
||||
|
||||
class ConcreteReferent(AbstractReferent):
|
||||
class Meta:
|
||||
apps = test_apps
|
||||
app_label = 'tests'
|
||||
|
||||
self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
|
||||
|
||||
|
||||
class ManyToManyFieldTests(test.SimpleTestCase):
|
||||
def test_abstract_model_pending_operations(self):
|
||||
@ -269,6 +292,37 @@ class ManyToManyFieldTests(test.SimpleTestCase):
|
||||
"Pending lookup added for a many-to-many field on an abstract model"
|
||||
)
|
||||
|
||||
def test_abstract_model_app_relative_foreign_key(self):
|
||||
test_apps = Apps(['model_fields', 'model_fields.tests'])
|
||||
|
||||
class Refered(models.Model):
|
||||
class Meta:
|
||||
apps = test_apps
|
||||
app_label = 'model_fields'
|
||||
|
||||
class Through(models.Model):
|
||||
refered = models.ForeignKey('Refered', on_delete=models.CASCADE)
|
||||
referent = models.ForeignKey('tests.ConcreteReferent', on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
apps = test_apps
|
||||
app_label = 'model_fields'
|
||||
|
||||
class AbstractReferent(models.Model):
|
||||
reference = models.ManyToManyField('Refered', through='Through')
|
||||
|
||||
class Meta:
|
||||
app_label = 'model_fields'
|
||||
abstract = True
|
||||
|
||||
class ConcreteReferent(AbstractReferent):
|
||||
class Meta:
|
||||
apps = test_apps
|
||||
app_label = 'tests'
|
||||
|
||||
self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
|
||||
self.assertEqual(ConcreteReferent.reference.through, Through)
|
||||
|
||||
|
||||
class TextFieldTests(test.TestCase):
|
||||
def test_to_python(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user