summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/contrib/admin/options.py42
-rw-r--r--django/contrib/admin/sites.py10
-rw-r--r--django/contrib/admin/templates/admin/500.html8
-rw-r--r--django/contrib/admin/templates/admin/app_index.html20
-rw-r--r--django/contrib/admin/templates/admin/auth/user/change_password.html19
-rw-r--r--django/contrib/admin/templates/admin/base.html13
-rw-r--r--django/contrib/admin/templates/admin/change_form.html16
-rw-r--r--django/contrib/admin/templates/admin/change_list.html24
-rw-r--r--django/contrib/admin/templates/admin/delete_confirmation.html12
-rw-r--r--django/contrib/admin/templates/admin/delete_selected_confirmation.html10
-rw-r--r--django/contrib/admin/templates/admin/index.html2
-rw-r--r--django/contrib/admin/templates/admin/invalid_setup.html8
-rw-r--r--django/contrib/admin/templates/admin/object_history.html12
-rw-r--r--django/contrib/admin/templates/registration/logged_out.html5
-rw-r--r--django/contrib/admin/templates/registration/password_change_done.html9
-rw-r--r--django/contrib/admin/templates/registration/password_change_form.html9
-rw-r--r--django/contrib/admin/templates/registration/password_reset_complete.html8
-rw-r--r--django/contrib/admin/templates/registration/password_reset_confirm.html8
-rw-r--r--django/contrib/admin/templates/registration/password_reset_done.html8
-rw-r--r--django/contrib/admin/templates/registration/password_reset_form.html8
-rw-r--r--django/contrib/admin/templatetags/admin_urls.py8
-rw-r--r--django/contrib/admindocs/templates/admin_doc/bookmarklets.html10
-rw-r--r--django/contrib/admindocs/templates/admin_doc/index.html9
-rw-r--r--django/contrib/admindocs/templates/admin_doc/missing_docutils.html8
-rw-r--r--django/contrib/admindocs/templates/admin_doc/model_detail.html13
-rw-r--r--django/contrib/admindocs/templates/admin_doc/model_index.html13
-rw-r--r--django/contrib/admindocs/templates/admin_doc/template_detail.html13
-rw-r--r--django/contrib/admindocs/templates/admin_doc/template_filter_index.html10
-rw-r--r--django/contrib/admindocs/templates/admin_doc/template_tag_index.html10
-rw-r--r--django/contrib/admindocs/templates/admin_doc/view_detail.html13
-rw-r--r--django/contrib/admindocs/templates/admin_doc/view_index.html14
-rw-r--r--django/contrib/admindocs/views.py3
-rw-r--r--docs/ref/contrib/admin/index.txt15
-rw-r--r--tests/regressiontests/admin_custom_urls/__init__.py1
-rw-r--r--tests/regressiontests/admin_custom_urls/fixtures/actions.json44
-rw-r--r--tests/regressiontests/admin_custom_urls/fixtures/users.json20
-rw-r--r--tests/regressiontests/admin_custom_urls/models.py50
-rw-r--r--tests/regressiontests/admin_custom_urls/tests.py72
-rw-r--r--tests/regressiontests/admin_custom_urls/urls.py7
-rw-r--r--tests/regressiontests/admin_views/tests.py4
-rw-r--r--tests/urls.py4
41 files changed, 488 insertions, 104 deletions
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 5271e021ef..2825c99988 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -11,6 +11,7 @@ from django.contrib import messages
from django.views.decorators.csrf import csrf_protect
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.paginator import Paginator
+from django.core.urlresolvers import reverse
from django.db import models, transaction, router
from django.db.models.related import RelatedObject
from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
@@ -776,9 +777,12 @@ 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),
+ current_app=self.admin_site.name)
else:
- post_url = '../../../'
+ post_url = reverse('admin:index',
+ current_app=self.admin_site.name)
return HttpResponseRedirect(post_url)
def response_change(self, request, obj):
@@ -787,11 +791,14 @@ class ModelAdmin(BaseModelAdmin):
"""
opts = obj._meta
- # Handle proxy models automatically created by .only() or .defer()
+ # Handle proxy models automatically created by .only() or .defer().
+ # Refs #14529
verbose_name = opts.verbose_name
+ module_name = opts.module_name
if obj._deferred:
opts_ = opts.proxy_for_model._meta
verbose_name = opts_.verbose_name
+ module_name = opts_.module_name
pk_value = obj._get_pk_val()
@@ -805,19 +812,28 @@ class ModelAdmin(BaseModelAdmin):
elif "_saveasnew" in request.POST:
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(verbose_name), 'obj': obj}
self.message_user(request, msg)
- return HttpResponseRedirect("../%s/" % pk_value)
+ return HttpResponseRedirect(reverse('admin:%s_%s_change' %
+ (opts.app_label, module_name),
+ args=(pk_value,),
+ current_app=self.admin_site.name))
elif "_addanother" in request.POST:
self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(verbose_name)))
- return HttpResponseRedirect("../add/")
+ return HttpResponseRedirect(reverse('admin:%s_%s_add' %
+ (opts.app_label, module_name),
+ current_app=self.admin_site.name))
else:
self.message_user(request, msg)
# Figure out where to redirect. If the user has change permission,
# redirect to the change-list page for this object. Otherwise,
# redirect to the admin index.
if self.has_change_permission(request, None):
- return HttpResponseRedirect('../')
+ post_url = reverse('admin:%s_%s_changelist' %
+ (opts.app_label, module_name),
+ current_app=self.admin_site.name)
else:
- return HttpResponseRedirect('../../../')
+ post_url = reverse('admin:index',
+ current_app=self.admin_site.name)
+ return HttpResponseRedirect(post_url)
def response_action(self, request, queryset):
"""
@@ -990,7 +1006,9 @@ 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 "_saveasnew" in request.POST:
- 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),
+ current_app=self.admin_site.name))
ModelForm = self.get_form(request, obj)
formsets = []
@@ -1246,8 +1264,11 @@ 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',
+ current_app=self.admin_site.name))
+ return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
+ (opts.app_label, opts.module_name),
+ current_app=self.admin_site.name))
object_name = force_unicode(opts.verbose_name)
@@ -1292,6 +1313,7 @@ class ModelAdmin(BaseModelAdmin):
'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
'object': obj,
'app_label': app_label,
+ 'opts': opts,
}
context.update(extra_context or {})
return TemplateResponse(request, self.object_history_template or [
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index 9bb9f4ab46..3a5c12b70a 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -339,9 +339,11 @@ class AdminSite(object):
# Check whether user has any perm for this module.
# If so, add the module to the model_list.
if True in perms.values():
+ info = (app_label, model._meta.module_name)
model_dict = {
'name': capfirst(model._meta.verbose_name_plural),
- 'admin_url': mark_safe('%s/%s/' % (app_label, model.__name__.lower())),
+ 'admin_url': reverse('admin:%s_%s_changelist' % info, current_app=self.name),
+ 'add_url': reverse('admin:%s_%s_add' % info, current_app=self.name),
'perms': perms,
}
if app_label in app_dict:
@@ -349,7 +351,7 @@ class AdminSite(object):
else:
app_dict[app_label] = {
'name': app_label.title(),
- 'app_url': app_label + '/',
+ 'app_url': reverse('admin:app_list', kwargs={'app_label': app_label}, current_app=self.name),
'has_module_perms': has_module_perms,
'models': [model_dict],
}
@@ -383,9 +385,11 @@ class AdminSite(object):
# Check whether user has any perm for this module.
# If so, add the module to the model_list.
if True in perms.values():
+ info = (app_label, model._meta.module_name)
model_dict = {
'name': capfirst(model._meta.verbose_name_plural),
- 'admin_url': '%s/' % model.__name__.lower(),
+ 'admin_url': reverse('admin:%s_%s_changelist' % info, current_app=self.name),
+ 'add_url': reverse('admin:%s_%s_add' % info, current_app=self.name),
'perms': perms,
}
if app_dict:
diff --git a/django/contrib/admin/templates/admin/500.html b/django/contrib/admin/templates/admin/500.html
index b30e43170d..eeb9e8da79 100644
--- a/django/contrib/admin/templates/admin/500.html
+++ b/django/contrib/admin/templates/admin/500.html
@@ -1,7 +1,13 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans "Home" %}</a> &rsaquo; {% trans "Server error" %}</div>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; {% trans 'Server error' %}
+</div>
+{% endblock %}
{% block title %}{% trans 'Server error (500)' %}{% endblock %}
diff --git a/django/contrib/admin/templates/admin/app_index.html b/django/contrib/admin/templates/admin/app_index.html
index 120433d708..4616b16d3d 100644
--- a/django/contrib/admin/templates/admin/app_index.html
+++ b/django/contrib/admin/templates/admin/app_index.html
@@ -1,15 +1,17 @@
-{% extends "admin/index.html" %}
-{% load i18n %}
+{% extends "admin/index.html" %}
+{% load i18n %}
+{% load url from future %}
{% if not is_popup %}
-
{% block breadcrumbs %}
-<div class="breadcrumbs"><a href="../">
-{% trans "Home" %}</a> &rsaquo;
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo;
{% for app in app_list %}
{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}
-{% endfor %}</div>{% endblock %}
-
-{% endif %}
+{% endfor %}
+</div>
+{% endblock %}
+{% endif %}
-{% block sidebar %}{% endblock %} \ No newline at end of file
+{% block sidebar %}{% endblock %}
diff --git a/django/contrib/admin/templates/admin/auth/user/change_password.html b/django/contrib/admin/templates/admin/auth/user/change_password.html
index b18b0aabf3..f79eacd459 100644
--- a/django/contrib/admin/templates/admin/auth/user/change_password.html
+++ b/django/contrib/admin/templates/admin/auth/user/change_password.html
@@ -1,21 +1,24 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_static admin_modify %}
{% load url from future %}
+{% load admin_urls %}
+
{% block extrahead %}{{ block.super }}
{% url 'admin:jsi18n' as jsi18nurl %}
<script type="text/javascript" src="{{ jsi18nurl|default:"../../../../jsi18n/" }}"></script>
{% endblock %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %}
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
-{% block breadcrumbs %}{% if not is_popup %}
-<div class="breadcrumbs">
- <a href="../../../../">{% trans "Home" %}</a> &rsaquo;
- <a href="../../../">{{ opts.app_label|capfirst|escape }}</a> &rsaquo;
- <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
- <a href="../">{{ original|truncatewords:"18" }}</a> &rsaquo;
- {% trans 'Change password' %}
+{% if not is_popup %}
+{% block breadcrumbs %}
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_label|capfirst|escape }}</a>
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}{{ original.pk }}">{{ original|truncatewords:"18" }}</a>
+&rsaquo; {% trans 'Change password' %}
</div>
-{% endif %}{% endblock %}
+{% endblock %}
+{% endif %}
{% block content %}<div id="content-main">
<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
<div>
diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html
index 4b3c42934f..3b50adcd6e 100644
--- a/django/contrib/admin/templates/admin/base.html
+++ b/django/contrib/admin/templates/admin/base.html
@@ -32,17 +32,20 @@
{% if docsroot %}
<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
{% endif %}
- <a href="{% url 'admin:password_change' %}">
- {% trans 'Change password' %}</a> /
- <a href="{% url 'admin:logout' %}">
- {% trans 'Log out' %}</a>
+ <a href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a> /
+ <a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>
{% endblock %}
</div>
{% endif %}
{% block nav-global %}{% endblock %}
</div>
<!-- END Header -->
- {% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans 'Home' %}</a>{% if title %} &rsaquo; {{ title }}{% endif %}</div>{% endblock %}
+ {% block breadcrumbs %}
+ <div class="breadcrumbs">
+ <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+ {% if title %} &rsaquo; {{ title }}{% endif %}
+ </div>
+ {% endblock %}
{% endif %}
{% block messages %}
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
index 56661e9a8e..0e2803d671 100644
--- a/django/contrib/admin/templates/admin/change_form.html
+++ b/django/contrib/admin/templates/admin/change_form.html
@@ -1,6 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_static admin_modify %}
{% load url from future %}
+{% load admin_urls %}
{% block extrahead %}{{ block.super }}
{% url 'admin:jsi18n' as jsi18nurl %}
@@ -14,14 +15,15 @@
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
-{% block breadcrumbs %}{% if not is_popup %}
-<div class="breadcrumbs">
- <a href="../../../">{% trans "Home" %}</a> &rsaquo;
- <a href="../../">{{ app_label|capfirst|escape }}</a> &rsaquo;
- {% if has_change_permission %}<a href="../">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %} &rsaquo;
- {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
+{% if not is_popup %}
+{% block breadcrumbs %}
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ app_label|capfirst|escape }}</a>
+&rsaquo; {% if has_change_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
+&rsaquo; {% if add %}{% trans 'Add' %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
</div>
-{% endif %}{% endblock %}
+{% endblock %}
+{% endif %}
{% block content %}<div id="content-main">
{% block object-tools %}
diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html
index 24c6d8cced..98869cfa5c 100644
--- a/django/contrib/admin/templates/admin/change_list.html
+++ b/django/contrib/admin/templates/admin/change_list.html
@@ -1,6 +1,8 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_static admin_list %}
{% load url from future %}
+{% load admin_urls %}
+
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static "admin/css/changelists.css" %}" />
@@ -36,19 +38,13 @@
{% block bodyclass %}change-list{% endblock %}
{% if not is_popup %}
- {% block breadcrumbs %}
- <div class="breadcrumbs">
- <a href="../../">
- {% trans "Home" %}
- </a>
- &rsaquo;
- <a href="../">
- {{ app_label|capfirst }}
- </a>
- &rsaquo;
- {{ cl.opts.verbose_name_plural|capfirst }}
- </div>
- {% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'admin:app_list' app_label=cl.opts.app_label %}">{{ app_label|capfirst|escape }}</a>
+&rsaquo; {{ cl.opts.verbose_name_plural|capfirst }}
+</div>
+{% endblock %}
{% endif %}
{% block coltype %}flex{% endblock %}
@@ -60,7 +56,7 @@
<ul class="object-tools">
{% block object-tools-items %}
<li>
- <a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">
+ <a href="{% url cl.opts|admin_urlname:'add' %}{% if is_popup %}?_popup=1{% endif %}" class="addlink">
{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
</a>
</li>
diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html
index 0e6d47e68d..f8b36e9c40 100644
--- a/django/contrib/admin/templates/admin/delete_confirmation.html
+++ b/django/contrib/admin/templates/admin/delete_confirmation.html
@@ -1,13 +1,15 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
+{% load admin_urls %}
{% block breadcrumbs %}
<div class="breadcrumbs">
- <a href="../../../../">{% trans "Home" %}</a> &rsaquo;
- <a href="../../../">{{ app_label|capfirst }}</a> &rsaquo;
- <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
- <a href="../">{{ object|truncatewords:"18" }}</a> &rsaquo;
- {% trans 'Delete' %}
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ app_label|capfirst }}</a>
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst|escape }}</a>
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}{{ object.pk }}">{{ object|truncatewords:"18" }}</a>
+&rsaquo; {% trans 'Delete' %}
</div>
{% endblock %}
diff --git a/django/contrib/admin/templates/admin/delete_selected_confirmation.html b/django/contrib/admin/templates/admin/delete_selected_confirmation.html
index 127519bea7..3120cc5289 100644
--- a/django/contrib/admin/templates/admin/delete_selected_confirmation.html
+++ b/django/contrib/admin/templates/admin/delete_selected_confirmation.html
@@ -1,12 +1,14 @@
{% extends "admin/base_site.html" %}
{% load i18n l10n %}
+{% load url from future %}
+{% load admin_urls %}
{% block breadcrumbs %}
<div class="breadcrumbs">
- <a href="../../">{% trans "Home" %}</a> &rsaquo;
- <a href="../">{{ app_label|capfirst }}</a> &rsaquo;
- <a href="./">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
- {% trans 'Delete multiple objects' %}
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'admin:app_list' app_label=app_label %}">{{ app_label|capfirst|escape }}</a>
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
+&rsaquo; {% trans 'Delete multiple objects' %}
</div>
{% endblock %}
diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html
index 7164220ce8..86c795b249 100644
--- a/django/contrib/admin/templates/admin/index.html
+++ b/django/contrib/admin/templates/admin/index.html
@@ -26,7 +26,7 @@
{% endif %}
{% if model.perms.add %}
- <td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
+ <td><a href="{{ model.add_url }}" class="addlink">{% trans 'Add' %}</a></td>
{% else %}
<td>&nbsp;</td>
{% endif %}
diff --git a/django/contrib/admin/templates/admin/invalid_setup.html b/django/contrib/admin/templates/admin/invalid_setup.html
index f09b316b06..c2df4df7f0 100644
--- a/django/contrib/admin/templates/admin/invalid_setup.html
+++ b/django/contrib/admin/templates/admin/invalid_setup.html
@@ -1,7 +1,13 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {{ title }}</div>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; {{ title }}
+</div>
+{% endblock %}
{% block content %}
<p>{% trans "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." %}</p>
diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html
index 5ae7847cfa..ebd19609cd 100644
--- a/django/contrib/admin/templates/admin/object_history.html
+++ b/django/contrib/admin/templates/admin/object_history.html
@@ -1,13 +1,15 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
+{% load admin_urls %}
{% block breadcrumbs %}
<div class="breadcrumbs">
- <a href="../../../../">{% trans 'Home' %}</a> &rsaquo;
- <a href="../../../">{{ app_label|capfirst }}</a> &rsaquo;
- <a href="../../">{{ module_name }}</a> &rsaquo;
- <a href="../">{{ object|truncatewords:"18" }}</a> &rsaquo;
- {% trans 'History' %}
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'admin:app_list' app_label=app_label %}">{{ app_label|capfirst|escape }}</a>
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ module_name }}</a>
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}{{ object.pk }}">{{ object|truncatewords:"18" }}</a>
+&rsaquo; {% trans 'History' %}
</div>
{% endblock %}
diff --git a/django/contrib/admin/templates/registration/logged_out.html b/django/contrib/admin/templates/registration/logged_out.html
index d339ef0a49..e95d864803 100644
--- a/django/contrib/admin/templates/registration/logged_out.html
+++ b/django/contrib/admin/templates/registration/logged_out.html
@@ -1,12 +1,13 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a></div>{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="{% url 'admin:index' %}">{% trans 'Home' %}</a></div>{% endblock %}
{% block content %}
<p>{% trans "Thanks for spending some quality time with the Web site today." %}</p>
-<p><a href="../">{% trans 'Log in again' %}</a></p>
+<p><a href="{% url 'admin:index' %}">{% trans 'Log in again' %}</a></p>
{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_change_done.html b/django/contrib/admin/templates/registration/password_change_done.html
index 0c0690d5e2..863fc9671d 100644
--- a/django/contrib/admin/templates/registration/password_change_done.html
+++ b/django/contrib/admin/templates/registration/password_change_done.html
@@ -1,8 +1,13 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% load url from future %}
-{% block userlinks %}{% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %}{% trans 'Change password' %} / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
+{% block userlinks %}{% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %}{% trans 'Change password' %} / <a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; {% trans 'Password change' %}
+</div>
+{% endblock %}
{% block title %}{% trans 'Password change successful' %}{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_change_form.html b/django/contrib/admin/templates/registration/password_change_form.html
index f76692d185..0e4ea535af 100644
--- a/django/contrib/admin/templates/registration/password_change_form.html
+++ b/django/contrib/admin/templates/registration/password_change_form.html
@@ -2,8 +2,13 @@
{% load i18n static %}
{% load url from future %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %}
-{% block userlinks %}{% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %} {% trans 'Change password' %} / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
+{% block userlinks %}{% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %} {% trans 'Change password' %} / <a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; {% trans 'Password change' %}
+</div>
+{% endblock %}
{% block title %}{% trans 'Password change' %}{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_reset_complete.html b/django/contrib/admin/templates/registration/password_reset_complete.html
index fceb167a88..7c07707f09 100644
--- a/django/contrib/admin/templates/registration/password_reset_complete.html
+++ b/django/contrib/admin/templates/registration/password_reset_complete.html
@@ -1,7 +1,13 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; {% trans 'Password reset' %}
+</div>
+{% endblock %}
{% block title %}{% trans 'Password reset complete' %}{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_reset_confirm.html b/django/contrib/admin/templates/registration/password_reset_confirm.html
index df9cf1b316..d5299ebd91 100644
--- a/django/contrib/admin/templates/registration/password_reset_confirm.html
+++ b/django/contrib/admin/templates/registration/password_reset_confirm.html
@@ -1,7 +1,13 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset confirmation' %}</div>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; {% trans 'Password reset confirmation' %}
+</div>
+{% endblock %}
{% block title %}{% trans 'Password reset' %}{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_reset_done.html b/django/contrib/admin/templates/registration/password_reset_done.html
index e223bdb9de..fd73af8928 100644
--- a/django/contrib/admin/templates/registration/password_reset_done.html
+++ b/django/contrib/admin/templates/registration/password_reset_done.html
@@ -1,7 +1,13 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; {% trans 'Password reset' %}
+</div>
+{% endblock %}
{% block title %}{% trans 'Password reset successful' %}{% endblock %}
diff --git a/django/contrib/admin/templates/registration/password_reset_form.html b/django/contrib/admin/templates/registration/password_reset_form.html
index d3a128428a..2b591fe22e 100644
--- a/django/contrib/admin/templates/registration/password_reset_form.html
+++ b/django/contrib/admin/templates/registration/password_reset_form.html
@@ -1,7 +1,13 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; {% trans 'Password reset' %}
+</div>
+{% endblock %}
{% block title %}{% trans "Password reset" %}{% endblock %}
diff --git a/django/contrib/admin/templatetags/admin_urls.py b/django/contrib/admin/templatetags/admin_urls.py
new file mode 100644
index 0000000000..53dc65b567
--- /dev/null
+++ b/django/contrib/admin/templatetags/admin_urls.py
@@ -0,0 +1,8 @@
+from django.core.urlresolvers import reverse, NoReverseMatch
+from django import template
+
+register = template.Library()
+
+@register.filter
+def admin_urlname(value, arg):
+ return 'admin:%s_%s_%s' % (value.app_label, value.module_name, arg)
diff --git a/django/contrib/admindocs/templates/admin_doc/bookmarklets.html b/django/contrib/admindocs/templates/admin_doc/bookmarklets.html
index 64475299c2..baa717c4b3 100644
--- a/django/contrib/admindocs/templates/admin_doc/bookmarklets.html
+++ b/django/contrib/admindocs/templates/admin_doc/bookmarklets.html
@@ -1,6 +1,14 @@
{% extends "admin/base_site.html" %}
+{% load i18n %}
+{% load url from future %}
-{% block breadcrumbs %}{% load i18n %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; <a href="../">{% trans "Documentation" %}</a> &rsaquo; {% trans "Bookmarklets" %}</div>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
+&rsaquo; {% trans 'Bookmarklets' %}
+</div>
+{% endblock %}
{% block title %}{% trans "Documentation bookmarklets" %}{% endblock %}
{% block content %}
diff --git a/django/contrib/admindocs/templates/admin_doc/index.html b/django/contrib/admindocs/templates/admin_doc/index.html
index a8b21c330d..652fbfd576 100644
--- a/django/contrib/admindocs/templates/admin_doc/index.html
+++ b/django/contrib/admindocs/templates/admin_doc/index.html
@@ -1,6 +1,13 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="{{ root_path }}">Home</a> &rsaquo; Documentation</div>{% endblock %}
+{% load url from future %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; {% trans 'Documentation' %}</a>
+</div>
+{% endblock %}
{% block title %}Documentation{% endblock %}
{% block content %}
diff --git a/django/contrib/admindocs/templates/admin_doc/missing_docutils.html b/django/contrib/admindocs/templates/admin_doc/missing_docutils.html
index 97c9d4711c..34c18ffb7d 100644
--- a/django/contrib/admindocs/templates/admin_doc/missing_docutils.html
+++ b/django/contrib/admindocs/templates/admin_doc/missing_docutils.html
@@ -1,6 +1,12 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
+{% load url from future %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; {% trans 'Documentation' %}</a>
+</div>
{% block title %}Please install docutils{% endblock %}
{% block content %}
diff --git a/django/contrib/admindocs/templates/admin_doc/model_detail.html b/django/contrib/admindocs/templates/admin_doc/model_detail.html
index 828df18377..82cb405639 100644
--- a/django/contrib/admindocs/templates/admin_doc/model_detail.html
+++ b/django/contrib/admindocs/templates/admin_doc/model_detail.html
@@ -1,5 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
+
{% block extrahead %}
{{ block.super }}
<style type="text/css">
@@ -8,7 +10,14 @@
</style>
{% endblock %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Models</a> &rsaquo; {{ name }}</div>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
+&rsaquo; <a href="{% url 'django-admindocs-models-index' %}">{% trans 'Models' %}</a>
+&rsaquo; {{ name }}
+</div>
+{% endblock %}
{% block title %}Model: {{ name }}{% endblock %}
@@ -41,6 +50,6 @@
</table>
</div>
-<p class="small"><a href="../">&lsaquo; Back to Models Documentation</a></p>
+<p class="small"><a href="{% url 'django-admindocs-models-index' %}">&lsaquo; Back to Models Documentation</a></p>
</div>
{% endblock %}
diff --git a/django/contrib/admindocs/templates/admin_doc/model_index.html b/django/contrib/admindocs/templates/admin_doc/model_index.html
index 47c94c0c70..4eb3cf69c9 100644
--- a/django/contrib/admindocs/templates/admin_doc/model_index.html
+++ b/django/contrib/admindocs/templates/admin_doc/model_index.html
@@ -1,7 +1,16 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
+
{% block coltype %}colSM{% endblock %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Models</div>{% endblock %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
+&rsaquo; {% trans 'Models' %}
+</div>
+{% endblock %}
{% block title %}Models{% endblock %}
@@ -19,7 +28,7 @@
<table class="xfull">
{% for model in group.list %}
<tr>
-<th><a href="{{ model.app_label }}.{{ model.object_name.lower }}/">{{ model.object_name }}</a></th>
+<th><a href="{% url 'django-admindocs-models-detail' app_label=model.app_label model_name=model.object_name.lower %}">{{ model.object_name }}</a></th>
</tr>
{% endfor %}
</table>
diff --git a/django/contrib/admindocs/templates/admin_doc/template_detail.html b/django/contrib/admindocs/templates/admin_doc/template_detail.html
index c04dedc530..307dd627df 100644
--- a/django/contrib/admindocs/templates/admin_doc/template_detail.html
+++ b/django/contrib/admindocs/templates/admin_doc/template_detail.html
@@ -1,6 +1,15 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; Templates &rsaquo; {{ name }}</div>{% endblock %}
+{% load url from future %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
+&rsaquo; {% trans 'Templates' %}
+&rsaquo; {{ name }}
+</div>
+{% endblock %}
{% block title %}Template: {{ name }}{% endblock %}
@@ -17,5 +26,5 @@
</ol>
{% endfor %}
-<p class="small"><a href="../../">&lsaquo; Back to Documentation</a></p>
+<p class="small"><a href="{% url 'django-admindocs-docroot' %}">&lsaquo; Back to Documentation</a></p>
{% endblock %}
diff --git a/django/contrib/admindocs/templates/admin_doc/template_filter_index.html b/django/contrib/admindocs/templates/admin_doc/template_filter_index.html
index 46ccf0fd0a..1809bc9182 100644
--- a/django/contrib/admindocs/templates/admin_doc/template_filter_index.html
+++ b/django/contrib/admindocs/templates/admin_doc/template_filter_index.html
@@ -1,7 +1,15 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
+
{% block coltype %}colSM{% endblock %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; filters</div>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
+&rsaquo; {% trans 'Filters' %}
+</div>
+{% endblock %}
{% block title %}Template filters{% endblock %}
{% block content %}
diff --git a/django/contrib/admindocs/templates/admin_doc/template_tag_index.html b/django/contrib/admindocs/templates/admin_doc/template_tag_index.html
index 676c025acb..18e5d95298 100644
--- a/django/contrib/admindocs/templates/admin_doc/template_tag_index.html
+++ b/django/contrib/admindocs/templates/admin_doc/template_tag_index.html
@@ -1,7 +1,15 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
+
{% block coltype %}colSM{% endblock %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Tags</div>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
+&rsaquo; {% trans 'Tags' %}
+</div>
+{% endblock %}
{% block title %}Template tags{% endblock %}
{% block content %}
diff --git a/django/contrib/admindocs/templates/admin_doc/view_detail.html b/django/contrib/admindocs/templates/admin_doc/view_detail.html
index c6d080c9ef..41c812110f 100644
--- a/django/contrib/admindocs/templates/admin_doc/view_detail.html
+++ b/django/contrib/admindocs/templates/admin_doc/view_detail.html
@@ -1,6 +1,15 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Views</a> &rsaquo; {{ name }}</div>{% endblock %}
+{% load url from future %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
+&rsaquo; <a href="{% url 'django-admindocs-views-index' %}">{% trans 'Views' %}</a>
+&rsaquo; {{ name }}
+</div>
+{% endblock %}
{% block title %}View: {{ name }}{% endblock %}
{% block content %}
@@ -21,5 +30,5 @@
<p>{{ meta.Templates }}</p>
{% endif %}
-<p class="small"><a href="../">&lsaquo; Back to Views Documentation</a></p>
+<p class="small"><a href="{% url 'django-admindocs-views-index' %}">&lsaquo; Back to Views Documentation</a></p>
{% endblock %}
diff --git a/django/contrib/admindocs/templates/admin_doc/view_index.html b/django/contrib/admindocs/templates/admin_doc/view_index.html
index 6b10fa65e3..e508c84e19 100644
--- a/django/contrib/admindocs/templates/admin_doc/view_index.html
+++ b/django/contrib/admindocs/templates/admin_doc/view_index.html
@@ -1,7 +1,15 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
+{% load url from future %}
+
{% block coltype %}colSM{% endblock %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Views</div>{% endblock %}
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
+&rsaquo; {% trans 'Views' %}
+</div>
+{% endblock %}
{% block title %}Views{% endblock %}
{% block content %}
@@ -29,8 +37,8 @@
{% for view in site_views.list|dictsort:"url" %}
{% ifchanged %}
-<h3><a href="{{ view.module }}.{{ view.name }}/">{{ view.url }}</a></h3>
-<p class="small quiet">View function: {{ view.module }}.{{ view.name }}</p>
+<h3><a href="{% url 'django-admindocs-views-detail' view=view.full_name %}">{{ view.url }}</a></h3>
+<p class="small quiet">View function: {{ view.full_name }}</p>
<p>{{ view.title }}</p>
<hr />
{% endifchanged %}
diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py
index 28319be9d5..9725865d81 100644
--- a/django/contrib/admindocs/views.py
+++ b/django/contrib/admindocs/views.py
@@ -136,8 +136,7 @@ def view_index(request):
site_obj = GenericSite()
for (func, regex) in view_functions:
views.append({
- 'name': getattr(func, '__name__', func.__class__.__name__),
- 'module': func.__module__,
+ 'full_name': '%s.%s' % (func.__module__, getattr(func, '__name__', func.__class__.__name__)),
'site_id': settings_mod.SITE_ID,
'site': site_obj,
'url': simplify_regex(regex),
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index 2da6774d44..21145c5652 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -1960,3 +1960,18 @@ if you specifically wanted the admin view from the admin instance named
For more details, see the documentation on :ref:`reversing namespaced URLs
<topics-http-reversing-url-namespaces>`.
+
+To allow easier reversing of the admin urls in templates, Django provides an
+``admin_url`` filter which takes an action as argument:
+
+.. code-block:: html+django
+
+ {% load admin_urls %}
+ {% load url from future %}
+ <a href="{% url opts|admin_urlname:'add' %}">Add user</a>
+ <a href="{% url opts|admin_urlname:'delete' user.pk %}">Delete this user</a>
+
+The action in the examples above match the last part of the URL names for
+:class:`ModelAdmin` instances described above. The ``opts`` variable can be any
+object which has an ``app_label`` and ``module_name`` and is usually supplied
+by the admin views for the current model.
diff --git a/tests/regressiontests/admin_custom_urls/__init__.py b/tests/regressiontests/admin_custom_urls/__init__.py
new file mode 100644
index 0000000000..792d600548
--- /dev/null
+++ b/tests/regressiontests/admin_custom_urls/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/tests/regressiontests/admin_custom_urls/fixtures/actions.json b/tests/regressiontests/admin_custom_urls/fixtures/actions.json
new file mode 100644
index 0000000000..d803393a12
--- /dev/null
+++ b/tests/regressiontests/admin_custom_urls/fixtures/actions.json
@@ -0,0 +1,44 @@
+[
+ {
+ "pk": "delete",
+ "model": "admin_custom_urls.action",
+ "fields": {
+ "description": "Remove things."
+ }
+ },
+ {
+ "pk": "rename",
+ "model": "admin_custom_urls.action",
+ "fields": {
+ "description": "Gives things other names."
+ }
+ },
+ {
+ "pk": "add",
+ "model": "admin_custom_urls.action",
+ "fields": {
+ "description": "Add things."
+ }
+ },
+ {
+ "pk": "path/to/file/",
+ "model": "admin_custom_urls.action",
+ "fields": {
+ "description": "An action with '/' in its name."
+ }
+ },
+ {
+ "pk": "path/to/html/document.html",
+ "model": "admin_custom_urls.action",
+ "fields": {
+ "description": "An action with a name similar to a HTML doc path."
+ }
+ },
+ {
+ "pk": "javascript:alert('Hello world');\">Click here</a>",
+ "model": "admin_custom_urls.action",
+ "fields": {
+ "description": "An action with a name suspected of being a XSS attempt"
+ }
+ }
+] \ No newline at end of file
diff --git a/tests/regressiontests/admin_custom_urls/fixtures/users.json b/tests/regressiontests/admin_custom_urls/fixtures/users.json
new file mode 100644
index 0000000000..72d86d70ad
--- /dev/null
+++ b/tests/regressiontests/admin_custom_urls/fixtures/users.json
@@ -0,0 +1,20 @@
+[
+ {
+ "pk": 100,
+ "model": "auth.user",
+ "fields": {
+ "username": "super",
+ "first_name": "Super",
+ "last_name": "User",
+ "is_active": true,
+ "is_superuser": true,
+ "is_staff": true,
+ "last_login": "2007-05-30 13:20:10",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158",
+ "email": "super@example.com",
+ "date_joined": "2007-05-30 13:20:10"
+ }
+ }
+]
diff --git a/tests/regressiontests/admin_custom_urls/models.py b/tests/regressiontests/admin_custom_urls/models.py
new file mode 100644
index 0000000000..f8c83a9024
--- /dev/null
+++ b/tests/regressiontests/admin_custom_urls/models.py
@@ -0,0 +1,50 @@
+from functools import update_wrapper
+
+from django.contrib import admin
+from django.db import models
+
+
+class Action(models.Model):
+ name = models.CharField(max_length=50, primary_key=True)
+ description = models.CharField(max_length=70)
+
+ def __unicode__(self):
+ return self.name
+
+
+class ActionAdmin(admin.ModelAdmin):
+ """
+ A ModelAdmin for the Action model that changes the URL of the add_view
+ to '<app name>/<model name>/!add/'
+ The Action model has a CharField PK.
+ """
+
+ list_display = ('name', 'description')
+
+ def remove_url(self, name):
+ """
+ Remove all entries named 'name' from the ModelAdmin instance URL
+ patterns list
+ """
+ return filter(lambda e: e.name != name, super(ActionAdmin, self).get_urls())
+
+ def get_urls(self):
+ # Add the URL of our custom 'add_view' view to the front of the URLs
+ # list. Remove the existing one(s) first
+ 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.model._meta.app_label, self.model._meta.module_name
+
+ view_name = '%s_%s_add' % info
+
+ return patterns('',
+ url(r'^!add/$', wrap(self.add_view), name=view_name),
+ ) + self.remove_url(view_name)
+
+
+admin.site.register(Action, ActionAdmin)
diff --git a/tests/regressiontests/admin_custom_urls/tests.py b/tests/regressiontests/admin_custom_urls/tests.py
new file mode 100644
index 0000000000..cfc6b8583e
--- /dev/null
+++ b/tests/regressiontests/admin_custom_urls/tests.py
@@ -0,0 +1,72 @@
+from django.core.urlresolvers import reverse
+from django.template.response import TemplateResponse
+from django.test import TestCase
+
+from models import Action
+
+
+class AdminCustomUrlsTest(TestCase):
+ fixtures = ['users.json', 'actions.json']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def testBasicAddGet(self):
+ """
+ A smoke test to ensure GET on the add_view works.
+ """
+ response = self.client.get('/custom_urls/admin/admin_custom_urls/action/!add/')
+ self.assertIsInstance(response, TemplateResponse)
+ self.assertEqual(response.status_code, 200)
+
+ def testAddWithGETArgs(self):
+ response = self.client.get('/custom_urls/admin/admin_custom_urls/action/!add/', {'name': 'My Action'})
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(
+ 'value="My Action"' in response.content,
+ "Couldn't find an input with the right value in the response."
+ )
+
+ def testBasicAddPost(self):
+ """
+ A smoke test to ensure POST on add_view works.
+ """
+ post_data = {
+ '_popup': u'1',
+ "name": u'Action added through a popup',
+ "description": u"Description of added action",
+ }
+ response = self.client.post('/custom_urls/admin/admin_custom_urls/action/!add/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'dismissAddAnotherPopup')
+ self.assertContains(response, 'Action added through a popup')
+
+ def testAdminUrlsNoClash(self):
+ """
+ Test that some admin URLs work correctly. The model has a CharField
+ PK and the add_view URL has been customized.
+ """
+ # Should get the change_view for model instance with PK 'add', not show
+ # the add_view
+ response = self.client.get('/custom_urls/admin/admin_custom_urls/action/add/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'Change action')
+
+ # Ditto, but use reverse() to build the URL
+ path = reverse('admin:%s_action_change' % Action._meta.app_label,
+ args=('add',))
+ response = self.client.get(path)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'Change action')
+
+ # Should correctly get the change_view for the model instance with the
+ # funny-looking PK
+ path = reverse('admin:%s_action_change' % Action._meta.app_label,
+ args=("path/to/html/document.html",))
+ response = self.client.get(path)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'Change action')
+ self.assertContains(response, 'value="path/to/html/document.html"')
diff --git a/tests/regressiontests/admin_custom_urls/urls.py b/tests/regressiontests/admin_custom_urls/urls.py
new file mode 100644
index 0000000000..6c2761a222
--- /dev/null
+++ b/tests/regressiontests/admin_custom_urls/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import *
+from django.contrib import admin
+
+urlpatterns = patterns('',
+ (r'^admin/', include(admin.site.urls)),
+)
+
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 668c71076e..78457d97ab 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -595,7 +595,7 @@ class SaveAsTests(TestCase):
self.assertTrue(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):
urls = "regressiontests.admin_views.urls"
@@ -842,7 +842,7 @@ class AdminViewPermissionsTest(TestCase):
self.client.post('/test_admin/admin/', self.adduser_login)
addpage = self.client.get('/test_admin/admin/admin_views/article/add/')
self.assertEqual(addpage.status_code, 200)
- change_list_link = '<a href="../">Articles</a> &rsaquo;'
+ change_list_link = '&rsaquo; <a href="/test_admin/admin/admin_views/article/">Articles</a>'
self.assertFalse(change_list_link in addpage.content,
'User restricted to add permission is given link to change list view in breadcrumbs.')
post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
diff --git a/tests/urls.py b/tests/urls.py
index 044395039d..e7c23e5144 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -1,6 +1,5 @@
from django.conf.urls import patterns, include
-
urlpatterns = patterns('',
# test_client modeltest urls
(r'^test_client/', include('modeltests.test_client.urls')),
@@ -25,4 +24,7 @@ urlpatterns = patterns('',
# admin widget tests
(r'widget_admin/', include('regressiontests.admin_widgets.urls')),
+ # admin custom URL tests
+ (r'^custom_urls/', include('regressiontests.admin_custom_urls.urls')),
+
)