Updated admindocs to use class-based views

Thanks Bouke Haarsma for the review.
This commit is contained in:
Claude Paroz 2013-11-21 08:48:29 +01:00
parent 1718b5256c
commit d6cc37d601
6 changed files with 361 additions and 287 deletions

View File

@ -4,7 +4,7 @@
{% block breadcrumbs %} {% block breadcrumbs %}
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a> <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; {% trans 'Documentation' %}</a> &rsaquo; {% trans 'Documentation' %}
</div> </div>
{% endblock %} {% endblock %}
{% block title %}{% trans 'Documentation' %}{% endblock %} {% block title %}{% trans 'Documentation' %}{% endblock %}

View File

@ -4,7 +4,7 @@
{% block breadcrumbs %} {% block breadcrumbs %}
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a> <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; {% trans 'Documentation' %}</a> &rsaquo; {% trans 'Documentation' %}
</div> </div>
{% endblock %} {% endblock %}
{% block title %}{% trans 'Please install docutils' %}{% endblock %} {% block title %}{% trans 'Please install docutils' %}{% endblock %}

View File

@ -3,39 +3,39 @@ from django.contrib.admindocs import views
urlpatterns = patterns('', urlpatterns = patterns('',
url('^$', url('^$',
views.doc_index, views.BaseAdminDocsView.as_view(template_name='admin_doc/index.html'),
name='django-admindocs-docroot' name='django-admindocs-docroot'
), ),
url('^bookmarklets/$', url('^bookmarklets/$',
views.bookmarklets, views.BookmarkletsView.as_view(),
name='django-admindocs-bookmarklets' name='django-admindocs-bookmarklets'
), ),
url('^tags/$', url('^tags/$',
views.template_tag_index, views.TemplateTagIndexView.as_view(),
name='django-admindocs-tags' name='django-admindocs-tags'
), ),
url('^filters/$', url('^filters/$',
views.template_filter_index, views.TemplateFilterIndexView.as_view(),
name='django-admindocs-filters' name='django-admindocs-filters'
), ),
url('^views/$', url('^views/$',
views.view_index, views.ViewIndexView.as_view(),
name='django-admindocs-views-index' name='django-admindocs-views-index'
), ),
url('^views/(?P<view>[^/]+)/$', url('^views/(?P<view>[^/]+)/$',
views.view_detail, views.ViewDetailView.as_view(),
name='django-admindocs-views-detail' name='django-admindocs-views-detail'
), ),
url('^models/$', url('^models/$',
views.model_index, views.ModelIndexView.as_view(),
name='django-admindocs-models-index' name='django-admindocs-models-index'
), ),
url('^models/(?P<app_label>[^\.]+)\.(?P<model_name>[^/]+)/$', url('^models/(?P<app_label>[^\.]+)\.(?P<model_name>[^/]+)/$',
views.model_detail, views.ModelDetailView.as_view(),
name='django-admindocs-models-detail' name='django-admindocs-models-detail'
), ),
url('^templates/(?P<template>.*)/$', url('^templates/(?P<template>.*)/$',
views.template_detail, views.TemplateDetailView.as_view(),
name='django-admindocs-templates' name='django-admindocs-templates'
), ),
) )

View File

