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
|
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>
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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