Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #9638 - Added %(app_label)s to the related_name format string f…

…or abstract models.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12139 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit bacfe3f3e8638433976bf1b12f16a8d975970884 1 parent 1e955e0
@jezdez jezdez authored
View
2  AUTHORS
@@ -266,7 +266,7 @@ answer newbie questions, and generally made Django that much better:
kurtiss@meetro.com
Denis Kuzmichyov <kuzmichyov@gmail.com>
Panos Laganakos <panos.laganakos@gmail.com>
- lakin.wecker@gmail.com
+ Lakin Wecker <lakin@structuredabstraction.com>
Nick Lane <nick.lane.au@gmail.com>
Stuart Langridge <http://www.kryogenix.org/>
Paul Lanier <planier@google.com>
View
5 django/db/models/fields/related.py
@@ -94,7 +94,10 @@ def contribute_to_class(self, cls, name):
sup.contribute_to_class(cls, name)
if not cls._meta.abstract and self.rel.related_name:
- self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()}
+ self.rel.related_name = self.rel.related_name % {
+ 'class': cls.__name__.lower(),
+ 'app_label': cls._meta.app_label.lower(),
+ }
other = self.rel.to
if isinstance(other, basestring) or other._meta.pk is None:
View
47 docs/topics/db/models.txt
@@ -871,13 +871,19 @@ fields on this class are included into each of the child classes, with exactly
the same values for the attributes (including :attr:`~django.db.models.ForeignKey.related_name`) each time.
To work around this problem, when you are using :attr:`~django.db.models.ForeignKey.related_name` in an
-abstract base class (only), part of the name should be the string
-``'%(class)s'``. This is replaced by the lower-cased name of the child class
-that the field is used in. Since each class has a different name, each related
-name will end up being different. For example::
+abstract base class (only), part of the name should contain
+``'%(app_label)s'`` and ``'%(class)s'``.
+
+- ``'%(class)s'`` is replaced by the lower-cased name of the child class
+ that the field is used in.
+- ``'%(app_label)s'`` is replaced by the lower-cased name of the app the child
+ class is contained within. Each installed application name must be unique
+ and the model class names within each app must also be unique, therefore the
+ resulting name will end up being different. For example, given an app
+ ``common/models.py``::
class Base(models.Model):
- m2m = models.ManyToManyField(OtherModel, related_name="%(class)s_related")
+ m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")
class Meta:
abstract = True
@@ -888,19 +894,28 @@ name will end up being different. For example::
class ChildB(Base):
pass
-The reverse name of the ``ChildA.m2m`` field will be ``childa_related``,
-whilst the reverse name of the ``ChildB.m2m`` field will be
-``childb_related``. It is up to you how you use the ``'%(class)s'`` portion to
-construct your related name, but if you forget to use it, Django will raise
+Along with another app ``rare/models.py``::
+ from common.models import Base
+
+ class ChildB(Base):
+ pass
+
+The reverse name of the ``commmon.ChildA.m2m`` field will be
+``common_childa_related``, whilst the reverse name of the
+``common.ChildB.m2m`` field will be ``common_childb_related``, and finally the
+reverse name of the ``rare.ChildB.m2m`` field will be ``rare_childb_related``.
+It is up to you how you use the ``'%(class)s'`` and ``'%(app_label)s`` portion
+to construct your related name, but if you forget to use it, Django will raise
errors when you validate your models (or run :djadmin:`syncdb`).
-If you don't specify a :attr:`~django.db.models.ForeignKey.related_name` attribute for a field in an
-abstract base class, the default reverse name will be the name of the
-child class followed by ``'_set'``, just as it normally would be if
-you'd declared the field directly on the child class. For example, in
-the above code, if the :attr:`~django.db.models.ForeignKey.related_name` attribute was omitted, the
-reverse name for the ``m2m`` field would be ``childa_set`` in the
-``ChildA`` case and ``childb_set`` for the ``ChildB`` field.
+If you don't specify a :attr:`~django.db.models.ForeignKey.related_name`
+attribute for a field in an abstract base class, the default reverse name will
+be the name of the child class followed by ``'_set'``, just as it normally
+would be if you'd declared the field directly on the child class. For example,
+in the above code, if the :attr:`~django.db.models.ForeignKey.related_name`
+attribute was omitted, the reverse name for the ``m2m`` field would be
+``childa_set`` in the ``ChildA`` case and ``childb_set`` for the ``ChildB``
+field.
.. _multi-table-inheritance:
View
25 tests/modeltests/model_inheritance/models.py
@@ -116,6 +116,31 @@ class ParkingLot(Place):
def __unicode__(self):
return u"%s the parking lot" % self.name
+#
+# Abstract base classes with related models where the sub-class has the
+# same name in a different app and inherits from the same abstract base
+# class.
+# NOTE: The actual API tests for the following classes are in
+# model_inheritance_same_model_name/models.py - They are defined
+# here in order to have the name conflict between apps
+#
+
+class Title(models.Model):
+ title = models.CharField(max_length=50)
+
+class NamedURL(models.Model):
+ title = models.ForeignKey(Title, related_name='attached_%(app_label)s_%(class)s_set')
+ url = models.URLField()
+
+ class Meta:
+ abstract = True
+
+class Copy(NamedURL):
+ content = models.TextField()
+
+ def __unicode__(self):
+ return self.content
+
__test__ = {'API_TESTS':"""
# The Student and Worker models both have 'name' and 'age' fields on them and
# inherit the __unicode__() method, just as with normal Python subclassing.
View
0  tests/modeltests/model_inheritance_same_model_name/__init__.py
No changes.
View
19 tests/modeltests/model_inheritance_same_model_name/models.py
@@ -0,0 +1,19 @@
+"""
+XX. Model inheritance
+
+Model inheritance across apps can result in models with the same name resulting
+in the need for an %(app_label)s format string. This app specifically tests
+this feature by redefining the Copy model from model_inheritance/models.py
+"""
+
+from django.db import models
+from modeltests.model_inheritance.models import NamedURL
+
+#
+# Abstract base classes with related models
+#
+class Copy(NamedURL):
+ content = models.TextField()
+
+ def __unicode__(self):
+ return self.content
View
32 tests/modeltests/model_inheritance_same_model_name/tests.py
@@ -0,0 +1,32 @@
+from django.test import TestCase
+from modeltests.model_inheritance.models import Title
+
+class InheritanceSameModelNameTests(TestCase):
+
+ def setUp(self):
+ # The Title model has distinct accessors for both
+ # model_inheritance.Copy and model_inheritance_same_model_name.Copy
+ # models.
+ self.title = Title.objects.create(title='Lorem Ipsum')
+
+ def test_inheritance_related_name(self):
+ from modeltests.model_inheritance.models import Copy
+ self.assertEquals(
+ self.title.attached_model_inheritance_copy_set.create(
+ content='Save $ on V1agr@',
+ url='http://v1agra.com/',
+ title='V1agra is spam',
+ ), Copy.objects.get(content='Save $ on V1agr@'))
+
+ def test_inheritance_with_same_model_name(self):
+ from modeltests.model_inheritance_same_model_name.models import Copy
+ self.assertEquals(
+ self.title.attached_model_inheritance_same_model_name_copy_set.create(
+ content='The Web framework for perfectionists with deadlines.',
+ url='http://www.djangoproject.com/',
+ title='Django Rocks'
+ ), Copy.objects.get(content='The Web framework for perfectionists with deadlines.'))
+
+ def test_related_name_attribute_exists(self):
+ # The Post model doesn't have an attribute called 'attached_%(app_label)s_%(class)s_set'.
+ self.assertEqual(hasattr(self.title, 'attached_%(app_label)s_%(class)s_set'), False)
Please sign in to comment.
Something went wrong with that request. Please try again.