diff --git a/django/conf/project_template/urls.py b/django/conf/project_template/urls.py
index af1d1db..dfb49d3 100644
--- a/django/conf/project_template/urls.py
+++ b/django/conf/project_template/urls.py
@@ -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)),
)
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 3d60b9d..966e96c 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -1,3 +1,5 @@
+import types
+
from django import forms, template
from django.forms.formsets import all_valid
from django.forms.models import modelform_factory, inlineformset_factory
@@ -5,7 +7,7 @@ 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 quote, unquote, flatten_fieldsets, get_deleted_objects, admin_perm_test
from django.core.exceptions import PermissionDenied
from django.db import models, transaction
from django.http import Http404, HttpResponse, HttpResponseRedirect
@@ -196,6 +198,22 @@ class ModelAdmin(BaseModelAdmin):
else:
return self.change_view(request, unquote(url))
+ def _get_urls(self):
+ from django.conf.urls.defaults import patterns, url
+ urls_module = types.ModuleType('%s.urls' % self.__class__.__name__)
+ info = self.admin_site.name, self.model._meta.app_label, self.model._meta.module_name
+ urlpatterns = patterns('',
+ url(r'^$', lambda *args, **kwargs: self.changelist_view(*args, **kwargs), name='%sadmin_%s_%s_changelist' % info),
+ url(r'^add/$', lambda *args, **kwargs: self.add_view(*args, **kwargs), name='%sadmin_%s_%s_add' % info),
+ url(r'^(.+)/history/$', lambda *args, **kwargs: self.history_view(*args, **kwargs), name='%sadmin_%s_%s_history' % info),
+ url(r'^(.+)/delete/$', lambda *args, **kwargs: self.delete_view(*args, **kwargs), name='%sadmin_%s_%s_delete' % info),
+ url(r'^(.+)/$', lambda *args, **kwargs: self.change_view(*args, **kwargs), name='%sadmin_%s_%s_change' % info),
+ )
+ urls_module.urlpatterns = urlpatterns
+ return urls_module
+ urls = property(_get_urls)
+
+
def _media(self):
from django.conf import settings
@@ -537,7 +555,7 @@ class ModelAdmin(BaseModelAdmin):
}
context.update(extra_context or {})
return self.render_change_form(request, context, add=True)
- add_view = transaction.commit_on_success(add_view)
+ add_view = transaction.commit_on_success(admin_perm_test(add_view))
def change_view(self, request, object_id, extra_context=None):
"The 'change' admin view for this model."
@@ -545,7 +563,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
@@ -616,7 +634,7 @@ class ModelAdmin(BaseModelAdmin):
}
context.update(extra_context or {})
return self.render_change_form(request, context, change=True, obj=obj)
- change_view = transaction.commit_on_success(change_view)
+ change_view = transaction.commit_on_success(admin_perm_test(change_view))
def changelist_view(self, request, extra_context=None):
"The 'change list' admin view for this model."
@@ -652,6 +670,7 @@ class ModelAdmin(BaseModelAdmin):
'admin/%s/change_list.html' % app_label,
'admin/change_list.html'
], context, context_instance=template.RequestContext(request))
+ changelist_view = admin_perm_test(changelist_view)
def delete_view(self, request, object_id, extra_context=None):
"The 'delete' admin view for this model."
@@ -659,7 +678,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 +693,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: %s' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []]
+ deleted_objects = [mark_safe(u'%s: %s' % (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)
@@ -707,6 +726,7 @@ class ModelAdmin(BaseModelAdmin):
"admin/%s/delete_confirmation.html" % app_label,
"admin/delete_confirmation.html"
], context, context_instance=template.RequestContext(request))
+ delete_view = admin_perm_test(delete_view)
def history_view(self, request, object_id, extra_context=None):
"The 'history' admin view for this model."
@@ -734,6 +754,7 @@ class ModelAdmin(BaseModelAdmin):
"admin/%s/object_history.html" % app_label,
"admin/object_history.html"
], context, context_instance=template.RequestContext(request))
+ history_view = admin_perm_test(history_view)
class InlineModelAdmin(BaseModelAdmin):
"""
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index c16ab6a..86c063f 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -1,7 +1,10 @@
import base64
import re
+import types
+
from django import http, template
from django.contrib.admin import ModelAdmin
+from django.contrib.admin.util import admin_perm_test
from django.contrib.auth import authenticate, login
from django.db.models.base import ModelBase
from django.core.exceptions import ImproperlyConfigured
@@ -34,8 +37,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):
"""
@@ -121,6 +133,9 @@ class AdminSite(object):
`url` is the remainder of the URL -- e.g. 'comments/comment/'.
"""
+ import warnings
+ warnings.warn("Using AdminSite.root() is deprecated, you should \
+ include(AdminSite.urls) instead", PendingDeprecationWarning)
if request.method == 'GET' and not request.path.endswith('/'):
return http.HttpResponseRedirect(request.path + '/')
@@ -159,7 +174,28 @@ class AdminSite(object):
return self.app_index(request, url)
raise http.Http404('The requested admin page does not exist.')
-
+
+ def _get_urls(self):
+ from django.conf.urls.defaults import patterns, url, include
+ from django.core.urlresolvers import RegexURLResolver
+ urls_module = types.ModuleType('%s.urls' % self.__class__.__name__)
+ urlpatterns = patterns('',
+ url(r'^$', lambda *args, **kwargs: self.index(*args, **kwargs), name='%sadmin_index' % self.name),
+ url(r'^logout/$', lambda *args, **kwargs: self.logout(*args, **kwargs), name='%sadmin_logout'),
+ url(r'^password_change/$', lambda *args, **kwargs: self.password_change(*args, **kwargs), name='%sadmin_password_change' % self.name),
+ url(r'^password_change/done/$', lambda *args, **kwargs: self.password_change_done(*args, **kwargs), name='%sadmin_password_change_done' % self.name),
+ url(r'^jsi18n/$', lambda *args, **kwargs: self.i18n_javascript(*args, **kwargs), name='%sadmin_jsi18n' % self.name),
+ url(r'^r/(?P\d+)/(?P.+)/$', 'django.views.defaults.shortcut'),
+ url(r'^(?P\w+)/$', lambda *args, **kwargs: self.app_index(*args, **kwargs), name='%sadmin_app_list' % self.name),
+ )
+ 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))
+ )
+ urls_module.urlpatterns = urlpatterns
+ return urls_module
+ urls = property(_get_urls)
+
def model_page(self, request, app_label, model_name, rest_of_url=None):
"""
Handles the model-specific functionality of the admin site, delegating
@@ -183,6 +219,7 @@ class AdminSite(object):
from django.contrib.auth.views import password_change
return password_change(request,
post_change_redirect='%spassword_change/done/' % self.root_path)
+ passoword_change = admin_perm_test(password_change)
def password_change_done(self, request):
"""
@@ -190,6 +227,7 @@ class AdminSite(object):
"""
from django.contrib.auth.views import password_change_done
return password_change_done(request)
+ password_change_done = admin_perm_test(password_change_done)
def i18n_javascript(self, request):
"""
@@ -203,6 +241,7 @@ class AdminSite(object):
else:
from django.views.i18n import null_javascript_catalog as javascript_catalog
return javascript_catalog(request, packages='django.conf')
+ i18n_javascript = admin_perm_test(i18n_javascript)
def logout(self, request):
"""
@@ -317,7 +356,7 @@ class AdminSite(object):
return render_to_response(self.index_template or 'admin/index.html', context,
context_instance=template.RequestContext(request)
)
- index = never_cache(index)
+ index = never_cache(admin_perm_test(index))
def display_login_form(self, request, error_message='', extra_context=None):
request.session.set_test_cookie()
@@ -377,6 +416,7 @@ class AdminSite(object):
return render_to_response(self.app_index_template or 'admin/app_index.html', context,
context_instance=template.RequestContext(request)
)
+ app_index = admin_perm_test(app_index)
# 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.
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
index 0900b4e..6b34c78 100644
--- a/django/contrib/admin/util.py
+++ b/django/contrib/admin/util.py
@@ -6,6 +6,17 @@ from django.utils.text import capfirst
from django.utils.encoding import force_unicode
from django.utils.translation import ugettext as _
+def admin_perm_test(func):
+ def inner(admin_site_or_modeladmin, request, *args, **kwargs):
+ if hasattr(admin_site_or_modeladmin, 'has_permission'):
+ admin_site = admin_site_or_modeladmin
+ else:
+ admin_site = admin_site_or_modeladmin.admin_site
+ if not admin_site.has_permission(request):
+ return admin_site.login(request)
+ # User has right permisssions show the view
+ return func(admin_site_or_modeladmin, request, *args, **kwargs)
+ return inner
def quote(s):
"""
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index 774e6d3..897cde2 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -143,6 +143,8 @@ class RegexURLResolver(object):
# urlconf_name is a string representing the module containing urlconfs.
self.regex = re.compile(regex, re.UNICODE)
self.urlconf_name = urlconf_name
+ if not isinstance(self.urlconf_name, basestring):
+ self._urlconf_module = self.urlconf_name
self.callback = None
self.default_kwargs = default_kwargs or {}
self._reverse_dict = MultiValueDict()
diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt
index 1144167..fb6794b 100644
--- a/docs/intro/tutorial02.txt
+++ b/docs/intro/tutorial02.txt
@@ -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.)
diff --git a/docs/ref/contrib/admin.txt b/docs/ref/contrib/admin.txt
index f24dc46..f80754d 100644
--- a/docs/ref/contrib/admin.txt
+++ b/docs/ref/contrib/admin.txt
@@ -1027,7 +1027,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 +1041,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 +1066,6 @@ 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.root)),
+ ('^advanced-admin/', include(advanced_site.root)),
)
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 391d1ff..8fb8ad8 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -26,7 +26,7 @@ class AdminViewBasicTest(TestCase):
"""
request = self.client.get('/test_admin/admin/admin_views/article/add')
self.assertRedirects(request,
- '/test_admin/admin/admin_views/article/add/'
+ '/test_admin/admin/admin_views/article/add/', status_code=301
)
def testBasicAddGet(self):
diff --git a/tests/regressiontests/admin_views/urls.py b/tests/regressiontests/admin_views/urls.py
index 4e5da48..02e0286 100644
--- a/tests/regressiontests/admin_views/urls.py
+++ b/tests/regressiontests/admin_views/urls.py
@@ -5,5 +5,5 @@ import views
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)),
)