Fixed #6470: made the admin use a URL resolver.

This *is* backwards compatible, but `admin.site.root()` has been deprecated. The new style is `('^admin/', include(admin.site.urls))`; users will need to update their code to take advantage of the new customizable admin URLs.

Thanks to Alex Gaynor.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@9739 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jacob Kaplan-Moss 2009-01-14 20:22:25 +00:00
parent 6c4e5f0f0e
commit 1f84630c87
10 changed files with 484 additions and 257 deletions

View File

@ -13,5 +13,5 @@ urlpatterns = patterns('',
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
# (r'^admin/(.*)', admin.site.root),
# (r'^admin/', include(admin.site.urls)),
)

View File

@ -5,11 +5,12 @@ from django.forms.models import BaseInlineFormSet
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets
from django.contrib.admin import helpers
from django.contrib.admin.util import quote, unquote, flatten_fieldsets, get_deleted_objects
from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects
from django.core.exceptions import PermissionDenied
from django.db import models, transaction
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render_to_response
from django.utils.functional import update_wrapper
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.text import capfirst, get_text_list
@ -183,18 +184,38 @@ class ModelAdmin(BaseModelAdmin):
self.inline_instances.append(inline_instance)
super(ModelAdmin, self).__init__()
def __call__(self, request, url):
# Delegate to the appropriate method, based on the URL.
if url is None:
return self.changelist_view(request)
elif url == "add":
return self.add_view(request)
elif url.endswith('/history'):
return self.history_view(request, unquote(url[:-8]))
elif url.endswith('/delete'):
return self.delete_view(request, unquote(url[:-7]))
else:
return self.change_view(request, unquote(url))
def get_urls(self):
from django.conf.urls.defaults import patterns, url
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
return update_wrapper(wrapper, view)
info = self.admin_site.name, self.model._meta.app_label, self.model._meta.module_name
urlpatterns = patterns('',
url(r'^$',
wrap(self.changelist_view),
name='%sadmin_%s_%s_changelist' % info),
url(r'^add/$',
wrap(self.add_view),
name='%sadmin_%s_%s_add' % info),
url(r'^(.+)/history/$',
wrap(self.history_view),
name='%sadmin_%s_%s_history' % info),
url(r'^(.+)/delete/$',
wrap(self.delete_view),
name='%sadmin_%s_%s_delete' % info),
url(r'^(.+)/$',
wrap(self.change_view),
name='%sadmin_%s_%s_change' % info),
)
return urlpatterns
def urls(self):
return self.get_urls()
urls = property(urls)
def _media(self):
from django.conf import settings
@ -545,7 +566,7 @@ class ModelAdmin(BaseModelAdmin):
opts = model._meta
try:
obj = model._default_manager.get(pk=object_id)
obj = model._default_manager.get(pk=unquote(object_id))
except model.DoesNotExist:
# Don't raise Http404 just yet, because we haven't checked
# permissions yet. We don't want an unauthenticated user to be able
@ -659,7 +680,7 @@ class ModelAdmin(BaseModelAdmin):
app_label = opts.app_label
try:
obj = self.model._default_manager.get(pk=object_id)
obj = self.model._default_manager.get(pk=unquote(object_id))
except self.model.DoesNotExist:
# Don't raise Http404 just yet, because we haven't checked
# permissions yet. We don't want an unauthenticated user to be able
@ -674,7 +695,7 @@ class ModelAdmin(BaseModelAdmin):
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []]
deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), object_id, escape(obj))), []]
perms_needed = set()
get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
@ -735,6 +756,34 @@ class ModelAdmin(BaseModelAdmin):
"admin/object_history.html"
], context, context_instance=template.RequestContext(request))
#
# DEPRECATED methods.
#
def __call__(self, request, url):
"""
DEPRECATED: this is the old way of URL resolution, replaced by
``get_urls()``. This only called by AdminSite.root(), which is also
deprecated.
Again, remember that the following code only exists for
backwards-compatibility. Any new URLs, changes to existing URLs, or
whatever need to be done up in get_urls(), above!
This function still exists for backwards-compatibility; it will be
removed in Django 1.3.
"""
# Delegate to the appropriate method, based on the URL.
if url is None:
return self.changelist_view(request)
elif url == "add":
return self.add_view(request)
elif url.endswith('/history'):
return self.history_view(request, unquote(url[:-8]))
elif url.endswith('/delete'):
return self.delete_view(request, unquote(url[:-7]))
else:
return self.change_view(request, unquote(url))
class InlineModelAdmin(BaseModelAdmin):
"""
Options for inline editing of ``model`` instances.

View File

@ -1,4 +1,3 @@
import base64
import re
from django import http, template
from django.contrib.admin import ModelAdmin
@ -6,12 +5,12 @@ from django.contrib.auth import authenticate, login
from django.db.models.base import ModelBase
from django.core.exceptions import ImproperlyConfigured
from django.shortcuts import render_to_response
from django.utils.functional import update_wrapper
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy, ugettext as _
from django.views.decorators.cache import never_cache
from django.conf import settings
from django.utils.hashcompat import md5_constructor
ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
LOGIN_FORM_KEY = 'this_is_the_login_form'
@ -34,8 +33,17 @@ class AdminSite(object):
login_template = None
app_index_template = None
def __init__(self):
def __init__(self, name=None):
self._registry = {} # model_class class -> admin_class instance
# TODO Root path is used to calculate urls under the old root() method
# in order to maintain backwards compatibility we are leaving that in
# so root_path isn't needed, not sure what to do about this.
self.root_path = 'admin/'
if name is None:
name = ''
else:
name += '_'
self.name = name
def register(self, model_or_iterable, admin_class=None, **options):
"""
@ -115,66 +123,74 @@ class AdminSite(object):
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.")
def root(self, request, url):
def admin_view(self, view):
"""
Handles main URL routing for the admin app.
Decorator to create an "admin view attached to this ``AdminSite``. This
wraps the view and provides permission checking by calling
``self.has_permission``.
`url` is the remainder of the URL -- e.g. 'comments/comment/'.
You'll want to use this from within ``AdminSite.get_urls()``:
class MyAdminSite(AdminSite):
def get_urls(self):
from django.conf.urls.defaults import patterns, url
urls = super(MyAdminSite, self).get_urls()
urls += patterns('',
url(r'^my_view/$', self.protected_view(some_view))
)
return urls
"""
if request.method == 'GET' and not request.path.endswith('/'):
return http.HttpResponseRedirect(request.path + '/')
def inner(request, *args, **kwargs):
if not self.has_permission(request):
return self.login(request)
return view(request, *args, **kwargs)
return update_wrapper(inner, view)
if settings.DEBUG:
self.check_dependencies()
def get_urls(self):
from django.conf.urls.defaults import patterns, url, include
# Figure out the admin base URL path and stash it for later use
self.root_path = re.sub(re.escape(url) + '$', '', request.path)
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_view(view)(*args, **kwargs)
return update_wrapper(wrapper, view)
url = url.rstrip('/') # Trim trailing slash, if it exists.
# Admin-site-wide views.
urlpatterns = patterns('',
url(r'^$',
wrap(self.index),
name='%sadmin_index' % self.name),
url(r'^logout/$',
wrap(self.logout),
name='%sadmin_logout'),
url(r'^password_change/$',
wrap(self.password_change),
name='%sadmin_password_change' % self.name),
url(r'^password_change/done/$',
wrap(self.password_change_done),
name='%sadmin_password_change_done' % self.name),
url(r'^jsi18n/$',
wrap(self.i18n_javascript),
name='%sadmin_jsi18n' % self.name),
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
'django.views.defaults.shortcut'),
url(r'^(?P<app_label>\w+)/$',
wrap(self.app_index),
name='%sadmin_app_list' % self.name),
)
# The 'logout' view doesn't require that the person is logged in.
if url == 'logout':
return self.logout(request)
# Add in each model's views.
for model, model_admin in self._registry.iteritems():
urlpatterns += patterns('',
url(r'^%s/%s/' % (model._meta.app_label, model._meta.module_name),
include(model_admin.urls))
)
return urlpatterns
# Check permission to continue or display login form.
if not self.has_permission(request):
return self.login(request)
if url == '':
return self.index(request)
elif url == 'password_change':
return self.password_change(request)
elif url == 'password_change/done':
return self.password_change_done(request)
elif url == 'jsi18n':
return self.i18n_javascript(request)
# URLs starting with 'r/' are for the "View on site" links.
elif url.startswith('r/'):
from django.contrib.contenttypes.views import shortcut
return shortcut(request, *url.split('/')[1:])
else:
if '/' in url:
return self.model_page(request, *url.split('/', 2))
else:
return self.app_index(request, url)
raise http.Http404('The requested admin page does not exist.')
def model_page(self, request, app_label, model_name, rest_of_url=None):
"""
Handles the model-specific functionality of the admin site, delegating
to the appropriate ModelAdmin class.
"""
from django.db import models
model = models.get_model(app_label, model_name)
if model is None:
raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
try:
admin_obj = self._registry[model]
except KeyError:
raise http.Http404("This model exists but has not been registered with the admin site.")
return admin_obj(request, rest_of_url)
model_page = never_cache(model_page)
def urls(self):
return self.get_urls()
urls = property(urls)
def password_change(self, request):
"""
@ -378,6 +394,81 @@ class AdminSite(object):
context_instance=template.RequestContext(request)
)
def root(self, request, url):
"""
DEPRECATED. This function is the old way of handling URL resolution, and
is deprecated in favor of real URL resolution -- see ``get_urls()``.
This function still exists for backwards-compatibility; it will be
removed in Django 1.3.
"""
import warnings
warnings.warn(
"AdminSite.root() is deprecated; use include(admin.site.urls) instead.",
PendingDeprecationWarning
)
#
# Again, remember that the following only exists for
# backwards-compatibility. Any new URLs, changes to existing URLs, or
# whatever need to be done up in get_urls(), above!
#
if request.method == 'GET' and not request.path.endswith('/'):
return http.HttpResponseRedirect(request.path + '/')
if settings.DEBUG:
self.check_dependencies()
# Figure out the admin base URL path and stash it for later use
self.root_path = re.sub(re.escape(url) + '$', '', request.path)
url = url.rstrip('/') # Trim trailing slash, if it exists.
# The 'logout' view doesn't require that the person is logged in.
if url == 'logout':
return self.logout(request)
# Check permission to continue or display login form.
if not self.has_permission(request):
return self.login(request)
if url == '':
return self.index(request)
elif url == 'password_change':
return self.password_change(request)
elif url == 'password_change/done':
return self.password_change_done(request)
elif url == 'jsi18n':
return self.i18n_javascript(request)
# URLs starting with 'r/' are for the "View on site" links.
elif url.startswith('r/'):
from django.contrib.contenttypes.views import shortcut
return shortcut(request, *url.split('/')[1:])
else:
if '/' in url:
return self.model_page(request, *url.split('/', 2))
else:
return self.app_index(request, url)
raise http.Http404('The requested admin page does not exist.')
def model_page(self, request, app_label, model_name, rest_of_url=None):
"""
DEPRECATED. This is the old way of handling a model view on the admin
site; the new views should use get_urls(), above.
"""
from django.db import models
model = models.get_model(app_label, model_name)
if model is None:
raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
try:
admin_obj = self._registry[model]
except KeyError:
raise http.Http404("This model exists but has not been registered with the admin site.")
return admin_obj(request, rest_of_url)
model_page = never_cache(model_page)
# This global object represents the default admin site, for the common case.
# You can instantiate AdminSite in your own code to create a custom admin site.
site = AdminSite()

View File

@ -6,7 +6,6 @@ from django.utils.text import capfirst
from django.utils.encoding import force_unicode
from django.utils.translation import ugettext as _
def quote(s):
"""
Ensure that primary key values do not confuse the admin URLs by escaping

View File

@ -41,6 +41,12 @@ class UserAdmin(admin.ModelAdmin):
return self.user_change_password(request, url.split('/')[0])
return super(UserAdmin, self).__call__(request, url)
def get_urls(self):
from django.conf.urls.defaults import patterns
return patterns('',
(r'^(\d+)/password/$', self.admin_site.admin_view(self.user_change_password))
) + super(UserAdmin, self).get_urls()
def add_view(self, request):
# It's an error for a user to have add permission but NOT change
# permission for users. If we allowed such users to add users, they

View File

@ -57,7 +57,7 @@ activate the admin site for your installation, do these three things:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
**(r'^admin/(.*)', admin.site.root),**
**(r'^admin/', include(admin.site.urls)),**
)
(The bold lines are the ones that needed to be uncommented.)

View File

@ -632,6 +632,49 @@ model instance::
instance.save()
formset.save_m2m()
``get_urls(self)``
~~~~~~~~~~~~~~~~~~~
The ``get_urls`` method on a ``ModelAdmin`` returns the URLs to be used for
that ModelAdmin in the same way as a URLconf. Therefore you can extend them as
documented in :ref:`topics-http-urls`::
class MyModelAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super(MyModelAdmin, self).get_urls()
my_urls = patterns('',
(r'^my_view/$', self.my_view)
)
return my_urls + urls
.. note::
Notice that the custom patterns are included *before* the regular admin
URLs: the admin URL patterns are very permissive and will match nearly
anything, so you'll usually want to prepend your custom URLs to the built-in
ones.
Note, however, that the ``self.my_view`` function registered above will *not*
have any permission check done; it'll be accessible to the general public. Since
this is usually not what you want, Django provides a convience wrapper to check
permissions. 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):
def get_urls(self):
urls = super(MyModelAdmin, self).get_urls()
my_urls = patterns('',
(r'^my_view/$', self.admin_site.admin_view(self.my_view))
)
return my_urls + urls
Notice the wrapped view in the fifth line above::
(r'^my_view/$', self.admin_site.admin_view(self.my_view))
This wrapping will protect ``self.my_view`` from unauthorized access.
``ModelAdmin`` media definitions
--------------------------------
@ -1027,7 +1070,7 @@ In this example, we register the default ``AdminSite`` instance
admin.autodiscover()
urlpatterns = patterns('',
('^admin/(.*)', admin.site.root),
('^admin/', include(admin.site.urls)),
)
Above we used ``admin.autodiscover()`` to automatically load the
@ -1041,15 +1084,13 @@ In this example, we register the ``AdminSite`` instance
from myproject.admin import admin_site
urlpatterns = patterns('',
('^myadmin/(.*)', admin_site.root),
('^myadmin/', include(admin_site.urls)),
)
There is really no need to use autodiscover when using your own ``AdminSite``
instance since you will likely be importing all the per-app admin.py modules
in your ``myproject.admin`` module.
Note that the regular expression in the URLpattern *must* group everything in
the URL that comes after the URL root -- hence the ``(.*)`` in these examples.
Multiple admin sites in the same URLconf
----------------------------------------
@ -1068,6 +1109,29 @@ respectively::
from myproject.admin import basic_site, advanced_site
urlpatterns = patterns('',
('^basic-admin/(.*)', basic_site.root),
('^advanced-admin/(.*)', advanced_site.root),
('^basic-admin/', include(basic_site.urls)),
('^advanced-admin/', include(advanced_site.urls)),
)
Adding views to admin sites
---------------------------
It possible to add additional views to the admin site in the same way one can
add them to ``ModelAdmins``. This by using the ``get_urls()`` method on an
AdminSite in the same way as `described above`__
__ `get_urls(self)`_
Protecting Custom ``AdminSite`` and ``ModelAdmin``
--------------------------------------------------
By default all the views in the Django admin are protected so that only staff
members can access them. If you add your own views to either a ``ModelAdmin``
or ``AdminSite`` you should ensure that where necessary they are protected in
the same manner. To do this use the ``admin_perm_test`` decorator provided in
``django.contrib.admin.utils.admin_perm_test``. It can be used in the same way
as the ``login_requied`` decorator.
.. note::
The ``admin_perm_test`` decorator can only be used on methods which are on
``ModelAdmins`` or ``AdminSites``, you cannot use it on arbitrary functions.

