Fixed #15272 -- Altered generic views to use the guaranteed untranslated object_name, rather than the possibly translated verbose_name(_plural) for default context objects. Thanks to szczav for the report and patch.

This is BACKWARDS INCOMPATIBLE for anyone relying on the default context object names for class-based Detail and List views. To migrate, either update your templates to use the new default names, or add a context_object_name argument to your generic views.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15531 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2011-02-14 13:04:51 +00:00
parent a26034ffbf
commit 06b22963ea
7 changed files with 44 additions and 41 deletions

View File

@ -80,8 +80,7 @@ class SingleObjectMixin(object):
if self.context_object_name: if self.context_object_name:
return self.context_object_name return self.context_object_name
elif hasattr(obj, '_meta'): elif hasattr(obj, '_meta'):
return smart_str(re.sub('[^a-zA-Z0-9]+', '_', return smart_str(obj._meta.object_name.lower())
obj._meta.verbose_name.lower()))
else: else:
return None return None

View File

@ -76,8 +76,7 @@ class MultipleObjectMixin(object):
if self.context_object_name: if self.context_object_name:
return self.context_object_name return self.context_object_name
elif hasattr(object_list, 'model'): elif hasattr(object_list, 'model'):
return smart_str(re.sub('[^a-zA-Z0-9]+', '_', return smart_str('%s_list' % object_list.model._meta.object_name.lower())
object_list.model._meta.verbose_name_plural.lower()))
else: else:
return None return None

View File

@ -162,14 +162,14 @@ SingleObjectMixin
it constructs a :class:`QuerySet` by calling the `all()` method on the it constructs a :class:`QuerySet` by calling the `all()` method on the
:attr:`~SingleObjectMixin.model` attribute's default manager. :attr:`~SingleObjectMixin.model` attribute's default manager.
.. method:: get_context_object_name(object_list) .. method:: get_context_object_name(obj)
Return the context variable name that will be used to contain the Return the context variable name that will be used to contain the
list of data that this view is manipulating. If ``object_list`` is a data that this view is manipulating. If
:class:`QuerySet` of Django objects and
:attr:`~SingleObjectMixin.context_object_name` is not set, the context :attr:`~SingleObjectMixin.context_object_name` is not set, the context
name will be constructed from the verbose plural name of the model that name will be constructed from the ``object_name`` of the model that
the queryset is composed from. the queryset is composed from. For example, the model ``Article``
would have context object named ``'article'``.
.. method:: get_context_data(**kwargs) .. method:: get_context_data(**kwargs)
@ -333,10 +333,14 @@ MultipleObjectMixin
.. method:: get_context_object_name(object_list) .. method:: get_context_object_name(object_list)
Return the context variable name that will be used to contain the list Return the context variable name that will be used to contain
of data that this view is manipulating. If object_list is a queryset of the list of data that this view is manipulating. If
Django objects, the context name will be verbose plural name of the ``object_list`` is a queryset of Django objects and
model that the queryset is composed from. :attr:`~MultipleObjectMixin.context_object_name` is not set,
the context name will be the ``object_name`` of the model that
the queryset is composed from, with postfix ``'_list'``
appended. For example, the model ``Article`` would have a
context object named ``article_list``.
.. method:: get_context_data(**kwargs) .. method:: get_context_data(**kwargs)

View File

@ -72,7 +72,8 @@ The ``_list`` suffix on list views
In a function-based :class:`ListView`, the ``template_object_name`` In a function-based :class:`ListView`, the ``template_object_name``
was appended with the suffix ``'_list'`` to yield the final context was appended with the suffix ``'_list'`` to yield the final context
variable name. In a class-based ``ListView``, the variable name. In a class-based ``ListView``, the
``context_object_name`` is used verbatim. ``context_object_name`` is used verbatim. The ``'_list'`` suffix
is only applied when generating a default context object name.
The context data for ``object_list`` views The context data for ``object_list`` views
------------------------------------------ ------------------------------------------

