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:
parent
1e955e0143
commit
bacfe3f3e8
2
AUTHORS
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>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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)
|
Loading…
Reference in New Issue