diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 234cfd1..977199a 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -16,6 +16,7 @@ from django.utils.text import capfirst, get_text_list
 from django.utils.translation import ugettext as _
 from django.utils.encoding import force_unicode
 import sets
+import types
 
 HORIZONTAL, VERTICAL = 1, 2
 # returns the <ul> class for a given radio_admin field
@@ -56,6 +57,15 @@ def flatten_fieldsets(fieldsets):
                 field_names.append(field)
     return field_names
 
+def admin_perm_test(func):
+    def inner(model_admin, request, *args, **kwargs):
+        if not model_admin.admin_site.has_permission(request):
+            response = model_admin.admin_site.login(request)
+            if response:          
+                return response
+        return func(model_admin, request, *args, **kwargs)
+    return inner
+
 class AdminForm(object):
     def __init__(self, form, fieldsets, prepopulated_fields):
         self.form, self.fieldsets = form, fieldsets
@@ -265,18 +275,21 @@ class ModelAdmin(BaseModelAdmin):
                 raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting in order to use the admin application.")
             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.")
-
-        # Delegate to the appropriate method, based on the URL.
-        if url is None:
-            return self.changelist_view(request)
-        elif url.endswith('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
+        urls_module = types.ModuleType('%s.urls' % self.__class__.__name__)
+        info = self.model._meta.app_label, self.model._meta.module_name
+        urlpatterns = patterns('',
+            url(r'^$', self.changelist_view, name='admin_%s_%s_changelist' % info),
+            url(r'^add/$', self.add_view, name='admin_%s_%s_add' % info),
+            url(r'^(.+)/history/$', self.history_view, name='admin_%s_%s_history' % info),
+            url(r'^(.+)/delete/$', self.delete_view, name='admin_%s_%s_delete' % info),
+            url(r'^(.+)/$', self.change_view, name='admin_%s_%s_change' % info),
+        )
+        urls_module.urlpatterns = urlpatterns
+        return urls_module
+    urls = property(_get_urls)
 
     def _media(self):
         from django.conf import settings
@@ -480,7 +493,6 @@ class ModelAdmin(BaseModelAdmin):
             'content_type_id': ContentType.objects.get_for_model(model).id,
             'save_as': self.save_as,
             'save_on_top': self.save_on_top,
-            'root_path': self.admin_site.root_path,
         })
         return render_to_response(self.change_form_template or [
             "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
@@ -540,10 +552,10 @@ class ModelAdmin(BaseModelAdmin):
             'media': mark_safe(media),
             'inline_admin_formsets': inline_admin_formsets,
             'errors': AdminErrorList(form, inline_formsets),
-            'root_path': self.admin_site.root_path,
         }
         context.update(extra_context or {})
         return self.render_change_form(request, model, context, add=True)
+    add_view = admin_perm_test(add_view)
 
     def change_view(self, request, object_id, extra_context=None):
         "The 'change' admin view for this model."
@@ -618,10 +630,10 @@ class ModelAdmin(BaseModelAdmin):
             'media': mark_safe(media),
             'inline_admin_formsets': inline_admin_formsets,
             'errors': AdminErrorList(form, inline_formsets),
-            'root_path': self.admin_site.root_path,
         }
         context.update(extra_context or {})
         return self.render_change_form(request, model, context, change=True, obj=obj)
+    change_view = admin_perm_test(change_view)
 
     def changelist_view(self, request, extra_context=None):
         "The 'change list' admin view for this model."
@@ -648,7 +660,6 @@ class ModelAdmin(BaseModelAdmin):
             'is_popup': cl.is_popup,
             'cl': cl,
             'has_add_permission': self.has_add_permission(request),
