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:
parent
6c4e5f0f0e
commit
1f84630c87
|
@ -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)),
|
||||
)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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)),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue