Fixed #9638 - Added %(app_label)s to the related_name format string for abstract models.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12139 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel 2010-01-09 20:03:52 +00:00
parent 1e955e0143
commit bacfe3f3e8
7 changed files with 112 additions and 18 deletions

View File

@ -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 File

@ -94,7 +94,10 @@ class RelatedField(object):
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 File

@ -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 File

@ -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 File

@ -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 File

@ -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)