[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:
parent
d5018abf1c
commit
1ed31efb87
1
AUTHORS
1
AUTHORS
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 [])),
|
||||||
|
|
|
@ -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`).
|
||||||
|
|
|
@ -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`).
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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()),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue