Fixed #19878 -- Deprecated TemplateView passing URL kwargs into context.

This commit is contained in:
Adam Johnson 2019-08-15 06:48:33 +01:00 committed by Mariusz Felisiak
parent f982f0bdb8
commit 4ed534758c
6 changed files with 84 additions and 29 deletions

View File

@ -1,4 +1,5 @@
import logging import logging
import warnings
from functools import update_wrapper from functools import update_wrapper
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
@ -9,6 +10,8 @@ from django.http import (
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.urls import reverse from django.urls import reverse
from django.utils.decorators import classonlymethod from django.utils.decorators import classonlymethod
from django.utils.deprecation import RemovedInDjango40Warning
from django.utils.functional import SimpleLazyObject
logger = logging.getLogger('django.request') logger = logging.getLogger('django.request')
@ -152,14 +155,33 @@ class TemplateResponseMixin:
class TemplateView(TemplateResponseMixin, ContextMixin, View): class TemplateView(TemplateResponseMixin, ContextMixin, View):
""" """Render a template."""
Render a template. Pass keyword arguments from the URLconf to the context.
"""
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs) # RemovedInDjango40Warning: when the deprecation ends, replace with:
# context = self.get_context_data()
context_kwargs = _wrap_url_kwargs_with_deprecation_warning(kwargs)
context = self.get_context_data(**context_kwargs)
return self.render_to_response(context) return self.render_to_response(context)
# RemovedInDjango40Warning
def _wrap_url_kwargs_with_deprecation_warning(url_kwargs):
context_kwargs = {}
for key, value in url_kwargs.items():
# Bind into function closure.
@SimpleLazyObject
def access_value(key=key, value=value):
warnings.warn(
'TemplateView passing URL kwargs to the context is '
'deprecated. Reference %s in your template through '
'view.kwargs instead.' % key,
RemovedInDjango40Warning, stacklevel=2,
)
return value
context_kwargs[key] = access_value
return context_kwargs
class RedirectView(View): class RedirectView(View):
"""Provide a redirect on any GET request.""" """Provide a redirect on any GET request."""
permanent = False permanent = False

View File

@ -66,6 +66,9 @@ details on these changes.
* The ``list`` message for ``ModelMultipleChoiceField`` will be removed. * The ``list`` message for ``ModelMultipleChoiceField`` will be removed.
* ``django.views.generic.TemplateView`` will no longer pass URL kwargs directly
to the ``context``.
See the :ref:`Django 3.1 release notes <deprecated-features-3.1>` for more See the :ref:`Django 3.1 release notes <deprecated-features-3.1>` for more
details on these changes. details on these changes.

View File

@ -117,8 +117,7 @@ MRO is an acronym for Method Resolution Order.
.. class:: django.views.generic.base.TemplateView .. class:: django.views.generic.base.TemplateView
Renders a given template, with the context containing parameters captured Renders a given template.
in the URL.
**Ancestors (MRO)** **Ancestors (MRO)**
@ -162,12 +161,17 @@ MRO is an acronym for Method Resolution Order.
**Context** **Context**
* Populated (through :class:`~django.views.generic.base.ContextMixin`) with * Populated (through :class:`~django.views.generic.base.ContextMixin`).
the keyword arguments captured from the URL pattern that served the view.
* You can also add context using the * You can also add context using the
:attr:`~django.views.generic.base.ContextMixin.extra_context` keyword :attr:`~django.views.generic.base.ContextMixin.extra_context` keyword
argument for :meth:`~django.views.generic.base.View.as_view`. argument for :meth:`~django.views.generic.base.View.as_view`.
.. deprecated:: 3.1
Starting in Django 4.0, the keyword arguments captured from the URL
pattern won't be passed to the context. Reference them with
``view.kwargs`` instead.
``RedirectView`` ``RedirectView``
================ ================

View File

@ -640,6 +640,10 @@ Miscellaneous
* The ``list`` message for :class:`~django.forms.ModelMultipleChoiceField` is * The ``list`` message for :class:`~django.forms.ModelMultipleChoiceField` is
deprecated in favor of ``invalid_list``. deprecated in favor of ``invalid_list``.
* The passing of URL kwargs directly to the context by
:class:`~django.views.generic.base.TemplateView` is deprecated. Reference
them in the template with ``view.kwargs`` instead.
.. _removed-features-3.1: .. _removed-features-3.1:
Features removed in 3.1 Features removed in 3.1