View File

@ -0,0 +1,30 @@
"""
A second, custom AdminSite -- see tests.CustomAdminSiteTests.
"""
from django.conf.urls.defaults import patterns
from django.contrib import admin
from django.http import HttpResponse
import models
class Admin2(admin.AdminSite):
login_template = 'custom_admin/login.html'
index_template = 'custom_admin/index.html'
# A custom index view.
def index(self, request, extra_context=None):
return super(Admin2, self).index(request, {'foo': '*bar*'})
def get_urls(self):
return patterns('',
(r'^my_view/$', self.admin_view(self.my_view)),
) + super(Admin2, self).get_urls()
def my_view(self, request):
return HttpResponse("Django is a magical pony!")
site = Admin2(name="admin2")
site.register(models.Article, models.ArticleAdmin)
site.register(models.Section, inlines=[models.ArticleInline])
site.register(models.Thing, models.ThingAdmin)

View File

@ -14,6 +14,11 @@ from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey
class AdminViewBasicTest(TestCase):
fixtures = ['admin-views-users.xml', 'admin-views-colors.xml']
# Store the bit of the URL where the admin is registered as a class
# variable. That way we can test a second AdminSite just by subclassing
# this test case and changing urlbit.
urlbit = 'admin'
def setUp(self):
self.client.login(username='super', password='secret')
@ -24,20 +29,20 @@ class AdminViewBasicTest(TestCase):
"""
If you leave off the trailing slash, app should redirect and add it.
"""
request = self.client.get('/test_admin/admin/admin_views/article/add')
request = self.client.get('/test_admin/%s/admin_views/article/add' % self.urlbit)
self.assertRedirects(request,
'/test_admin/admin/admin_views/article/add/'
'/test_admin/%s/admin_views/article/add/' % self.urlbit, status_code=301
)
def testBasicAddGet(self):
"""
A smoke test to ensure GET on the add_view works.
"""
response = self.client.get('/test_admin/admin/admin_views/section/add/')
response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit)
self.failUnlessEqual(response.status_code, 200)
def testAddWithGETArgs(self):
response = self.client.get('/test_admin/admin/admin_views/section/add/', {'name': 'My Section'})
response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit, {'name': 'My Section'})
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
'value="My Section"' in response.content,
@ -48,7 +53,7 @@ class AdminViewBasicTest(TestCase):
"""
A smoke test to ensureGET on the change_view works.
"""
response = self.client.get('/test_admin/admin/admin_views/section/1/')
response = self.client.get('/test_admin/%s/admin_views/section/1/' % self.urlbit)
self.failUnlessEqual(response.status_code, 200)
def testBasicAddPost(self):
@ -61,7 +66,7 @@ class AdminViewBasicTest(TestCase):
"article_set-TOTAL_FORMS": u"3",
"article_set-INITIAL_FORMS": u"0",
}
response = self.client.post('/test_admin/admin/admin_views/section/add/', post_data)
response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data)
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
def testBasicEditPost(self):
@ -106,7 +111,7 @@ class AdminViewBasicTest(TestCase):
"article_set-5-date_0": u"",
"article_set-5-date_1": u"",
}
response = self.client.post('/test_admin/admin/admin_views/section/1/', post_data)
response = self.client.post('/test_admin/%s/admin_views/section/1/' % self.urlbit, post_data)
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
def testChangeListSortingCallable(self):
@ -114,7 +119,7 @@ class AdminViewBasicTest(TestCase):
Ensure we can sort on a list_display field that is a callable
(column 2 is callable_year in ArticleAdmin)
"""
response = self.client.get('/test_admin/admin/admin_views/article/', {'ot': 'asc', 'o': 2})
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 2})
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
response.content.index('Oldest content') < response.content.index('Middle content') and
@ -127,7 +132,7 @@ class AdminViewBasicTest(TestCase):
Ensure we can sort on a list_display field that is a Model method
(colunn 3 is 'model_year' in ArticleAdmin)
"""
response = self.client.get('/test_admin/admin/admin_views/article/', {'ot': 'dsc', 'o': 3})
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'dsc', 'o': 3})
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
response.content.index('Newest content') < response.content.index('Middle content') and
@ -140,7 +145,7 @@ class AdminViewBasicTest(TestCase):
Ensure we can sort on a list_display field that is a ModelAdmin method
(colunn 4 is 'modeladmin_year' in ArticleAdmin)
"""
response = self.client.get('/test_admin/admin/admin_views/article/', {'ot': 'asc', 'o': 4})
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 4})
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
response.content.index('Oldest content') < response.content.index('Middle content') and
@ -150,7 +155,7 @@ class AdminViewBasicTest(TestCase):
def testLimitedFilter(self):
"""Ensure admin changelist filters do not contain objects excluded via limit_choices_to."""
response = self.client.get('/test_admin/admin/admin_views/thing/')
response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit)
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
'<div id="changelist-filter">' in response.content,
@ -163,10 +168,29 @@ class AdminViewBasicTest(TestCase):
def testIncorrectLookupParameters(self):
"""Ensure incorrect lookup parameters are handled gracefully."""
response = self.client.get('/test_admin/admin/admin_views/thing/', {'notarealfield': '5'})
self.assertRedirects(response, '/test_admin/admin/admin_views/thing/?e=1')
response = self.client.get('/test_admin/admin/admin_views/thing/', {'color__id__exact': 'StringNotInteger!'})
self.assertRedirects(response, '/test_admin/admin/admin_views/thing/?e=1')
response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'notarealfield': '5'})
self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
class CustomModelAdminTest(AdminViewBasicTest):
urlbit = "admin2"
def testCustomAdminSiteLoginTemplate(self):
self.client.logout()
request = self.client.get('/test_admin/admin2/')
self.assertTemplateUsed(request, 'custom_admin/login.html')
self.assert_('Hello from a custom login template' in request.content)
def testCustomAdminSiteIndexViewAndTemplate(self):
request = self.client.get('/test_admin/admin2/')
self.assertTemplateUsed(request, 'custom_admin/index.html')
self.assert_('Hello from a custom index template *bar*' in request.content)
def testCustomAdminSiteView(self):
self.client.login(username='super', password='secret')
response = self.client.get('/test_admin/%s/my_view/' % self.urlbit)
self.assert_(response.content == "Django is a magical pony!", response.content)
def get_perm(Model, perm):
"""Return the permission object, for the Model"""
@ -432,44 +456,6 @@ class AdminViewPermissionsTest(TestCase):
self.client.get('/test_admin/admin/logout/')
def testCustomAdminSiteTemplates(self):
from django.contrib import admin
self.assertEqual(admin.site.index_template, None)
self.assertEqual(admin.site.login_template, None)
self.client.get('/test_admin/admin/logout/')
request = self.client.get('/test_admin/admin/')
self.assertTemplateUsed(request, 'admin/login.html')
self.client.post('/test_admin/admin/', self.changeuser_login)
request = self.client.get('/test_admin/admin/')
self.assertTemplateUsed(request, 'admin/index.html')
self.client.get('/test_admin/admin/logout/')
admin.site.login_template = 'custom_admin/login.html'
admin.site.index_template = 'custom_admin/index.html'
request = self.client.get('/test_admin/admin/')
self.assertTemplateUsed(request, 'custom_admin/login.html')
self.assert_('Hello from a custom login template' in request.content)
self.client.post('/test_admin/admin/', self.changeuser_login)
request = self.client.get('/test_admin/admin/')
self.assertTemplateUsed(request, 'custom_admin/index.html')
self.assert_('Hello from a custom index template' in request.content)
# Finally, using monkey patching check we can inject custom_context arguments in to index
original_index = admin.site.index
def index(*args, **kwargs):
kwargs['extra_context'] = {'foo': '*bar*'}
return original_index(*args, **kwargs)
admin.site.index = index
request = self.client.get('/test_admin/admin/')
self.assertTemplateUsed(request, 'custom_admin/index.html')
self.assert_('Hello from a custom index template *bar*' in request.content)
self.client.get('/test_admin/admin/logout/')
del admin.site.index # Resets to using the original
admin.site.login_template = None
admin.site.index_template = None
def testDeleteView(self):
"""Delete view should restrict access and actually delete items."""

View File

@ -1,9 +1,11 @@
from django.conf.urls.defaults import *
from django.contrib import admin
import views
import customadmin
urlpatterns = patterns('',
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
(r'^admin/secure-view/$', views.secure_view),
(r'^admin/(.*)', admin.site.root),
(r'^admin/', include(admin.site.urls)),
(r'^admin2/', include(customadmin.site.urls)),
)