Loading django/db/models/fields/related.py +6 −15 Original line number Diff line number Diff line Loading @@ -40,7 +40,7 @@ from .reverse_related import ( RECURSIVE_RELATIONSHIP_CONSTANT = 'self' def resolve_relation(scope_model, relation, resolve_recursive_relationship=True): def resolve_relation(scope_model, relation): """ Transform relation into a model or fully-qualified model string of the form "app_label.ModelName", relative to scope_model. Loading @@ -55,10 +55,11 @@ def resolve_relation(scope_model, relation, resolve_recursive_relationship=True) """ # Check for recursive relations if relation == RECURSIVE_RELATIONSHIP_CONSTANT: if resolve_recursive_relationship: relation = scope_model # Look for an "app.Model" relation elif isinstance(relation, six.string_types) and '.' not in relation: if isinstance(relation, six.string_types): if "." not in relation: relation = "%s.%s" % (scope_model._meta.app_label, relation) return relation Loading Loading @@ -303,11 +304,6 @@ 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): """ Loading Loading @@ -1584,11 +1580,6 @@ 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)) Loading docs/ref/models/fields.txt +29 −0 Original line number Diff line number Diff line Loading @@ -1155,6 +1155,35 @@ you can use the name of the model, rather than the model object itself:: # ... pass Relationships defined this way on :ref:`abstract models <abstract-base-classes>` are resolved when the model is subclassed as a concrete model and are not relative to the abstract model's ``app_label``: .. snippet:: :filename: products/models.py from django.db import models class AbstractCar(models.Model): manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE) class Meta: abstract = True .. snippet:: :filename: production/models.py from django.db import models from products.models import AbstractCar class Manufacturer(models.Model): pass class Car(AbstractCar): pass # Car.manufacturer will point to `production.Manufacturer` here. To refer to models defined in another application, you can explicitly specify a model with the full application label. For example, if the ``Manufacturer`` model above is defined in another application called ``production``, you'd Loading docs/releases/1.9.3.txt +4 −0 Original line number Diff line number Diff line Loading @@ -52,3 +52,7 @@ Bugfixes * Prevented ``ContentTypeManager`` instances from sharing their cache (:ticket:`26286`). * Reverted a change in Django 1.9.2 (:ticket:`25858`) that prevented relative lazy relationships defined on abstract models to be resolved according to their concrete model's ``app_label`` (:ticket:`26186`). tests/model_fields/tests.py +38 −28 Original line number Diff line number Diff line Loading @@ -251,25 +251,30 @@ class ForeignKeyTests(test.TestCase): 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: apps = test_apps app_label = 'model_fields' abstract = True def assert_app_model_resolved(label): class Refered(models.Model): class Meta: apps = test_apps app_label = label class ConcreteReferent(AbstractReferent): class Meta: apps = test_apps app_label = 'tests' app_label = label self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) assert_app_model_resolved('model_fields') assert_app_model_resolved('tests') class ManyToManyFieldTests(test.SimpleTestCase): def test_abstract_model_pending_operations(self): Loading @@ -295,34 +300,39 @@ class ManyToManyFieldTests(test.SimpleTestCase): def test_abstract_model_app_relative_foreign_key(self): test_apps = Apps(['model_fields', 'model_fields.tests']) class Refered(models.Model): class AbstractReferent(models.Model): reference = models.ManyToManyField('Refered', through='Through') class Meta: apps = test_apps app_label = 'model_fields' abstract = True class Through(models.Model): refered = models.ForeignKey('Refered', on_delete=models.CASCADE) referent = models.ForeignKey('tests.ConcreteReferent', on_delete=models.CASCADE) def assert_app_model_resolved(label): class Refered(models.Model): class Meta: apps = test_apps app_label = 'model_fields' app_label = label class AbstractReferent(models.Model): reference = models.ManyToManyField('Refered', through='Through') class Through(models.Model): refered = models.ForeignKey('Refered', on_delete=models.CASCADE) referent = models.ForeignKey('ConcreteReferent', on_delete=models.CASCADE) class Meta: app_label = 'model_fields' abstract = True apps = test_apps app_label = label class ConcreteReferent(AbstractReferent): class Meta: apps = test_apps app_label = 'tests' app_label = label self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) self.assertEqual(ConcreteReferent.reference.through, Through) assert_app_model_resolved('model_fields') assert_app_model_resolved('tests') class TextFieldTests(test.TestCase): def test_to_python(self): Loading Loading
django/db/models/fields/related.py +6 −15 Original line number Diff line number Diff line Loading @@ -40,7 +40,7 @@ from .reverse_related import ( RECURSIVE_RELATIONSHIP_CONSTANT = 'self' def resolve_relation(scope_model, relation, resolve_recursive_relationship=True): def resolve_relation(scope_model, relation): """ Transform relation into a model or fully-qualified model string of the form "app_label.ModelName", relative to scope_model. Loading @@ -55,10 +55,11 @@ def resolve_relation(scope_model, relation, resolve_recursive_relationship=True) """ # Check for recursive relations if relation == RECURSIVE_RELATIONSHIP_CONSTANT: if resolve_recursive_relationship: relation = scope_model # Look for an "app.Model" relation elif isinstance(relation, six.string_types) and '.' not in relation: if isinstance(relation, six.string_types): if "." not in relation: relation = "%s.%s" % (scope_model._meta.app_label, relation) return relation Loading Loading @@ -303,11 +304,6 @@ 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): """ Loading Loading @@ -1584,11 +1580,6 @@ 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)) Loading
docs/ref/models/fields.txt +29 −0 Original line number Diff line number Diff line Loading @@ -1155,6 +1155,35 @@ you can use the name of the model, rather than the model object itself:: # ... pass Relationships defined this way on :ref:`abstract models <abstract-base-classes>` are resolved when the model is subclassed as a concrete model and are not relative to the abstract model's ``app_label``: .. snippet:: :filename: products/models.py from django.db import models class AbstractCar(models.Model): manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE) class Meta: abstract = True .. snippet:: :filename: production/models.py from django.db import models from products.models import AbstractCar class Manufacturer(models.Model): pass class Car(AbstractCar): pass # Car.manufacturer will point to `production.Manufacturer` here. To refer to models defined in another application, you can explicitly specify a model with the full application label. For example, if the ``Manufacturer`` model above is defined in another application called ``production``, you'd Loading
docs/releases/1.9.3.txt +4 −0 Original line number Diff line number Diff line Loading @@ -52,3 +52,7 @@ Bugfixes * Prevented ``ContentTypeManager`` instances from sharing their cache (:ticket:`26286`). * Reverted a change in Django 1.9.2 (:ticket:`25858`) that prevented relative lazy relationships defined on abstract models to be resolved according to their concrete model's ``app_label`` (:ticket:`26186`).
tests/model_fields/tests.py +38 −28 Original line number Diff line number Diff line Loading @@ -251,25 +251,30 @@ class ForeignKeyTests(test.TestCase): 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: apps = test_apps app_label = 'model_fields' abstract = True def assert_app_model_resolved(label): class Refered(models.Model): class Meta: apps = test_apps app_label = label class ConcreteReferent(AbstractReferent): class Meta: apps = test_apps app_label = 'tests' app_label = label self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) assert_app_model_resolved('model_fields') assert_app_model_resolved('tests') class ManyToManyFieldTests(test.SimpleTestCase): def test_abstract_model_pending_operations(self): Loading @@ -295,34 +300,39 @@ class ManyToManyFieldTests(test.SimpleTestCase): def test_abstract_model_app_relative_foreign_key(self): test_apps = Apps(['model_fields', 'model_fields.tests']) class Refered(models.Model): class AbstractReferent(models.Model): reference = models.ManyToManyField('Refered', through='Through') class Meta: apps = test_apps app_label = 'model_fields' abstract = True class Through(models.Model): refered = models.ForeignKey('Refered', on_delete=models.CASCADE) referent = models.ForeignKey('tests.ConcreteReferent', on_delete=models.CASCADE) def assert_app_model_resolved(label): class Refered(models.Model): class Meta: apps = test_apps app_label = 'model_fields' app_label = label class AbstractReferent(models.Model): reference = models.ManyToManyField('Refered', through='Through') class Through(models.Model): refered = models.ForeignKey('Refered', on_delete=models.CASCADE) referent = models.ForeignKey('ConcreteReferent', on_delete=models.CASCADE) class Meta: app_label = 'model_fields' abstract = True apps = test_apps app_label = label class ConcreteReferent(AbstractReferent): class Meta: apps = test_apps app_label = 'tests' app_label = label self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) self.assertEqual(ConcreteReferent.reference.through, Through) assert_app_model_resolved('model_fields') assert_app_model_resolved('tests') class TextFieldTests(test.TestCase): def test_to_python(self): Loading