Fixed #16074 -- Added ContextMixin to class-based generic views to handle get_context_data. Thanks emyller, Luke Plant, Preston Holmes for working on the ticket and patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17875 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Claude Paroz 2012-04-06 21:24:33 +00:00
parent b4a9827133
commit 8663bc1103
9 changed files with 77 additions and 38 deletions

View File

@ -8,6 +8,16 @@ from django.utils.decorators import classonlymethod
logger = getLogger('django.request') logger = getLogger('django.request')
class ContextMixin(object):
"""
A default context mixin that passes the keyword arguments received by
get_context_data as the template context.
"""
def get_context_data(self, **kwargs):
return kwargs
class View(object): class View(object):
""" """
Intentionally simple parent class for all views. Only implements Intentionally simple parent class for all views. Only implements
@ -110,17 +120,13 @@ class TemplateResponseMixin(object):
return [self.template_name] return [self.template_name]
class TemplateView(TemplateResponseMixin, View): class TemplateView(TemplateResponseMixin, ContextMixin, View):
""" """
A view that renders a template. A view that renders a template. This view is different from all the others
insofar as it also passes ``kwargs`` as ``params`` to the template context.
""" """
def get_context_data(self, **kwargs):
return {
'params': kwargs
}
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs) context = self.get_context_data(params=kwargs)
return self.render_to_response(context) return self.render_to_response(context)

View File

@ -217,16 +217,6 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
return date_list return date_list
def get_context_data(self, **kwargs):
"""
Get the context. Must return a Context (or subclass) instance.
"""
items = kwargs.pop('object_list')
context = super(BaseDateListView, self).get_context_data(object_list=items)
context.update(kwargs)
return context
class BaseArchiveIndexView(BaseDateListView): class BaseArchiveIndexView(BaseDateListView):
""" """
Base class for archives of date-based items. Base class for archives of date-based items.

View File

@ -2,10 +2,10 @@ from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.http import Http404 from django.http import Http404
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic.base import TemplateResponseMixin, View from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
class SingleObjectMixin(object): class SingleObjectMixin(ContextMixin):
""" """
Provides the ability to retrieve a single object for further manipulation. Provides the ability to retrieve a single object for further manipulation.
""" """
@ -86,11 +86,12 @@ class SingleObjectMixin(object):
return None return None
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = kwargs context = {}
context_object_name = self.get_context_object_name(self.object) context_object_name = self.get_context_object_name(self.object)
if context_object_name: if context_object_name:
context[context_object_name] = self.object context[context_object_name] = self.object
return context context.update(kwargs)
return super(SingleObjectMixin, self).get_context_data(**context)
class BaseDetailView(SingleObjectMixin, View): class BaseDetailView(SingleObjectMixin, View):

View File

@ -1,12 +1,12 @@
from django.forms import models as model_forms from django.forms import models as model_forms
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.views.generic.base import TemplateResponseMixin, View from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
from django.views.generic.detail import (SingleObjectMixin, from django.views.generic.detail import (SingleObjectMixin,
SingleObjectTemplateResponseMixin, BaseDetailView) SingleObjectTemplateResponseMixin, BaseDetailView)
class FormMixin(object): class FormMixin(ContextMixin):
""" """
A mixin that provides a way to show and handle a form in a request. A mixin that provides a way to show and handle a form in a request.
""" """
@ -45,9 +45,6 @@ class FormMixin(object):
}) })
return kwargs return kwargs
def get_context_data(self, **kwargs):
return kwargs
def get_success_url(self): def get_success_url(self):
if self.success_url: if self.success_url:
url = self.success_url url = self.success_url
@ -113,13 +110,14 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
return super(ModelFormMixin, self).form_valid(form) return super(ModelFormMixin, self).form_valid(form)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = kwargs context = {}
if self.object: if self.object:
context['object'] = self.object context['object'] = self.object
context_object_name = self.get_context_object_name(self.object) context_object_name = self.get_context_object_name(self.object)
if context_object_name: if context_object_name:
context[context_object_name] = self.object context[context_object_name] = self.object
return context context.update(kwargs)
return super(ModelFormMixin, self).get_context_data(**context)
class ProcessFormView(View): class ProcessFormView(View):

View File