@ -4,19 +4,19 @@ import os
import re import re
from django import template from django import template
from django.template import RequestContext
from django.conf import settings from django.conf import settings
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.db import models from django.db import models
from django.shortcuts import render_to_response
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.http import Http404 from django.http import Http404
from django.core import urlresolvers from django.core import urlresolvers
from django.contrib.admindocs import utils from django.contrib.admindocs import utils
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.utils.decorators import method_decorator
from django.utils._os import upath from django.utils._os import upath
from django.utils import six from django.utils import six
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic import TemplateView
# 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_')
@ -27,29 +27,39 @@ class GenericSite(object):
name = 'my site' name = 'my site'
@staff_member_required class BaseAdminDocsView(TemplateView):
def doc_index(request): """
Base view for admindocs views.
"""
@method_decorator(staff_member_required)
def dispatch(self, *args, **kwargs):
if not utils.docutils_is_available: if not utils.docutils_is_available:
return missing_docutils_page(request) # Display an error message for people without docutils
return render_to_response('admin_doc/index.html', { self.template_name = 'admin_doc/missing_docutils.html'
'root_path': urlresolvers.reverse('admin:index'), return self.render_to_response({})
}, context_instance=RequestContext(request)) return super(BaseAdminDocsView, self).dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
kwargs.update({'root_path': urlresolvers.reverse('admin:index')})
return super(BaseAdminDocsView, self).get_context_data(**kwargs)
@staff_member_required class BookmarkletsView(BaseAdminDocsView):
def bookmarklets(request): template_name = 'admin_doc/bookmarklets.html'
admin_root = urlresolvers.reverse('admin:index')
return render_to_response('admin_doc/bookmarklets.html', { def get_context_data(self, **kwargs):
'root_path': admin_root, context = super(BookmarkletsView, self).get_context_data(**kwargs)
'admin_url': "%s://%s%s" % (request.scheme, request.get_host(), admin_root), context.update({
}, context_instance=RequestContext(request)) 'admin_url': "%s://%s%s" % (
self.request.scheme, self.request.get_host(), context['root_path'])
})
return context
@staff_member_required class TemplateTagIndexView(BaseAdminDocsView):
def template_tag_index(request): template_name = 'admin_doc/template_tag_index.html'
if not utils.docutils_is_available:
return missing_docutils_page(request)
def get_context_data(self, **kwargs):
load_all_installed_template_libraries() load_all_installed_template_libraries()
tags = [] tags = []
@ -75,17 +85,14 @@ def template_tag_index(request):
'meta': metadata, 'meta': metadata,
'library': tag_library, 'library': tag_library,
}) })
return render_to_response('admin_doc/template_tag_index.html', { kwargs.update({'tags': tags})
'root_path': urlresolvers.reverse('admin:index'), return super(TemplateTagIndexView, self).get_context_data(**kwargs)
'tags': tags
}, context_instance=RequestContext(request))
@staff_member_required class TemplateFilterIndexView(BaseAdminDocsView):
def template_filter_index(request): template_name = 'admin_doc/template_filter_index.html'
if not utils.docutils_is_available:
return missing_docutils_page(request)
def get_context_data(self, **kwargs):
load_all_installed_template_libraries() load_all_installed_template_libraries()
filters = [] filters = []
@ -111,17 +118,14 @@ def template_filter_index(request):
'meta': metadata, 'meta': metadata,
'library': tag_library, 'library': tag_library,
}) })
return render_to_response('admin_doc/template_filter_index.html', { kwargs.update({'filters': filters})
'root_path': urlresolvers.reverse('admin:index'), return super(TemplateFilterIndexView, self).get_context_data(**kwargs)
'filters': filters
}, context_instance=RequestContext(request))
@staff_member_required class ViewIndexView(BaseAdminDocsView):
def view_index(request): template_name = 'admin_doc/view_index.html'
if not utils.docutils_is_available:
return missing_docutils_page(request)
def get_context_data(self, **kwargs):
if settings.ADMIN_FOR: if settings.ADMIN_FOR:
settings_modules = [import_module(m) for m in settings.ADMIN_FOR] settings_modules = [import_module(m) for m in settings.ADMIN_FOR]
else: else:
@ -142,17 +146,15 @@ def view_index(request):
'site': site_obj, 'site': site_obj,
'url': simplify_regex(regex), 'url': simplify_regex(regex),
}) })
return render_to_response('admin_doc/view_index.html', { kwargs.update({'views': views})
'root_path': urlresolvers.reverse('admin:index'), return super(ViewIndexView, self).get_context_data(**kwargs)
'views': views
}, context_instance=RequestContext(request))
@staff_member_required class ViewDetailView(BaseAdminDocsView):
def view_detail(request, view): template_name = 'admin_doc/view_detail.html'
if not utils.docutils_is_available:
return missing_docutils_page(request)
def get_context_data(self, **kwargs):
view = self.kwargs['view']
mod, func = urlresolvers.get_mod_func(view) mod, func = urlresolvers.get_mod_func(view)
try: try:
view_func = getattr(import_module(mod), func) view_func = getattr(import_module(mod), func)
@ -165,43 +167,41 @@ def view_detail(request, view):
body = utils.parse_rst(body, 'view', _('view:') + view) body = utils.parse_rst(body, 'view', _('view:') + view)
for key in metadata: for key in metadata:
metadata[key] = utils.parse_rst(metadata[key], 'model', _('view:') + view) metadata[key] = utils.parse_rst(metadata[key], 'model', _('view:') + view)
return render_to_response('admin_doc/view_detail.html', { kwargs.update({
'root_path': urlresolvers.reverse('admin:index'),
'name': view, 'name': view,
'summary': title, 'summary': title,
'body': body, 'body': body,
'meta': metadata, 'meta': metadata,
}, context_instance=RequestContext(request)) })
return super(ViewDetailView, self).get_context_data(**kwargs)
@staff_member_required class ModelIndexView(BaseAdminDocsView):
def model_index(request): template_name = 'admin_doc/model_index.html'
if not utils.docutils_is_available:
return missing_docutils_page(request) def get_context_data(self, **kwargs):
m_list = [m._meta for m in models.get_models()] m_list = [m._meta for m in models.get_models()]
return render_to_response('admin_doc/model_index.html', { kwargs.update({'models': m_list})
'root_path': urlresolvers.reverse('admin:index'), return super(ModelIndexView, self).get_context_data(**kwargs)
'models': m_list
}, context_instance=RequestContext(request))
@staff_member_required class ModelDetailView(BaseAdminDocsView):
def model_detail(request, app_label, model_name): template_name = 'admin_doc/model_detail.html'
if not utils.docutils_is_available:
return missing_docutils_page(request)
def get_context_data(self, **kwargs):
# Get the model class. # Get the model class.
try: try:
app_mod = models.get_app(app_label) app_mod = models.get_app(self.kwargs['app_label'])
except ImproperlyConfigured: except ImproperlyConfigured:
raise Http404(_("App %r not found") % app_label) raise Http404(_("App %r not found") % self.kwargs['app_label'])
model = None model = None
for m in models.get_models(app_mod): for m in models.get_models(app_mod):
if m._meta.model_name == model_name: if m._meta.model_name == self.kwargs['model_name']:
model = m model = m
break break
if model is None: if model is None:
raise Http404(_("Model %(model_name)r not found in app %(app_label)r") % {'model_name': model_name, 'app_label': app_label}) raise Http404(_("Model %(model_name)r not found in app %(app_label)r") % {
'model_name': self.kwargs['model_name'], 'app_label': self.kwargs['app_label']})
opts = model._meta opts = model._meta
@ -278,18 +278,21 @@ def model_detail(request, app_label, model_name):
'data_type': 'Integer', 'data_type': 'Integer',
'verbose': utils.parse_rst(_("number of %s") % verbose, 'model', _('model:') + opts.model_name), 'verbose': utils.parse_rst(_("number of %s") % verbose, 'model', _('model:') + opts.model_name),
}) })
return render_to_response('admin_doc/model_detail.html', { kwargs.update({
'root_path': urlresolvers.reverse('admin:index'),
'name': '%s.%s' % (opts.app_label, opts.object_name), 'name': '%s.%s' % (opts.app_label, opts.object_name),
# Translators: %s is an object type name # Translators: %s is an object type name
'summary': _("Attributes on %s objects") % opts.object_name, 'summary': _("Attributes on %s objects") % opts.object_name,
'description': model.__doc__, 'description': model.__doc__,
'fields': fields, 'fields': fields,
}, context_instance=RequestContext(request)) })
return super(ModelDetailView, self).get_context_data(**kwargs)
@staff_member_required class TemplateDetailView(BaseAdminDocsView):
def template_detail(request, template): template_name = 'admin_doc/template_detail.html'
def get_context_data(self, **kwargs):
template = self.kwargs['template']
templates = [] templates = []
for site_settings_module in settings.ADMIN_FOR: for site_settings_module in settings.ADMIN_FOR:
settings_mod = import_module(site_settings_module) settings_mod = import_module(site_settings_module)
@ -307,22 +310,17 @@ def template_detail(request, template):
'site': site_obj, 'site': site_obj,
'order': list(settings_mod.TEMPLATE_DIRS).index(dir), 'order': list(settings_mod.TEMPLATE_DIRS).index(dir),
}) })
return render_to_response('admin_doc/template_detail.html', { kwargs.update({
'root_path': urlresolvers.reverse('admin:index'),
'name': template, 'name': template,
'templates': templates, 'templates': templates,
}, context_instance=RequestContext(request)) })
return super(TemplateDetailView, self).get_context_data(**kwargs)
#################### ####################
# Helper functions # # Helper functions #
#################### ####################
def missing_docutils_page(request):
"""Display an error message for people without docutils"""
return render_to_response('admin_doc/missing_docutils.html')
def load_all_installed_template_libraries(): def load_all_installed_template_libraries():
# Load/register all template tag libraries from installed apps. # Load/register all template tag libraries from installed apps.
for module_name in template.get_templatetags_modules(): for module_name in template.get_templatetags_modules():

View File

@ -1,16 +1,89 @@
import unittest import unittest
try:
import docutils
except ImportError:
docutils = None
from django.contrib.admindocs import utils from django.contrib.admindocs import utils
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@unittest.skipUnless(utils.docutils_is_available, "no docutils installed.")
class AdminDocViewTests(TestCase):
fixtures = ['data.xml']
urls = 'admin_docs.urls'
def setUp(self):
self.client.login(username='super', password='secret')
def test_index(self):
self.client.logout()
response = self.client.get(reverse('django-admindocs-docroot'))
# Should display the login screen
self.assertContains(response,
'<input type="hidden" name="next" value="/admindocs/" />', html=True)
self.client.login(username='super', password='secret')
response = self.client.get(reverse('django-admindocs-docroot'))
self.assertContains(response, '<h1>Documentation</h1>', html=True)
def test_bookmarklets(self):
response = self.client.get(reverse('django-admindocs-bookmarklets'))
self.assertContains(response, 'http://testserver/admin/doc/views/')
def test_templatetag_index(self):
response = self.client.get(reverse('django-admindocs-tags'))
self.assertContains(response, '<h3 id="built_in-extends">extends</h3>', html=True)
def test_templatefilter_index(self):
response = self.client.get(reverse('django-admindocs-filters'))
self.assertContains(response, '<h3 id="built_in-first">first</h3>', html=True)
def test_view_index(self):
response = self.client.get(reverse('django-admindocs-views-index'))
self.assertContains(response,
'<h3><a href="/admindocs/views/django.contrib.admindocs.views.BaseAdminDocsView/">/admindocs/</a></h3>',
html=True)
def test_view_detail(self):
response = self.client.get(
reverse('django-admindocs-views-detail',
args=['django.contrib.admindocs.views.BaseAdminDocsView']))
# View docstring
self.assertContains(response, 'Base view for admindocs views.')
def test_model_index(self):
response = self.client.get(reverse('django-admindocs-models-index'))
self.assertContains(response, '<h2 id="app-auth">Auth</h2>', html=True)
def test_model_detail(self):
response = self.client.get(reverse('django-admindocs-models-detail',
args=['auth', 'user']))
# Check for attribute, many-to-many field
self.assertContains(response,
'<tr><td>email</td><td>Email address</td><td>email address</td></tr>', html=True)
self.assertContains(response,
'<tr><td>user_permissions.all</td><td>List</td><td>'
'<p>all related <a class="reference external" href="/admindocs/models/auth.permission/">'
'auth.Permission</a> objects</p></td></tr>', html=True)
def test_template_detail(self):
response = self.client.get(reverse('django-admindocs-templates',
args=['admin_doc/template_detail.html']))
self.assertContains(response,
'<h1>Template: "admin_doc/template_detail.html"</h1>', html=True)
def test_missing_docutils(self):
utils.docutils_is_available = False
try:
response = self.client.get(reverse('django-admindocs-docroot'))
self.assertContains(response,
'<h3>The admin documentation system requires Python\'s '
'<a href="http://docutils.sf.net/">docutils</a> library.</h3>',
html=True)
finally:
utils.docutils_is_available = True
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class XViewMiddlewareTest(TestCase): class XViewMiddlewareTest(TestCase):
fixtures = ['data.xml'] fixtures = ['data.xml']
@ -53,7 +126,7 @@ class XViewMiddlewareTest(TestCase):
self.assertFalse('X-View' in response) self.assertFalse('X-View' in response)
@unittest.skipUnless(docutils, "no docutils installed.") @unittest.skipUnless(utils.docutils_is_available, "no docutils installed.")
class DefaultRoleTest(TestCase): class DefaultRoleTest(TestCase):
urls = 'admin_docs.urls' urls = 'admin_docs.urls'
@ -81,6 +154,7 @@ class DefaultRoleTest(TestCase):
when ``publish_parts`` is used directly, by setting it to when ``publish_parts`` is used directly, by setting it to
``cmsreference``. See #6681. ``cmsreference``. See #6681.
""" """
import docutils
self.assertNotEqual(docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE, self.assertNotEqual(docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE,
'cmsreference') 'cmsreference')
source = 'reST, `interpreted text`, default role.' source = 'reST, `interpreted text`, default role.'

View File

@ -1,8 +1,10 @@
from django.conf.urls import include, patterns from django.conf.urls import include, patterns
from django.contrib import admin
from . import views from . import views
urlpatterns = patterns('', urlpatterns = patterns('',
(r'^admin/', include(admin.site.urls)),
(r'^admindocs/', include('django.contrib.admindocs.urls')), (r'^admindocs/', include('django.contrib.admindocs.urls')),
(r'^xview/func/$', views.xview_dec(views.xview)), (r'^xview/func/$', views.xview_dec(views.xview)),
(r'^xview/class/$', views.xview_dec(views.XViewClass.as_view())), (r'^xview/class/$', views.xview_dec(views.XViewClass.as_view())),