View File

@ -2,9 +2,12 @@ import time
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponse from django.http import HttpResponse
from django.test import RequestFactory, SimpleTestCase, override_settings from django.test import (
RequestFactory, SimpleTestCase, ignore_warnings, override_settings,
)
from django.test.utils import require_jinja2 from django.test.utils import require_jinja2
from django.urls import resolve from django.urls import resolve
from django.utils.deprecation import RemovedInDjango40Warning
from django.views.generic import RedirectView, TemplateView, View from django.views.generic import RedirectView, TemplateView, View
from . import views from . import views
@ -347,25 +350,6 @@ class TemplateViewTest(SimpleTestCase):
view = TemplateView.as_view(template_name='generic_views/using.html', template_engine='jinja2') view = TemplateView.as_view(template_name='generic_views/using.html', template_engine='jinja2')
self.assertEqual(view(request).render().content, b'Jinja2\n') self.assertEqual(view(request).render().content, b'Jinja2\n')
def test_template_params(self):
"""
A generic template view passes kwargs as context.
"""
response = self.client.get('/template/simple/bar/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['foo'], 'bar')
self.assertIsInstance(response.context['view'], View)
def test_extra_template_params(self):
"""
A template view can be customized to return extra context.
"""
response = self.client.get('/template/custom/bar/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['foo'], 'bar')
self.assertEqual(response.context['key'], 'value')
self.assertIsInstance(response.context['view'], View)
def test_cached_views(self): def test_cached_views(self):
""" """
A template view can be cached A template view can be cached
@ -584,3 +568,38 @@ class SingleObjectTemplateResponseMixinTest(SimpleTestCase):
) )
with self.assertRaisesMessage(ImproperlyConfigured, msg): with self.assertRaisesMessage(ImproperlyConfigured, msg):
view.get_template_names() view.get_template_names()
@override_settings(ROOT_URLCONF='generic_views.urls')
class DeprecationTests(SimpleTestCase):
@ignore_warnings(category=RemovedInDjango40Warning)
def test_template_params(self):
"""A generic template view passes kwargs as context."""
response = self.client.get('/template/simple/bar/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['foo'], 'bar')
self.assertIsInstance(response.context['view'], View)
@ignore_warnings(category=RemovedInDjango40Warning)
def test_extra_template_params(self):
"""A template view can be customized to return extra context."""
response = self.client.get('/template/custom/bar1/bar2/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['foo1'], 'bar1')
self.assertEqual(response.context['foo2'], 'bar2')
self.assertEqual(response.context['key'], 'value')
self.assertIsInstance(response.context['view'], View)
def test_template_params_warning(self):
response = self.client.get('/template/custom/bar1/bar2/')
self.assertEqual(response.status_code, 200)
msg = (
'TemplateView passing URL kwargs to the context is deprecated. '
'Reference %s in your template through view.kwargs instead.'
)
with self.assertRaisesMessage(RemovedInDjango40Warning, msg % 'foo1'):
str(response.context['foo1'])
with self.assertRaisesMessage(RemovedInDjango40Warning, msg % 'foo2'):
str(response.context['foo2'])
self.assertEqual(response.context['key'], 'value')
self.assertIsInstance(response.context['view'], View)

View File

@ -12,7 +12,10 @@ urlpatterns = [
path('template/no_template/', TemplateView.as_view()), path('template/no_template/', TemplateView.as_view()),
path('template/login_required/', login_required(TemplateView.as_view())), path('template/login_required/', login_required(TemplateView.as_view())),
path('template/simple/<foo>/', TemplateView.as_view(template_name='generic_views/about.html')), path('template/simple/<foo>/', TemplateView.as_view(template_name='generic_views/about.html')),
path('template/custom/<foo>/', views.CustomTemplateView.as_view(template_name='generic_views/about.html')), path(
'template/custom/<foo1>/<foo2>/',
views.CustomTemplateView.as_view(template_name='generic_views/about.html'),
),
path( path(
'template/content_type/', 'template/content_type/',
TemplateView.as_view(template_name='generic_views/robots.txt', content_type='text/plain'), TemplateView.as_view(template_name='generic_views/robots.txt', content_type='text/plain'),