@ -3,10 +3,10 @@ from django.core.exceptions import ImproperlyConfigured
from django.http import Http404 from django.http import Http404
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic.base import TemplateResponseMixin, View from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
class MultipleObjectMixin(object): class MultipleObjectMixin(ContextMixin):
allow_empty = True allow_empty = True
queryset = None queryset = None
model = None model = None
@ -103,10 +103,10 @@ class MultipleObjectMixin(object):
'is_paginated': False, 'is_paginated': False,
'object_list': queryset 'object_list': queryset
} }
context.update(kwargs)
if context_object_name is not None: if context_object_name is not None:
context[context_object_name] = queryset context[context_object_name] = queryset
return context context.update(kwargs)
return super(MultipleObjectMixin, self).get_context_data(**context)
class BaseListView(MultipleObjectMixin, View): class BaseListView(MultipleObjectMixin, View):

View File

@ -270,6 +270,16 @@ more::
context['book_list'] = Book.objects.all() context['book_list'] = Book.objects.all()
return context return context
.. note::
Generally, get_context_data will merge the context data of all parent classes
with those of the current class. To preserve this behavior in your own classes
where you want to alter the context, you should be sure to call
get_context_data on the super class. When no two classes try to define the same
key, this will give the expected results. However if any class attempts to
override a key after parent classes have set it (after the call to super), any
children of that class will also need to explictly set it after super if they
want to be sure to override all parents.
Viewing subsets of objects Viewing subsets of objects
-------------------------- --------------------------

View File

@ -1,3 +1,5 @@
from __future__ import absolute_import
import time import time
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
@ -6,6 +8,7 @@ from django.test import TestCase, RequestFactory
from django.utils import unittest from django.utils import unittest
from django.views.generic import View, TemplateView, RedirectView from django.views.generic import View, TemplateView, RedirectView
from . import views
class SimpleView(View): class SimpleView(View):
""" """
@ -331,3 +334,19 @@ class RedirectViewTest(unittest.TestCase):
# we can't use self.rf.get because it always sets QUERY_STRING # we can't use self.rf.get because it always sets QUERY_STRING
response = RedirectView.as_view(url='/bar/')(self.rf.request(PATH_INFO='/foo/')) response = RedirectView.as_view(url='/bar/')(self.rf.request(PATH_INFO='/foo/'))
self.assertEqual(response.status_code, 301) self.assertEqual(response.status_code, 301)
class GetContextDataTest(unittest.TestCase):
def test_get_context_data_super(self):
test_view = views.CustomContextView()
context = test_view.get_context_data(kwarg_test='kwarg_value')
# the test_name key is inserted by the test classes parent
self.assertTrue('test_name' in context)
self.assertEqual(context['kwarg_test'], 'kwarg_value')
self.assertEqual(context['custom_key'], 'custom_value')
# test that kwarg overrides values assigned higher up
context = test_view.get_context_data(test_name='test_value')
self.assertEqual(context['test_name'], 'test_value')

View File

@ -1,6 +1,7 @@
from __future__ import absolute_import from __future__ import absolute_import
from .base import ViewTest, TemplateViewTest, RedirectViewTest from .base import (ViewTest, TemplateViewTest, RedirectViewTest,
GetContextDataTest)
from .dates import (ArchiveIndexViewTests, YearArchiveViewTests, from .dates import (ArchiveIndexViewTests, YearArchiveViewTests,
MonthArchiveViewTests, WeekArchiveViewTests, DayArchiveViewTests, MonthArchiveViewTests, WeekArchiveViewTests, DayArchiveViewTests,
DateDetailViewTests) DateDetailViewTests)

View File

@ -14,10 +14,9 @@ class CustomTemplateView(generic.TemplateView):
template_name = 'generic_views/about.html' template_name = 'generic_views/about.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
return { context = super(CustomTemplateView, self).get_context_data(**kwargs)
'params': kwargs, context.update({'key': 'value'})
'key': 'value' return context
}
class ObjectDetail(generic.DetailView): class ObjectDetail(generic.DetailView):
@ -184,3 +183,18 @@ class BookDetailGetObjectCustomQueryset(BookDetail):
def get_object(self, queryset=None): def get_object(self, queryset=None):
return super(BookDetailGetObjectCustomQueryset,self).get_object( return super(BookDetailGetObjectCustomQueryset,self).get_object(
queryset=Book.objects.filter(pk=2)) queryset=Book.objects.filter(pk=2))
class CustomContextView(generic.detail.SingleObjectMixin, generic.View):
model = Book
object = Book(name='dummy')
def get_object(self):
return Book(name="dummy")
def get_context_data(self, **kwargs):
context = {'custom_key': 'custom_value'}
context.update(kwargs)
return super(CustomContextView, self).get_context_data(**context)
def get_context_object_name(self, obj):
return "test_name"