Fixed #11416 -- Restored use of the never_cache decorator on admin views. Thanks to Ramiro Morales and Michael Newmann for their work on the patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@11229 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
33ea28c2b2
commit
e992e57d3e
|
@ -159,9 +159,9 @@ class AdminSite(object):
|
||||||
if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
|
if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
|
||||||
raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
|
raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
|
||||||
|
|
||||||
def admin_view(self, view):
|
def admin_view(self, view, cacheable=False):
|
||||||
"""
|
"""
|
||||||
Decorator to create an "admin view attached to this ``AdminSite``. This
|
Decorator to create an admin view attached to this ``AdminSite``. This
|
||||||
wraps the view and provides permission checking by calling
|
wraps the view and provides permission checking by calling
|
||||||
``self.has_permission``.
|
``self.has_permission``.
|
||||||
|
|
||||||
|
@ -177,19 +177,25 @@ class AdminSite(object):
|
||||||
url(r'^my_view/$', self.admin_view(some_view))
|
url(r'^my_view/$', self.admin_view(some_view))
|
||||||
)
|
)
|
||||||
return urls
|
return urls
|
||||||
|
|
||||||
|
By default, admin_views are marked non-cacheable using the
|
||||||
|
``never_cache`` decorator. If the view can be safely cached, set
|
||||||
|
cacheable=True.
|
||||||
"""
|
"""
|
||||||
def inner(request, *args, **kwargs):
|
def inner(request, *args, **kwargs):
|
||||||
if not self.has_permission(request):
|
if not self.has_permission(request):
|
||||||
return self.login(request)
|
return self.login(request)
|
||||||
return view(request, *args, **kwargs)
|
return view(request, *args, **kwargs)
|
||||||
|
if not cacheable:
|
||||||
|
inner = never_cache(inner)
|
||||||
return update_wrapper(inner, view)
|
return update_wrapper(inner, view)
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
from django.conf.urls.defaults import patterns, url, include
|
from django.conf.urls.defaults import patterns, url, include
|
||||||
|
|
||||||
def wrap(view):
|
def wrap(view, cacheable=False):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
return self.admin_view(view)(*args, **kwargs)
|
return self.admin_view(view, cacheable)(*args, **kwargs)
|
||||||
return update_wrapper(wrapper, view)
|
return update_wrapper(wrapper, view)
|
||||||
|
|
||||||
# Admin-site-wide views.
|
# Admin-site-wide views.
|
||||||
|
@ -201,13 +207,13 @@ class AdminSite(object):
|
||||||
wrap(self.logout),
|
wrap(self.logout),
|
||||||
name='%sadmin_logout'),
|
name='%sadmin_logout'),
|
||||||
url(r'^password_change/$',
|
url(r'^password_change/$',
|
||||||
wrap(self.password_change),
|
wrap(self.password_change, cacheable=True),
|
||||||
name='%sadmin_password_change' % self.name),
|
name='%sadmin_password_change' % self.name),
|
||||||
url(r'^password_change/done/$',
|
url(r'^password_change/done/$',
|
||||||
wrap(self.password_change_done),
|
wrap(self.password_change_done, cacheable=True),
|
||||||
name='%sadmin_password_change_done' % self.name),
|
name='%sadmin_password_change_done' % self.name),
|
||||||
url(r'^jsi18n/$',
|
url(r'^jsi18n/$',
|
||||||
wrap(self.i18n_javascript),
|
wrap(self.i18n_javascript, cacheable=True),
|
||||||
name='%sadmin_jsi18n' % self.name),
|
name='%sadmin_jsi18n' % self.name),
|
||||||
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
|
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
|
||||||
'django.views.defaults.shortcut'),
|
'django.views.defaults.shortcut'),
|
||||||
|
|
|
@ -762,12 +762,19 @@ documented in :ref:`topics-http-urls`::
|
||||||
anything, so you'll usually want to prepend your custom URLs to the built-in
|
anything, so you'll usually want to prepend your custom URLs to the built-in
|
||||||
ones.
|
ones.
|
||||||
|
|
||||||
Note, however, that the ``self.my_view`` function registered above will *not*
|
However, the ``self.my_view`` function registered above suffers from two
|
||||||
have any permission check done; it'll be accessible to the general public. Since
|
problems:
|
||||||
this is usually not what you want, Django provides a convience wrapper to check
|
|
||||||
permissions. This wrapper is :meth:`AdminSite.admin_view` (i.e.
|
* It will *not* perform and permission checks, so it will be accessible to
|
||||||
``self.admin_site.admin_view`` inside a ``ModelAdmin`` instance); use it like
|
the general public.
|
||||||
so::
|
* It will *not* provide any header details to prevent caching. This means if
|
||||||
|
the page retrieves data from the database, and caching middleware is
|
||||||
|
active, the page could show outdated information.
|
||||||
|
|
||||||
|
Since this is usually not what you want, Django provides a convenience wrapper
|
||||||
|
to check permissions and mark the view as non-cacheable. This wrapper is
|
||||||
|
:meth:`AdminSite.admin_view` (i.e. ``self.admin_site.admin_view`` inside a
|
||||||
|
``ModelAdmin`` instance); use it like so:
|
||||||
|
|
||||||
class MyModelAdmin(admin.ModelAdmin):
|
class MyModelAdmin(admin.ModelAdmin):
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
|
@ -781,7 +788,14 @@ Notice the wrapped view in the fifth line above::
|
||||||
|
|
||||||
(r'^my_view/$', self.admin_site.admin_view(self.my_view))
|
(r'^my_view/$', self.admin_site.admin_view(self.my_view))
|
||||||
|
|
||||||
This wrapping will protect ``self.my_view`` from unauthorized access.
|
This wrapping will protect ``self.my_view`` from unauthorized access and will
|
||||||
|
apply the ``django.views.decorators.cache.never_cache`` decorator to make sure
|
||||||
|
it is not cached if the cache middleware is active.
|
||||||
|
|
||||||
|
If the page is cacheable, but you still want the permission check to be performed,
|
||||||
|
you can pass a ``cacheable=True`` argument to :meth:`AdminSite.admin_view`::
|
||||||
|
|
||||||
|
(r'^my_view/$', self.admin_site.admin_view(self.my_view, cacheable=True))
|
||||||
|
|
||||||
.. method:: ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)
|
.. method:: ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ from django.contrib.admin.models import LogEntry, DELETION
|
||||||
from django.contrib.admin.sites import LOGIN_FORM_KEY
|
from django.contrib.admin.sites import LOGIN_FORM_KEY
|
||||||
from django.contrib.admin.util import quote
|
from django.contrib.admin.util import quote
|
||||||
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
||||||
|
from django.utils.cache import get_max_age
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
|
||||||
# local test models
|
# local test models
|
||||||
|
@ -1527,3 +1528,76 @@ class AdminInlineTests(TestCase):
|
||||||
self.failUnlessEqual(Category.objects.get(id=2).order, 13)
|
self.failUnlessEqual(Category.objects.get(id=2).order, 13)
|
||||||
self.failUnlessEqual(Category.objects.get(id=3).order, 1)
|
self.failUnlessEqual(Category.objects.get(id=3).order, 1)
|
||||||
self.failUnlessEqual(Category.objects.get(id=4).order, 0)
|
self.failUnlessEqual(Category.objects.get(id=4).order, 0)
|
||||||
|
|
||||||
|
|
||||||
|
class NeverCacheTests(TestCase):
|
||||||
|
fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client.login(username='super', password='secret')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
def testAdminIndex(self):
|
||||||
|
"Check the never-cache status of the main index"
|
||||||
|
response = self.client.get('/test_admin/admin/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), 0)
|
||||||
|
|
||||||
|
def testAppIndex(self):
|
||||||
|
"Check the never-cache status of an application index"
|
||||||
|
response = self.client.get('/test_admin/admin/admin_views/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), 0)
|
||||||
|
|
||||||
|
def testModelIndex(self):
|
||||||
|
"Check the never-cache status of a model index"
|
||||||
|
response = self.client.get('/test_admin/admin/admin_views/fabric/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), 0)
|
||||||
|
|
||||||
|
def testModelAdd(self):
|
||||||
|
"Check the never-cache status of a model add page"
|
||||||
|
response = self.client.get('/test_admin/admin/admin_views/fabric/add/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), 0)
|
||||||
|
|
||||||
|
def testModelView(self):
|
||||||
|
"Check the never-cache status of a model edit page"
|
||||||
|
response = self.client.get('/test_admin/admin/admin_views/section/1/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), 0)
|
||||||
|
|
||||||
|
def testModelHistory(self):
|
||||||
|
"Check the never-cache status of a model history page"
|
||||||
|
response = self.client.get('/test_admin/admin/admin_views/section/1/history/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), 0)
|
||||||
|
|
||||||
|
def testModelDelete(self):
|
||||||
|
"Check the never-cache status of a model delete page"
|
||||||
|
response = self.client.get('/test_admin/admin/admin_views/section/1/delete/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), 0)
|
||||||
|
|
||||||
|
def testLogin(self):
|
||||||
|
"Check the never-cache status of login views"
|
||||||
|
self.client.logout()
|
||||||
|
response = self.client.get('/test_admin/admin/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), 0)
|
||||||
|
|
||||||
|
def testLogout(self):
|
||||||
|
"Check the never-cache status of logout view"
|
||||||
|
response = self.client.get('/test_admin/admin/logout/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), 0)
|
||||||
|
|
||||||
|
def testPasswordChange(self):
|
||||||
|
"Check the never-cache status of the password change view"
|
||||||
|
self.client.logout()
|
||||||
|
response = self.client.get('/test_admin/password_change/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), None)
|
||||||
|
|
||||||
|
def testPasswordChangeDone(self):
|
||||||
|
"Check the never-cache status of the password change done view"
|
||||||
|
response = self.client.get('/test_admin/admin/password_change/done/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), None)
|
||||||
|
|
||||||
|
def testJsi18n(self):
|
||||||
|
"Check the never-cache status of the Javascript i18n view"
|
||||||
|
response = self.client.get('/test_admin/jsi18n/')
|
||||||
|
self.failUnlessEqual(get_max_age(response), None)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue