Fixed #22778 -- Added a model Meta option to define default_related_name.

Thanks jorgecarleitao and mmardini for reviews.
This commit is contained in:
Renaud Parent 2014-06-06 16:16:17 +00:00 committed by Tim Graham
parent de90129070
commit 87d0a3384c
7 changed files with 120 additions and 2 deletions

View File

@ -487,6 +487,7 @@ answer newbie questions, and generally made Django that much better:
Tomek Paczkowski <tomek@hauru.eu>
Jens Page
Guillaume Pannatier <guillaume.pannatier@gmail.com>
Renaud Parent <renaud.parent@gmail.com>
Jay Parlar <parlar@gmail.com>
Carlos Eduardo de Paula <carlosedp@gmail.com>
John Paulett <john@paulett.org>

View File

@ -20,7 +20,7 @@ DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
'order_with_respect_to', 'app_label', 'db_tablespace',
'abstract', 'managed', 'proxy', 'swappable', 'auto_created',
'index_together', 'apps', 'default_permissions',
'select_on_save')
'select_on_save', 'default_related_name')
def normalize_together(option_together):
@ -99,6 +99,8 @@ class Options(object):
# A custom app registry to use, if you're making a separate model set.
self.apps = apps
self.default_related_name = None
@property
def app_config(self):
# Don't go through get_app_config to avoid triggering imports.

View File

@ -57,7 +57,14 @@ class RelatedObject(object):
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
if getattr(self.field.rel, 'symmetrical', False) and self.model == self.parent_model:
return None
return self.field.rel.related_name or (self.opts.model_name + '_set')
if self.field.rel.related_name:
return self.field.rel.related_name
if self.opts.default_related_name:
return self.opts.default_related_name % {
'model_name': self.opts.model_name.lower(),
'app_label': self.opts.app_label.lower(),
}
return self.opts.model_name + '_set'
else:
return self.field.rel.related_name or (self.opts.model_name)

View File

@ -95,6 +95,23 @@ Django quotes column and table names behind the scenes.
setting, if set. If the backend doesn't support tablespaces, this option is
ignored.
``default_related_name``
------------------------
.. attribute:: Options.default_related_name
.. versionadded:: 1.8
The name that will be used by default for the relation from a related object
back to this one. The default is ``<model_name>_set``.
As the reverse name for a field should be unique, be careful if you intend
to subclass your model. To work around name collisions, part of the name
should contain ``'%(app_label)s'`` and ``'%(model_name)s'``, which are
replaced respectively by the name of the application the model is in,
and the name of the model, both lowercased. See the paragraph on
:ref:`related names for abstract models <abstract-related-name>`.
``get_latest_by``
-----------------

View File

@ -178,6 +178,10 @@ Models
* Django now logs at most 9000 queries in ``connections.queries``, in order
to prevent excessive memory usage in long-running processes in debug mode.
* There is now a model ``Meta`` option to define a
:attr:`default related name <django.db.models.Options.default_related_name>`
for all relational fields of a model.
* Pickling models and querysets across different versions of Django isn't
officially supported (it may work, but there's no guarantee). An extra
variable that specifies the current Django version is now added to the

View File

@ -0,0 +1,41 @@
from django.db import models
class Author(models.Model):
first_name = models.CharField(max_length=128)
last_name = models.CharField(max_length=128)
class Editor(models.Model):
name = models.CharField(max_length=128)
bestselling_author = models.ForeignKey(Author)
class Book(models.Model):
title = models.CharField(max_length=128)
authors = models.ManyToManyField(Author)
editor = models.ForeignKey(Editor, related_name="edited_books")
class Meta:
default_related_name = "books"
class Store(models.Model):
name = models.CharField(max_length=128)
address = models.CharField(max_length=128)
class Meta:
abstract = True
default_related_name = "%(app_label)s_%(model_name)ss"
class BookStore(Store):
available_books = models.ManyToManyField(Book)
class EditorStore(Store):
editor = models.ForeignKey(Editor)
available_books = models.ManyToManyField(Book)
class Meta:
default_related_name = "editor_stores"

View File

@ -0,0 +1,46 @@
from django.test import TestCase
from .models.default_related_name import Author, Editor, Book
class DefaultRelatedNameTests(TestCase):
def setUp(self):
self.author = Author.objects.create(first_name="Dave", last_name="Loper")
self.editor = Editor.objects.create(name="Test Editions",
bestselling_author=self.author)
self.book = Book.objects.create(title="Test Book", editor=self.editor)
self.book.authors.add(self.author)
self.book.save()
def test_no_default_related_name(self):
try:
self.author.editor_set
except AttributeError:
self.fail("Author should have an editor_set relation.")
def test_default_related_name(self):
try:
self.author.books
except AttributeError:
self.fail("Author should have a books relation.")
def test_related_name_overrides_default_related_name(self):
try:
self.editor.edited_books
except AttributeError:
self.fail("Editor should have a edited_books relation.")
def test_inheritance(self):
try:
# Here model_options corresponds to the name of the application used
# in this test
self.book.model_options_bookstores
except AttributeError:
self.fail("Book should have a model_options_bookstores relation.")
def test_inheritance_with_overrided_default_related_name(self):
try:
self.book.editor_stores
except AttributeError:
self.fail("Book should have a editor_stores relation.")