diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py
index b75c91b..e98d4eb 100644
--- a/django/contrib/admin/actions.py
+++ b/django/contrib/admin/actions.py
@@ -32,7 +32,7 @@ def delete_selected(modeladmin, request, queryset):
 
     # Populate deletable_objects, a data structure of all related objects that
     # will also be deleted.
-    deletable_objects, perms_needed = get_deleted_objects(queryset, opts, request.user, modeladmin.admin_site, levels_to_root=2)
+    deletable_objects, perms_needed = get_deleted_objects(queryset, opts, request.user, modeladmin.admin_site)
 
     # The user has already confirmed the deletion.
     # Do the deletion and return a None to display the change list view again.
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 1f8ff6d..97210e5 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -9,6 +9,7 @@ from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_ob
 from django.contrib import messages
 from django.views.decorators.csrf import csrf_protect
 from django.core.exceptions import PermissionDenied, ValidationError
+from django.core.urlresolvers import reverse
 from django.db import models, transaction
 from django.db.models.fields import BLANK_CHOICE_DASH
 from django.http import Http404, HttpResponse, HttpResponseRedirect
@@ -663,9 +664,10 @@ class ModelAdmin(BaseModelAdmin):
             # redirect to the change-list page for this object. Otherwise,
             # redirect to the admin index.
             if self.has_change_permission(request, None):
-                post_url = '../'
+                post_url = reverse('admin:%s_%s_changelist' %
+                                   (opts.app_label, opts.module_name))
             else:
-                post_url = '../../../'
+                post_url = reverse('admin:index')
             return HttpResponseRedirect(post_url)
 
     def response_change(self, request, obj):
@@ -685,13 +687,18 @@ class ModelAdmin(BaseModelAdmin):
         elif request.POST.has_key("_saveasnew"):
             msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj}
             self.message_user(request, msg)
-            return HttpResponseRedirect("../%s/" % pk_value)
+            return HttpResponseRedirect(reverse('admin:%s_%s_change' %
+                                        (opts.app_label, opts.module_name),
+                                        args=pk_value))
+
         elif request.POST.has_key("_addanother"):
             self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
-            return HttpResponseRedirect("../add/")
+            return HttpResponseRedirect(reverse('admin:%s_%s_add' %
+                                        (opts.app_label, opts.module_name)))
         else:
             self.message_user(request, msg)
-            return HttpResponseRedirect("../")
+            return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
+                                        (opts.app_label, opts.module_name)))
 
     def response_action(self, request, queryset):
         """
@@ -866,7 +873,8 @@ class ModelAdmin(BaseModelAdmin):
             raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
 
         if request.method == 'POST' and request.POST.has_key("_saveasnew"):
-            return self.add_view(request, form_url='../add/')
+            return self.add_view(request, form_url=reverse('admin:%s_%s_add' %
+                                    (opts.app_label, opts.module_name)))
 
         ModelForm = self.get_form(request, obj)
         formsets = []
@@ -1118,8 +1126,9 @@ class ModelAdmin(BaseModelAdmin):
             self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})
 
             if not self.has_change_permission(request, None):
-                return HttpResponseRedirect("../../../../")
-            return HttpResponseRedirect("../../")
+                return HttpResponseRedirect(reverse('admin:index'))
+            return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
+                                        (opts.app_label, opts.module_name)))
 
         context = {
             "title": _("Are you sure?"),
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index 4446490..e451f8f 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -456,80 +456,6 @@ class AdminSite(object):
             context_instance=context_instance
         )
 
-    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.",
-            DeprecationWarning
-        )
-
-        #
-        # 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.
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
index 776a6f0..74c4a18 100644
--- a/django/contrib/admin/util.py
+++ b/django/contrib/admin/util.py
@@ -7,7 +7,7 @@ from django.utils.safestring import mark_safe
 from django.utils.text import capfirst
 from django.utils.encoding import force_unicode, smart_unicode, smart_str
 from django.utils.translation import ungettext, ugettext as _
-from django.core.urlresolvers import reverse, NoReverseMatch
+from django.core.urlresolvers import reverse
 from django.utils.datastructures import SortedDict
 
 def quote(s):
@@ -58,21 +58,15 @@ def flatten_fieldsets(fieldsets):
                 field_names.append(field)
     return field_names
 
-def _format_callback(obj, user, admin_site, levels_to_root, perms_needed):
+def _format_callback(obj, user, admin_site, perms_needed):
     has_admin = obj.__class__ in admin_site._registry
     opts = obj._meta
-    try:
+    if has_admin:
         admin_url = reverse('%s:%s_%s_change'
                             % (admin_site.name,
                                opts.app_label,
                                opts.object_name.lower()),
                             None, (quote(obj._get_pk_val()),))
-    except NoReverseMatch:
-        admin_url = '%s%s/%s/%s/' % ('../'*levels_to_root,
-                                     opts.app_label,
-                                     opts.object_name.lower(),
-                                     quote(obj._get_pk_val()))
-    if has_admin:
         p = '%s.%s' % (opts.app_label,
                        opts.get_delete_permission())
         if not user.has_perm(p):
@@ -88,7 +82,7 @@ def _format_callback(obj, user, admin_site, levels_to_root, perms_needed):
         return u'%s: %s' % (capfirst(opts.verbose_name),
                             force_unicode(obj))
 
-def get_deleted_objects(objs, opts, user, admin_site, levels_to_root=4):
+def get_deleted_objects(objs, opts, user, admin_site, levels_to_root=0):
     """
     Find all objects related to ``objs`` that should also be
     deleted. ``objs`` should be an iterable of objects.
