[2.0.x] Fixed #29296 -- Fixed crashes in admindocs when a view is a callable object.

Backport of 33a0b7ac81 from master
This commit is contained in:
Paul Donohue 2018-04-08 13:35:24 -04:00 committed by Tim Graham
parent d5018abf1c
commit 1ed31efb87
10 changed files with 37 additions and 7 deletions

View File

@ -629,6 +629,7 @@ answer newbie questions, and generally made Django that much better:
Paul Bissex <http://e-scribe.com/> Paul Bissex <http://e-scribe.com/>
Paul Collier <paul@paul-collier.com> Paul Collier <paul@paul-collier.com>
Paul Collins <paul.collins.iii@gmail.com> Paul Collins <paul.collins.iii@gmail.com>
Paul Donohue <django@PaulSD.com>
Paul Lanier <planier@google.com> Paul Lanier <planier@google.com>
Paul McLanahan <paul@mclanahan.net> Paul McLanahan <paul@mclanahan.net>
Paul McMillan <Paul@McMillan.ws> Paul McMillan <Paul@McMillan.ws>

View File

@ -2,6 +2,8 @@ from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from .utils import get_view_name
class XViewMiddleware(MiddlewareMixin): class XViewMiddleware(MiddlewareMixin):
""" """
@ -24,5 +26,5 @@ class XViewMiddleware(MiddlewareMixin):
if request.method == 'HEAD' and (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or if request.method == 'HEAD' and (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or
(request.user.is_active and request.user.is_staff)): (request.user.is_active and request.user.is_staff)):
response = HttpResponse() response = HttpResponse()
response['X-View'] = "%s.%s" % (view_func.__module__, view_func.__name__) response['X-View'] = get_view_name(view_func)
return response return response

View File

@ -18,6 +18,12 @@ else:
docutils_is_available = True docutils_is_available = True
def get_view_name(view_func):
mod_name = view_func.__module__
view_name = getattr(view_func, '__qualname__', view_func.__class__.__name__)
return mod_name + '.' + view_name
def trim_docstring(docstring): def trim_docstring(docstring):
""" """
Uniformly trim leading/trailing whitespace from docstrings. Uniformly trim leading/trailing whitespace from docstrings.

View File

@ -23,6 +23,8 @@ from django.utils.inspect import (
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import TemplateView from django.views.generic import TemplateView
from .utils import get_view_name
# Exclude methods starting with these strings from documentation # Exclude methods starting with these strings from documentation
MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_') MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
@ -128,18 +130,13 @@ class TemplateFilterIndexView(BaseAdminDocsView):
class ViewIndexView(BaseAdminDocsView): class ViewIndexView(BaseAdminDocsView):
template_name = 'admin_doc/view_index.html' template_name = 'admin_doc/view_index.html'
@staticmethod
def _get_full_name(func):
mod_name = func.__module__
return '%s.%s' % (mod_name, func.__qualname__)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
views = [] views = []
urlconf = import_module(settings.ROOT_URLCONF) urlconf = import_module(settings.ROOT_URLCONF)
view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns) view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
for (func, regex, namespace, name) in view_functions: for (func, regex, namespace, name) in view_functions:
views.append({ views.append({
'full_name': self._get_full_name(func), 'full_name': get_view_name(func),
'url': simplify_regex(regex), 'url': simplify_regex(regex),
'url_name': ':'.join((namespace or []) + (name and [name] or [])), 'url_name': ':'.join((namespace or []) + (name and [name] or [])),
'namespace': ':'.join((namespace or [])), 'namespace': ':'.join((namespace or [])),

View File

@ -12,3 +12,6 @@ Bugfixes
* Fixed a regression in Django 1.11.8 where altering a field with a unique * Fixed a regression in Django 1.11.8 where altering a field with a unique
constraint may drop and rebuild more foreign keys than necessary constraint may drop and rebuild more foreign keys than necessary
(:ticket:`29193`). (:ticket:`29193`).
* Fixed crashes in ``django.contrib.admindocs`` when a view is a callable
object, such as ``django.contrib.syndication.views.Feed`` (:ticket:`29296`).

View File

@ -15,3 +15,6 @@ Bugfixes
* Fixed a regression in Django 1.11.8 where altering a field with a unique * Fixed a regression in Django 1.11.8 where altering a field with a unique
constraint may drop and rebuild more foreign keys than necessary constraint may drop and rebuild more foreign keys than necessary
(:ticket:`29193`). (:ticket:`29193`).
* Fixed crashes in ``django.contrib.admindocs`` when a view is a callable
object, such as ``django.contrib.syndication.views.Feed`` (:ticket:`29296`).

View File

@ -40,3 +40,8 @@ class XViewMiddlewareTest(TestDataMixin, AdminDocsTestCase):
user.save() user.save()
response = self.client.head('/xview/class/') response = self.client.head('/xview/class/')
self.assertNotIn('X-View', response) self.assertNotIn('X-View', response)
def test_callable_object_view(self):
self.client.force_login(self.superuser)
response = self.client.head('/xview/callable_object/')
self.assertEqual(response['X-View'], 'admin_docs.views.XViewCallableObject')

View File

@ -51,6 +51,12 @@ class AdminDocViewTests(TestDataMixin, AdminDocsTestCase):
) )
self.assertContains(response, 'Views by namespace test') self.assertContains(response, 'Views by namespace test')
self.assertContains(response, 'Name: <code>test:func</code>.') self.assertContains(response, 'Name: <code>test:func</code>.')
self.assertContains(
response,
'<h3><a href="/admindocs/views/admin_docs.views.XViewCallableObject/">'
'/xview/callable_object_without_xview/</a></h3>',
html=True,
)
def test_view_index_with_method(self): def test_view_index_with_method(self):
""" """

View File

@ -13,4 +13,6 @@ urlpatterns = [
url(r'^', include(ns_patterns, namespace='test')), url(r'^', include(ns_patterns, namespace='test')),
url(r'^xview/func/$', views.xview_dec(views.xview)), url(r'^xview/func/$', views.xview_dec(views.xview)),
url(r'^xview/class/$', views.xview_dec(views.XViewClass.as_view())), url(r'^xview/class/$', views.xview_dec(views.XViewClass.as_view())),
url(r'^xview/callable_object/$', views.xview_dec(views.XViewCallableObject())),
url(r'^xview/callable_object_without_xview/$', views.XViewCallableObject()),
] ]

View File

@ -13,3 +13,8 @@ def xview(request):
class XViewClass(View): class XViewClass(View):
def get(self, request): def get(self, request):
return HttpResponse() return HttpResponse()
class XViewCallableObject(View):
def __call__(self, request):
return HttpResponse()