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 kurtiss@meetro.com
Denis Kuzmichyov <kuzmichyov@gmail.com> Denis Kuzmichyov <kuzmichyov@gmail.com>
Panos Laganakos <panos.laganakos@gmail.com> Panos Laganakos <panos.laganakos@gmail.com>
lakin.wecker@gmail.com Lakin Wecker <lakin@structuredabstraction.com>
Nick Lane <nick.lane.au@gmail.com> Nick Lane <nick.lane.au@gmail.com>
Stuart Langridge <http://www.kryogenix.org/> Stuart Langridge <http://www.kryogenix.org/>
Paul Lanier <planier@google.com> Paul Lanier <planier@google.com>

View File

@ -94,7 +94,10 @@ class RelatedField(object):
sup.contribute_to_class(cls, name) sup.contribute_to_class(cls, name)
if not cls._meta.abstract and self.rel.related_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 other = self.rel.to
if isinstance(other, basestring) or other._meta.pk is None: 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. 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 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 abstract base class (only), part of the name should contain
``'%(class)s'``. This is replaced by the lower-cased name of the child class ``'%(app_label)s'`` and ``'%(class)s'``.
that the field is used in. Since each class has a different name, each related
name will end up being different. For example:: - ``'%(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): 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: class Meta:
abstract = True abstract = True
@ -888,19 +894,28 @@ name will end up being different. For example::
class ChildB(Base): class ChildB(Base):
pass pass
The reverse name of the ``ChildA.m2m`` field will be ``childa_related``, Along with another app ``rare/models.py``::
whilst the reverse name of the ``ChildB.m2m`` field will be from common.models import Base
``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 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`). 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 If you don't specify a :attr:`~django.db.models.ForeignKey.related_name`
abstract base class, the default reverse name will be the name of the attribute for a field in an abstract base class, the default reverse name will
child class followed by ``'_set'``, just as it normally would be if be the name of the child class followed by ``'_set'``, just as it normally
you'd declared the field directly on the child class. For example, in would be if you'd declared the field directly on the child class. For example,
the above code, if the :attr:`~django.db.models.ForeignKey.related_name` attribute was omitted, the in the above code, if the :attr:`~django.db.models.ForeignKey.related_name`
reverse name for the ``m2m`` field would be ``childa_set`` in the attribute was omitted, the reverse name for the ``m2m`` field would be
``ChildA`` case and ``childb_set`` for the ``ChildB`` field. ``childa_set`` in the ``ChildA`` case and ``childb_set`` for the ``ChildB``
field.
.. _multi-table-inheritance: .. _multi-table-inheritance:

View File

@ -116,6 +116,31 @@ class ParkingLot(Place):
def __unicode__(self): def __unicode__(self):
return u"%s the parking lot" % self.name 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':""" __test__ = {'API_TESTS':"""
# The Student and Worker models both have 'name' and 'age' fields on them and # The Student and Worker models both have 'name' and 'age' fields on them and
# inherit the __unicode__() method, just as with normal Python subclassing. # 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)