View File

@ -92,7 +92,7 @@ class YearArchiveViewTests(TestCase):
res = self.client.get('/dates/books/2006/make_object_list/') res = self.client.get('/dates/books/2006/make_object_list/')
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(list(res.context['date_list']), [datetime.datetime(2006, 5, 1)]) self.assertEqual(list(res.context['date_list']), [datetime.datetime(2006, 5, 1)])
self.assertEqual(list(res.context['books']), list(Book.objects.filter(pubdate__year=2006))) self.assertEqual(list(res.context['book_list']), list(Book.objects.filter(pubdate__year=2006)))
self.assertEqual(list(res.context['object_list']), list(Book.objects.filter(pubdate__year=2006))) self.assertEqual(list(res.context['object_list']), list(Book.objects.filter(pubdate__year=2006)))
self.assertTemplateUsed(res, 'generic_views/book_archive_year.html') self.assertTemplateUsed(res, 'generic_views/book_archive_year.html')
@ -102,7 +102,7 @@ class YearArchiveViewTests(TestCase):
res = self.client.get('/dates/books/1999/allow_empty/') res = self.client.get('/dates/books/1999/allow_empty/')
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(list(res.context['date_list']), []) self.assertEqual(list(res.context['date_list']), [])
self.assertEqual(list(res.context['books']), []) self.assertEqual(list(res.context['book_list']), [])
def test_year_view_allow_future(self): def test_year_view_allow_future(self):
# Create a new book in the future # Create a new book in the future
@ -113,7 +113,7 @@ class YearArchiveViewTests(TestCase):
res = self.client.get('/dates/books/%s/allow_empty/' % year) res = self.client.get('/dates/books/%s/allow_empty/' % year)
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(list(res.context['books']), []) self.assertEqual(list(res.context['book_list']), [])
res = self.client.get('/dates/books/%s/allow_future/' % year) res = self.client.get('/dates/books/%s/allow_future/' % year)
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
@ -132,7 +132,7 @@ class MonthArchiveViewTests(TestCase):
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'generic_views/book_archive_month.html') self.assertTemplateUsed(res, 'generic_views/book_archive_month.html')
self.assertEqual(list(res.context['date_list']), [datetime.datetime(2008, 10, 1)]) self.assertEqual(list(res.context['date_list']), [datetime.datetime(2008, 10, 1)])
self.assertEqual(list(res.context['books']), self.assertEqual(list(res.context['book_list']),
list(Book.objects.filter(pubdate=datetime.date(2008, 10, 1)))) list(Book.objects.filter(pubdate=datetime.date(2008, 10, 1))))
self.assertEqual(res.context['month'], datetime.date(2008, 10, 1)) self.assertEqual(res.context['month'], datetime.date(2008, 10, 1))
@ -149,7 +149,7 @@ class MonthArchiveViewTests(TestCase):
res = self.client.get('/dates/books/2000/jan/allow_empty/') res = self.client.get('/dates/books/2000/jan/allow_empty/')
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(list(res.context['date_list']), []) self.assertEqual(list(res.context['date_list']), [])
self.assertEqual(list(res.context['books']), []) self.assertEqual(list(res.context['book_list']), [])
self.assertEqual(res.context['month'], datetime.date(2000, 1, 1)) self.assertEqual(res.context['month'], datetime.date(2000, 1, 1))
# Since it's allow empty, next/prev are allowed to be empty months (#7164) # Since it's allow empty, next/prev are allowed to be empty months (#7164)
@ -175,7 +175,7 @@ class MonthArchiveViewTests(TestCase):
res = self.client.get('/dates/books/%s/allow_future/' % urlbit) res = self.client.get('/dates/books/%s/allow_future/' % urlbit)
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(res.context['date_list'][0].date(), b.pubdate) self.assertEqual(res.context['date_list'][0].date(), b.pubdate)
self.assertEqual(list(res.context['books']), [b]) self.assertEqual(list(res.context['book_list']), [b])
self.assertEqual(res.context['month'], future) self.assertEqual(res.context['month'], future)
# Since it's allow_future but not allow_empty, next/prev are not # Since it's allow_future but not allow_empty, next/prev are not
@ -229,7 +229,7 @@ class WeekArchiveViewTests(TestCase):
res = self.client.get('/dates/books/2008/week/39/') res = self.client.get('/dates/books/2008/week/39/')
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'generic_views/book_archive_week.html') self.assertTemplateUsed(res, 'generic_views/book_archive_week.html')
self.assertEqual(res.context['books'][0], Book.objects.get(pubdate=datetime.date(2008, 10, 1))) self.assertEqual(res.context['book_list'][0], Book.objects.get(pubdate=datetime.date(2008, 10, 1)))
self.assertEqual(res.context['week'], datetime.date(2008, 9, 28)) self.assertEqual(res.context['week'], datetime.date(2008, 9, 28))
def test_week_view_allow_empty(self): def test_week_view_allow_empty(self):
@ -238,7 +238,7 @@ class WeekArchiveViewTests(TestCase):
res = self.client.get('/dates/books/2008/week/12/allow_empty/') res = self.client.get('/dates/books/2008/week/12/allow_empty/')
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(list(res.context['books']), []) self.assertEqual(list(res.context['book_list']), [])
def test_week_view_allow_future(self): def test_week_view_allow_future(self):
future = datetime.date(datetime.date.today().year + 1, 1, 1) future = datetime.date(datetime.date.today().year + 1, 1, 1)
@ -249,7 +249,7 @@ class WeekArchiveViewTests(TestCase):
res = self.client.get('/dates/books/%s/week/1/allow_future/' % future.year) res = self.client.get('/dates/books/%s/week/1/allow_future/' % future.year)
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(list(res.context['books']), [b]) self.assertEqual(list(res.context['book_list']), [b])
def test_week_view_invalid_pattern(self): def test_week_view_invalid_pattern(self):
res = self.client.get('/dates/books/2007/week/no_week/') res = self.client.get('/dates/books/2007/week/no_week/')
@ -273,7 +273,7 @@ class DayArchiveViewTests(TestCase):
res = self.client.get('/dates/books/2008/oct/01/') res = self.client.get('/dates/books/2008/oct/01/')
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'generic_views/book_archive_day.html') self.assertTemplateUsed(res, 'generic_views/book_archive_day.html')
self.assertEqual(list(res.context['books']), self.assertEqual(list(res.context['book_list']),
list(Book.objects.filter(pubdate=datetime.date(2008, 10, 1)))) list(Book.objects.filter(pubdate=datetime.date(2008, 10, 1))))
self.assertEqual(res.context['day'], datetime.date(2008, 10, 1)) self.assertEqual(res.context['day'], datetime.date(2008, 10, 1))
@ -289,7 +289,7 @@ class DayArchiveViewTests(TestCase):
# allow_empty = True, empty month # allow_empty = True, empty month
res = self.client.get('/dates/books/2000/jan/1/allow_empty/') res = self.client.get('/dates/books/2000/jan/1/allow_empty/')
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(list(res.context['books']), []) self.assertEqual(list(res.context['book_list']), [])
self.assertEqual(res.context['day'], datetime.date(2000, 1, 1)) self.assertEqual(res.context['day'], datetime.date(2000, 1, 1))
# Since it's allow empty, next/prev are allowed to be empty months (#7164) # Since it's allow empty, next/prev are allowed to be empty months (#7164)
@ -314,7 +314,7 @@ class DayArchiveViewTests(TestCase):
# allow_future = True, valid future month # allow_future = True, valid future month
res = self.client.get('/dates/books/%s/allow_future/' % urlbit) res = self.client.get('/dates/books/%s/allow_future/' % urlbit)
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(list(res.context['books']), [b]) self.assertEqual(list(res.context['book_list']), [b])
self.assertEqual(res.context['day'], future) self.assertEqual(res.context['day'], future)
# allow_future but not allow_empty, next/prev amust be valid # allow_future but not allow_empty, next/prev amust be valid

View File

@ -32,7 +32,7 @@ class DetailViewTest(TestCase):
res = self.client.get('/detail/artist/1/') res = self.client.get('/detail/artist/1/')
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(res.context['object'], Artist.objects.get(pk=1)) self.assertEqual(res.context['object'], Artist.objects.get(pk=1))
self.assertEqual(res.context['professional_artist'], Artist.objects.get(pk=1)) self.assertEqual(res.context['artist'], Artist.objects.get(pk=1))
self.assertTemplateUsed(res, 'generic_views/artist_detail.html') self.assertTemplateUsed(res, 'generic_views/artist_detail.html')
def test_template_name(self): def test_template_name(self):

View File

@ -19,7 +19,7 @@ class ListViewTests(TestCase):
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'generic_views/author_list.html') self.assertTemplateUsed(res, 'generic_views/author_list.html')
self.assertEqual(list(res.context['object_list']), list(Author.objects.all())) self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
self.assertIs(res.context['authors'], res.context['object_list']) self.assertIs(res.context['author_list'], res.context['object_list'])
self.assertIsNone(res.context['paginator']) self.assertIsNone(res.context['paginator'])
self.assertIsNone(res.context['page_obj']) self.assertIsNone(res.context['page_obj'])
self.assertFalse(res.context['is_paginated']) self.assertFalse(res.context['is_paginated'])
@ -30,12 +30,12 @@ class ListViewTests(TestCase):
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'generic_views/author_list.html') self.assertTemplateUsed(res, 'generic_views/author_list.html')
self.assertEqual(len(res.context['object_list']), 30) self.assertEqual(len(res.context['object_list']), 30)
self.assertIs(res.context['authors'], res.context['object_list']) self.assertIs(res.context['author_list'], res.context['object_list'])
self.assertTrue(res.context['is_paginated']) self.assertTrue(res.context['is_paginated'])
self.assertEqual(res.context['page_obj'].number, 1) self.assertEqual(res.context['page_obj'].number, 1)
self.assertEqual(res.context['paginator'].num_pages, 4) self.assertEqual(res.context['paginator'].num_pages, 4)
self.assertEqual(res.context['authors'][0].name, 'Author 00') self.assertEqual(res.context['author_list'][0].name, 'Author 00')
self.assertEqual(list(res.context['authors'])[-1].name, 'Author 29') self.assertEqual(list(res.context['author_list'])[-1].name, 'Author 29')
def test_paginated_queryset_shortdata(self): def test_paginated_queryset_shortdata(self):
# Test that short datasets ALSO result in a paginated view. # Test that short datasets ALSO result in a paginated view.
@ -43,7 +43,7 @@ class ListViewTests(TestCase):
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'generic_views/author_list.html') self.assertTemplateUsed(res, 'generic_views/author_list.html')
self.assertEqual(list(res.context['object_list']), list(Author.objects.all())) self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
self.assertIs(res.context['authors'], res.context['object_list']) self.assertIs(res.context['author_list'], res.context['object_list'])
self.assertEqual(res.context['page_obj'].number, 1) self.assertEqual(res.context['page_obj'].number, 1)
self.assertEqual(res.context['paginator'].num_pages, 1) self.assertEqual(res.context['paginator'].num_pages, 1)
self.assertTrue(res.context['is_paginated']) self.assertTrue(res.context['is_paginated'])
@ -54,8 +54,8 @@ class ListViewTests(TestCase):
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'generic_views/author_list.html') self.assertTemplateUsed(res, 'generic_views/author_list.html')
self.assertEqual(len(res.context['object_list']), 30) self.assertEqual(len(res.context['object_list']), 30)
self.assertIs(res.context['authors'], res.context['object_list']) self.assertIs(res.context['author_list'], res.context['object_list'])
self.assertEqual(res.context['authors'][0].name, 'Author 30') self.assertEqual(res.context['author_list'][0].name, 'Author 30')
self.assertEqual(res.context['page_obj'].number, 2) self.assertEqual(res.context['page_obj'].number, 2)
def test_paginated_get_last_page_by_query_string(self): def test_paginated_get_last_page_by_query_string(self):
@ -63,8 +63,8 @@ class ListViewTests(TestCase):
res = self.client.get('/list/authors/paginated/', {'page': 'last'}) res = self.client.get('/list/authors/paginated/', {'page': 'last'})
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(len(res.context['object_list']), 10) self.assertEqual(len(res.context['object_list']), 10)
self.assertIs(res.context['authors'], res.context['object_list']) self.assertIs(res.context['author_list'], res.context['object_list'])
self.assertEqual(res.context['authors'][0].name, 'Author 90') self.assertEqual(res.context['author_list'][0].name, 'Author 90')
self.assertEqual(res.context['page_obj'].number, 4) self.assertEqual(res.context['page_obj'].number, 4)
def test_paginated_get_page_by_urlvar(self): def test_paginated_get_page_by_urlvar(self):
@ -73,8 +73,8 @@ class ListViewTests(TestCase):
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'generic_views/author_list.html') self.assertTemplateUsed(res, 'generic_views/author_list.html')
self.assertEqual(len(res.context['object_list']), 30) self.assertEqual(len(res.context['object_list']), 30)
self.assertIs(res.context['authors'], res.context['object_list']) self.assertIs(res.context['author_list'], res.context['object_list'])
self.assertEqual(res.context['authors'][0].name, 'Author 60') self.assertEqual(res.context['author_list'][0].name, 'Author 60')
self.assertEqual(res.context['page_obj'].number, 3) self.assertEqual(res.context['page_obj'].number, 3)
def test_paginated_page_out_of_range(self): def test_paginated_page_out_of_range(self):
@ -112,7 +112,7 @@ class ListViewTests(TestCase):
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'generic_views/list.html') self.assertTemplateUsed(res, 'generic_views/list.html')
self.assertEqual(list(res.context['object_list']), list(Artist.objects.all())) self.assertEqual(list(res.context['object_list']), list(Artist.objects.all()))
self.assertIs(res.context['professional_artists'], res.context['object_list']) self.assertIs(res.context['artist_list'], res.context['object_list'])
self.assertIsNone(res.context['paginator']) self.assertIsNone(res.context['paginator'])
self.assertIsNone(res.context['page_obj']) self.assertIsNone(res.context['page_obj'])
self.assertFalse(res.context['is_paginated']) self.assertFalse(res.context['is_paginated'])
@ -128,14 +128,14 @@ class ListViewTests(TestCase):
res = self.client.get('/list/authors/template_name/') res = self.client.get('/list/authors/template_name/')
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(list(res.context['object_list']), list(Author.objects.all())) self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
self.assertIs(res.context['authors'], res.context['object_list']) self.assertIs(res.context['author_list'], res.context['object_list'])
self.assertTemplateUsed(res, 'generic_views/list.html') self.assertTemplateUsed(res, 'generic_views/list.html')
def test_template_name_suffix(self): def test_template_name_suffix(self):
res = self.client.get('/list/authors/template_name_suffix/') res = self.client.get('/list/authors/template_name_suffix/')
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertEqual(list(res.context['object_list']), list(Author.objects.all())) self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
self.assertIs(res.context['authors'], res.context['object_list']) self.assertIs(res.context['author_list'], res.context['object_list'])
self.assertTemplateUsed(res, 'generic_views/author_objects.html') self.assertTemplateUsed(res, 'generic_views/author_objects.html')
def test_context_object_name(self): def test_context_object_name(self):