From a00e8d4e4204bfb730ba3eac2ee11ad83c8ec91b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 3 Jan 2011 13:15:58 +0000 Subject: [PATCH] Fixed #14878 -- Clarified the way verbose_name_plural is used in generic list views as a context variable. Thanks to diegueus9 for the report. git-svn-id: http://code.djangoproject.com/svn/django/trunk@15133 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/generic/detail.py | 5 ++-- django/views/generic/list.py | 6 ++++- docs/ref/class-based-views.txt | 2 +- docs/topics/class-based-views.txt | 23 +++++++++++++------ tests/regressiontests/generic_views/detail.py | 9 +++++++- .../fixtures/generic-views-test-data.json | 7 ++++++ tests/regressiontests/generic_views/list.py | 12 +++++++++- tests/regressiontests/generic_views/models.py | 2 ++ tests/regressiontests/generic_views/urls.py | 3 +++ tests/regressiontests/generic_views/views.py | 5 ++++ 10 files changed, 61 insertions(+), 13 deletions(-) diff --git a/django/views/generic/detail.py b/django/views/generic/detail.py index e75dd77409..3fc21c1f49 100644 --- a/django/views/generic/detail.py +++ b/django/views/generic/detail.py @@ -2,6 +2,7 @@ import re from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.http import Http404 +from django.utils.encoding import smart_str from django.views.generic.base import TemplateResponseMixin, View @@ -79,8 +80,8 @@ class SingleObjectMixin(object): if self.context_object_name: return self.context_object_name elif hasattr(obj, '_meta'): - return re.sub('[^a-zA-Z0-9]+', '_', - obj._meta.verbose_name.lower()) + return smart_str(re.sub('[^a-zA-Z0-9]+', '_', + obj._meta.verbose_name.lower())) else: return None diff --git a/django/views/generic/list.py b/django/views/generic/list.py index 22135720ed..cd7f9f6ae8 100644 --- a/django/views/generic/list.py +++ b/django/views/generic/list.py @@ -1,9 +1,12 @@ +import re + from django.core.paginator import Paginator, InvalidPage from django.core.exceptions import ImproperlyConfigured from django.http import Http404 from django.utils.encoding import smart_str from django.views.generic.base import TemplateResponseMixin, View + class MultipleObjectMixin(object): allow_empty = True queryset = None @@ -76,7 +79,8 @@ class MultipleObjectMixin(object): if self.context_object_name: return self.context_object_name elif hasattr(object_list, 'model'): - return smart_str(object_list.model._meta.verbose_name_plural) + return smart_str(re.sub('[^a-zA-Z0-9]+', '_', + object_list.model._meta.verbose_name_plural.lower())) else: return None diff --git a/docs/ref/class-based-views.txt b/docs/ref/class-based-views.txt index e8255a4381..8350fd22b2 100644 --- a/docs/ref/class-based-views.txt +++ b/docs/ref/class-based-views.txt @@ -428,7 +428,7 @@ FormMixin .. method:: get_form_kwargs() Build the keyword arguments requried to instanciate an the form. - + The ``initial`` argument is set to :meth:`.get_initial`. If the request is a ``POST`` or ``PUT``, the request data (``request.POST`` and ``request.FILES``) will also be provided. diff --git a/docs/topics/class-based-views.txt b/docs/topics/class-based-views.txt index 877323bcdd..516a3150ed 100644 --- a/docs/topics/class-based-views.txt +++ b/docs/topics/class-based-views.txt @@ -206,14 +206,23 @@ their attributes or methods. Making "friendly" template contexts ----------------------------------- -You might have noticed that our sample publisher list template stores all the -publishers in a variable named ``object_list``. While this works just fine, it -isn't all that "friendly" to template authors: they have to "just know" that -they're dealing with publishers here. A more obvious name for that variable -would be ``publisher_list``. +You might have noticed that our sample publisher list template stores +all the publishers in a variable named ``object_list``. While this +works just fine, it isn't all that "friendly" to template authors: +they have to "just know" that they're dealing with publishers here. -We can change the name of that variable easily with the ``context_object_name`` -attribute - here, we'll override it in the URLconf, since it's a simple change: +Well, if you're dealing with a Django object, this is already done for +you. When you are dealing with an object or queryset, Django is able +to populate the context using the verbose name (or the plural verbose +name, in the case of a list of objects) of the object being displayed. +This is provided in addition to the default ``object_list`` entry, but +contains exactly the same data. + +If the verbose name (or plural verbose name) still isn't a good match, +you can manually set the name of the context variable. The +``context_object_name`` attribute on a generic view specifies the +context variable to use. In this example, we'll override it in the +URLconf, since it's a simple change: .. parsed-literal:: diff --git a/tests/regressiontests/generic_views/detail.py b/tests/regressiontests/generic_views/detail.py index 91cacf65ba..c26ca67bd1 100644 --- a/tests/regressiontests/generic_views/detail.py +++ b/tests/regressiontests/generic_views/detail.py @@ -1,7 +1,7 @@ from django.core.exceptions import ImproperlyConfigured from django.test import TestCase -from regressiontests.generic_views.models import Author, Page +from regressiontests.generic_views.models import Artist, Author, Page class DetailViewTest(TestCase): @@ -28,6 +28,13 @@ class DetailViewTest(TestCase): self.assertEqual(res.context['author'], Author.objects.get(slug='scott-rosenberg')) self.assertTemplateUsed(res, 'generic_views/author_detail.html') + def test_verbose_name(self): + res = self.client.get('/detail/artist/1/') + self.assertEqual(res.status_code, 200) + self.assertEqual(res.context['object'], Artist.objects.get(pk=1)) + self.assertEqual(res.context['professional_artist'], Artist.objects.get(pk=1)) + self.assertTemplateUsed(res, 'generic_views/artist_detail.html') + def test_template_name(self): res = self.client.get('/detail/author/1/template_name/') self.assertEqual(res.status_code, 200) diff --git a/tests/regressiontests/generic_views/fixtures/generic-views-test-data.json b/tests/regressiontests/generic_views/fixtures/generic-views-test-data.json index 8ecfe5e1e2..dfffbbf8d8 100644 --- a/tests/regressiontests/generic_views/fixtures/generic-views-test-data.json +++ b/tests/regressiontests/generic_views/fixtures/generic-views-test-data.json @@ -1,4 +1,11 @@ [ + { + "model": "generic_views.artist", + "pk": 1, + "fields": { + "name": "Rene Magritte" + } + }, { "model": "generic_views.author", "pk": 1, diff --git a/tests/regressiontests/generic_views/list.py b/tests/regressiontests/generic_views/list.py index 6ed28170a7..7414c62d3a 100644 --- a/tests/regressiontests/generic_views/list.py +++ b/tests/regressiontests/generic_views/list.py @@ -1,7 +1,7 @@ from django.core.exceptions import ImproperlyConfigured from django.test import TestCase -from regressiontests.generic_views.models import Author +from regressiontests.generic_views.models import Author, Artist from regressiontests.generic_views.views import CustomPaginator class ListViewTests(TestCase): @@ -106,6 +106,16 @@ class ListViewTests(TestCase): self.assertEqual(res.status_code, 200) self.assertEqual(len(res.context['object_list']), 1) + def test_verbose_name(self): + res = self.client.get('/list/artists/') + self.assertEqual(res.status_code, 200) + self.assertTemplateUsed(res, 'generic_views/list.html') + self.assertEqual(list(res.context['object_list']), list(Artist.objects.all())) + self.assertIs(res.context['professional_artists'], res.context['object_list']) + self.assertIsNone(res.context['paginator']) + self.assertIsNone(res.context['page_obj']) + self.assertFalse(res.context['is_paginated']) + def test_allow_empty_false(self): res = self.client.get('/list/authors/notempty/') self.assertEqual(res.status_code, 200) diff --git a/tests/regressiontests/generic_views/models.py b/tests/regressiontests/generic_views/models.py index 5a8577d0f6..5445e24cee 100644 --- a/tests/regressiontests/generic_views/models.py +++ b/tests/regressiontests/generic_views/models.py @@ -5,6 +5,8 @@ class Artist(models.Model): class Meta: ordering = ['name'] + verbose_name = 'professional artist' + verbose_name_plural = 'professional artists' def __unicode__(self): return self.name diff --git a/tests/regressiontests/generic_views/urls.py b/tests/regressiontests/generic_views/urls.py index 037da42d9a..bd997e7e6d 100644 --- a/tests/regressiontests/generic_views/urls.py +++ b/tests/regressiontests/generic_views/urls.py @@ -100,6 +100,9 @@ urlpatterns = patterns('', views.DictList.as_view()), (r'^list/dict/paginated/$', views.DictList.as_view(paginate_by=1)), + url(r'^list/artists/$', + views.ArtistList.as_view(), + name="artists_list"), url(r'^list/authors/$', views.AuthorList.as_view(), name="authors_list"), diff --git a/tests/regressiontests/generic_views/views.py b/tests/regressiontests/generic_views/views.py index e3a3c40694..e9be7c7ca9 100644 --- a/tests/regressiontests/generic_views/views.py +++ b/tests/regressiontests/generic_views/views.py @@ -47,6 +47,11 @@ class DictList(generic.ListView): template_name = 'generic_views/list.html' +class ArtistList(generic.ListView): + template_name = 'generic_views/list.html' + queryset = Artist.objects.all() + + class AuthorList(generic.ListView): queryset = Author.objects.all()