@@ -96,13 +90,8 @@ def get_deleted_objects(objs, opts, user, admin_site, levels_to_root=4):
     Returns a nested list of strings suitable for display in the
     template with the ``unordered_list`` filter.
 
-    `levels_to_root` defines the number of directories (../) to reach
-    the admin root path. In a change_view this is 4, in a change_list
-    view 2.
-
-    This is for backwards compatibility since the options.delete_selected
-    method uses this function also from a change_list view.
-    This will not be used if we can reverse the URL.
+    `levels_to_root` is for backwards compatibility api compability, it is not
+    used at all.
     """
     collector = NestedObjects()
     for obj in objs:
@@ -114,7 +103,6 @@ def get_deleted_objects(objs, opts, user, admin_site, levels_to_root=4):
     to_delete = collector.nested(_format_callback,
                                  user=user,
                                  admin_site=admin_site,
-                                 levels_to_root=levels_to_root,
                                  perms_needed=perms_needed)
 
     return to_delete, perms_needed
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 1d321d0..66a84f2 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -13,7 +13,7 @@ from django.utils.translation import ugettext as _
 from django.utils.safestring import mark_safe
 from django.utils.encoding import force_unicode
 from django.conf import settings
-from django.core.urlresolvers import reverse, NoReverseMatch
+from django.core.urlresolvers import reverse
 
 class FilteredSelectMultiple(forms.SelectMultiple):
     """
@@ -113,6 +113,7 @@ class ForeignKeyRawIdWidget(forms.TextInput):
     def render(self, name, value, attrs=None):
         if attrs is None:
             attrs = {}
+        # TODO, only add link if related object is in the admin...
         related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
         params = self.url_parameters()
         if params:
@@ -229,14 +230,10 @@ class RelatedFieldWidgetWrapper(forms.Widget):
     def render(self, name, value, *args, **kwargs):
         rel_to = self.rel.to
         info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
-        try:
-            related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
-        except NoReverseMatch:
-            info = (self.admin_site.root_path, rel_to._meta.app_label, rel_to._meta.object_name.lower())
-            related_url = '%s%s/%s/add/' % info
         self.widget.choices = self.choices
         output = [self.widget.render(name, value, *args, **kwargs)]
         if rel_to in self.admin_site._registry: # If the related object has an admin interface:
+            related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
             # TODO: "id_" is hard-coded here. This should instead use the correct
             # API to determine the ID dynamically.
             output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/regressiontests/admin_inlines/models.py
index 5a12e07..2998373 100644
--- a/tests/regressiontests/admin_inlines/models.py
+++ b/tests/regressiontests/admin_inlines/models.py
@@ -43,6 +43,7 @@ class BookInline(admin.TabularInline):
 class AuthorAdmin(admin.ModelAdmin):
     inlines = [BookInline]
 
+admin.site.register(Book)
 admin.site.register(Author, AuthorAdmin)
 
 class Holder(models.Model):
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 1385e5e..f60bb04 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -324,7 +324,7 @@ class SaveAsTests(TestCase):
         self.assert_(response.context['save_as'])
         post_data = {'_saveasnew':'', 'name':'John M', 'gender':3, 'alive':'checked'}
         response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
-        self.assertEqual(response.context['form_url'], '../add/')
+        self.assertEqual(response.context['form_url'], '/test_admin/admin/admin_views/person/add/')
 
 class CustomModelAdminTest(AdminViewBasicTest):
     urlbit = "admin2"