-            'root_path': self.admin_site.root_path,
         }
         context.update(extra_context or {})
         return render_to_response(self.change_list_template or [
@@ -656,6 +667,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."
@@ -702,7 +714,6 @@ class ModelAdmin(BaseModelAdmin):
             "deleted_objects": deleted_objects,
             "perms_lacking": perms_needed,
             "opts": opts,
-            "root_path": self.admin_site.root_path,
         }
         context.update(extra_context or {})
         return render_to_response(self.delete_confirmation_template or [
@@ -710,6 +721,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."
@@ -728,7 +740,6 @@ class ModelAdmin(BaseModelAdmin):
             'action_list': action_list,
             'module_name': capfirst(opts.verbose_name_plural),
             'object': obj,
-            'root_path': self.admin_site.root_path,
         }
         context.update(extra_context or {})
         return render_to_response(self.object_history_template or [
@@ -736,6 +747,7 @@ class ModelAdmin(BaseModelAdmin):
             "admin/%s/object_history.html" % opts.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 6aa65c2..33f152e 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -11,6 +11,7 @@ import base64
 import cPickle as pickle
 import datetime
 import md5
+import types
 import re
 
 ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
@@ -39,6 +40,15 @@ def _decode_post_data(encoded_data):
         raise SuspiciousOperation, "User may have tampered with session cookie."
     return pickle.loads(pickled)
 
+def admin_perm_test(func):
+    def inner(admin_site, request, *args, **kwargs):
+        if not admin_site.has_permission(request):
+            response = admin_site.login(request)
+            if response:          
+                return response
+        return func(admin_site, request, *args, **kwargs)
+    return inner
+
 class AdminSite(object):
     """
     An AdminSite object encapsulates an instance of the Django admin application, ready
@@ -93,70 +103,30 @@ class AdminSite(object):
         *at least one* page in the admin site.
         """
         return request.user.is_authenticated() and request.user.is_staff
-
-    def root(self, request, url):
-        """ 
-        Handles main URL routing for the admin app.
-
-        `url` is the remainder of the URL -- e.g. 'comments/comment/'.
-        """
-        if request.method == 'GET' and not request.path.endswith('/'):
-            return http.HttpResponseRedirect(request.path + '/')
-        
-        # Figure out the admin base URL path and stash it for later use
-        self.root_path = re.sub(re.escape(url) + '$', '', request.path)
+    
+    def _get_urls(self):
+        from django.conf.urls.defaults import patterns, url, include
+        from django.core.urlresolvers import RegexURLResolver
         
-        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)
-
-        if not self.has_permission(request):
-            response = self.login(request)
-            if response:
-                # make sure that there is a response before returning
-                # this addresses any post data that might persist from
-                # expired sessions and continue through (#5999)
-                return response
-
-        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 "show in web" links
-        elif url.startswith('r/'):
-            from django.views.defaults import shortcut
-            return shortcut(request, *url.split('/')[1:])
-        else:
-            match = USER_CHANGE_PASSWORD_URL_RE.match(url)
-            if match:
-                return self.user_change_password(request, match.group(1))
-                
-            if '/' in url:
-                return self.model_page(request, *url.split('/', 2))
+        urls_module = types.ModuleType('%s.urls' % self.__class__.__name__)
+        urlpatterns = patterns('',
+            url(r'^$', self.index, name='admin_index'),
+            url(r'^logout/$', self.logout, name='admin_logout'),
+            url(r'^password_change/$', self.password_change, name='admin_password_change'),
+            url(r'^password_change_done/$', self.password_change_done, name='admin_password_change_done'),
+            url(r'^jsi18n/$', self.i18n_javascript, name='admin_jsi18n'),
+            url(r'^auth/user/(\d+)/password/$', self.user_change_password, name='admin_change_password'),
+            url('^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', 'django.views.defaults.shortcut')
+        )
 
-        raise http.Http404('The requested admin page does not exist.')
+        for model, model_admin in self._registry.iteritems():
+            urlpatterns += patterns('',
+                url('^%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
-        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 password_change(self, request):
         """
@@ -164,6 +134,7 @@ class AdminSite(object):
         """
         from django.contrib.auth.views import password_change
         return password_change(request)
+    passoword_change = admin_perm_test(password_change)
 
     def password_change_done(self, request):
         """
@@ -171,6 +142,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 user_change_password(self, request, id):
         """
@@ -178,6 +150,7 @@ class AdminSite(object):
         """
         from django.contrib.auth.views import user_change_password
         return user_change_password(request, id)
+    user_change_password = admin_perm_test(user_change_password)
 
     def i18n_javascript(self, request):
         """
@@ -192,6 +165,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):
         """
@@ -309,13 +283,12 @@ class AdminSite(object):
         context = {
             'title': _('Site administration'),
             'app_list': app_list,
-            'root_path': self.root_path,
         }
         context.update(extra_context or {})
         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()
@@ -333,7 +306,6 @@ class AdminSite(object):
             'app_path': request.path,
             'post_data': post_data,
             'error_message': error_message,
-            'root_path': self.root_path,
         }
         context.update(extra_context or {})
         return render_to_response(self.login_template or 'admin/login.html', context,
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
index 998692a..e184353 100644
--- a/django/contrib/auth/admin.py
+++ b/django/contrib/auth/admin.py
@@ -58,7 +58,6 @@ class UserAdmin(admin.ModelAdmin):
             'opts': User._meta,
             'save_as': False,
             'username_help_text': User._meta.get_field('username').help_text,
-            'root_path': self.admin_site.root_path,
         }, context_instance=template.RequestContext(request))
 
 admin.site.register(Group, GroupAdmin)
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index ff0bcbc..ba7908b 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -204,6 +204,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 = {}
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 98ccab6..c79f868 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -84,7 +84,7 @@ class AdminViewPermissionsTest(TestCase):
             '/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 testLogin(self):
@@ -320,4 +320,4 @@ class AdminViewPermissionsTest(TestCase):
         self.assertRedirects(post, '/test_admin/admin/')
         self.failUnlessEqual(Article.objects.all().count(), 0)
         self.client.get('/test_admin/admin/logout/')
-        
\ No newline at end of file
+        
diff --git a/tests/regressiontests/admin_views/urls.py b/tests/regressiontests/admin_views/urls.py
index e556812..4fdfeb4 100644
--- a/tests/regressiontests/admin_views/urls.py
+++ b/tests/regressiontests/admin_views/urls.py
@@ -3,5 +3,5 @@ from django.contrib import admin
 
 urlpatterns = patterns('',
     (r'^admin/doc/', include('django.contrib.admindocs.urls')),
-    (r'^admin/(.*)', admin.site.root),
+    (r'^admin/', include(admin.site.urls)),
 )
