Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Converted internal link generation in the admin and admin document ge…

…nerator to use named URLs.

Thanks to Florian Apolloner for both the initial patch and his final push to get
this fixed, to Dario Ocles for his great work on the admin templates and
switching the admin_doc application to also use named URLs, to Mikko Hellsing
for his comments and to Jannis and Julien for their review and design guidance.

Fixes #15294.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16857 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit aaf77c1676e44019abe544911ff7a06eb2690295 1 parent 7b21bfc
Ramiro Morales authored September 20, 2011

Showing 41 changed files with 489 additions and 105 deletions. Show diff stats Hide diff stats

  1. 42  django/contrib/admin/options.py
  2. 10  django/contrib/admin/sites.py
  3. 8  django/contrib/admin/templates/admin/500.html
  4. 20  django/contrib/admin/templates/admin/app_index.html
  5. 19  django/contrib/admin/templates/admin/auth/user/change_password.html
  6. 13  django/contrib/admin/templates/admin/base.html
  7. 16  django/contrib/admin/templates/admin/change_form.html
  8. 24  django/contrib/admin/templates/admin/change_list.html
  9. 12  django/contrib/admin/templates/admin/delete_confirmation.html
  10. 10  django/contrib/admin/templates/admin/delete_selected_confirmation.html
  11. 2  django/contrib/admin/templates/admin/index.html
  12. 8  django/contrib/admin/templates/admin/invalid_setup.html
  13. 12  django/contrib/admin/templates/admin/object_history.html
  14. 5  django/contrib/admin/templates/registration/logged_out.html
  15. 9  django/contrib/admin/templates/registration/password_change_done.html
  16. 9  django/contrib/admin/templates/registration/password_change_form.html
  17. 8  django/contrib/admin/templates/registration/password_reset_complete.html
  18. 8  django/contrib/admin/templates/registration/password_reset_confirm.html
  19. 8  django/contrib/admin/templates/registration/password_reset_done.html
  20. 8  django/contrib/admin/templates/registration/password_reset_form.html
  21. 8  django/contrib/admin/templatetags/admin_urls.py
  22. 12  django/contrib/admindocs/templates/admin_doc/bookmarklets.html
  23. 9  django/contrib/admindocs/templates/admin_doc/index.html
  24. 8  django/contrib/admindocs/templates/admin_doc/missing_docutils.html
  25. 13  django/contrib/admindocs/templates/admin_doc/model_detail.html
  26. 13  django/contrib/admindocs/templates/admin_doc/model_index.html
  27. 13  django/contrib/admindocs/templates/admin_doc/template_detail.html
  28. 10  django/contrib/admindocs/templates/admin_doc/template_filter_index.html
  29. 10  django/contrib/admindocs/templates/admin_doc/template_tag_index.html
  30. 13  django/contrib/admindocs/templates/admin_doc/view_detail.html
  31. 14  django/contrib/admindocs/templates/admin_doc/view_index.html
  32. 3  django/contrib/admindocs/views.py
  33. 15  docs/ref/contrib/admin/index.txt
  34. 1  tests/regressiontests/admin_custom_urls/__init__.py
  35. 44  tests/regressiontests/admin_custom_urls/fixtures/actions.json
  36. 20  tests/regressiontests/admin_custom_urls/fixtures/users.json
  37. 50  tests/regressiontests/admin_custom_urls/models.py
  38. 72  tests/regressiontests/admin_custom_urls/tests.py
  39. 7  tests/regressiontests/admin_custom_urls/urls.py
  40. 4  tests/regressiontests/admin_views/tests.py
  41. 4  tests/urls.py
42  django/contrib/admin/options.py
@@ -11,6 +11,7 @@
11 11
 from django.views.decorators.csrf import csrf_protect
12 12
 from django.core.exceptions import PermissionDenied, ValidationError
13 13
 from django.core.paginator import Paginator
  14
+from django.core.urlresolvers import reverse
14 15
 from django.db import models, transaction, router
15 16
 from django.db.models.related import RelatedObject
16 17
 from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
@@ -776,9 +777,12 @@ def response_add(self, request, obj, post_url_continue='../%s/'):
776 777
             # redirect to the change-list page for this object. Otherwise,
777 778
             # redirect to the admin index.
778 779
             if self.has_change_permission(request, None):
779  
-                post_url = '../'
  780
+                post_url = reverse('admin:%s_%s_changelist' %
  781
+                                   (opts.app_label, opts.module_name),
  782
+                                   current_app=self.admin_site.name)
780 783
             else:
781  
-                post_url = '../../../'
  784
+                post_url = reverse('admin:index',
  785
+                                   current_app=self.admin_site.name)
782 786
             return HttpResponseRedirect(post_url)
783 787
 
784 788
     def response_change(self, request, obj):
@@ -787,11 +791,14 @@ def response_change(self, request, obj):
787 791
         """
788 792
         opts = obj._meta
789 793
 
790  
-        # Handle proxy models automatically created by .only() or .defer()
  794
+        # Handle proxy models automatically created by .only() or .defer().
  795
+        # Refs #14529
791 796
         verbose_name = opts.verbose_name
  797
+        module_name = opts.module_name
792 798
         if obj._deferred:
793 799
             opts_ = opts.proxy_for_model._meta
794 800
             verbose_name = opts_.verbose_name
  801
+            module_name = opts_.module_name
795 802
 
796 803
         pk_value = obj._get_pk_val()
797 804
 
@@ -805,19 +812,28 @@ def response_change(self, request, obj):
805 812
         elif "_saveasnew" in request.POST:
806 813
             msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(verbose_name), 'obj': obj}
807 814
             self.message_user(request, msg)
808  
-            return HttpResponseRedirect("../%s/" % pk_value)
  815
+            return HttpResponseRedirect(reverse('admin:%s_%s_change' %
  816
+                                        (opts.app_label, module_name),
  817
+                                        args=(pk_value,),
  818
+                                        current_app=self.admin_site.name))
809 819
         elif "_addanother" in request.POST:
810 820
             self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(verbose_name)))
811  
-            return HttpResponseRedirect("../add/")
  821
+            return HttpResponseRedirect(reverse('admin:%s_%s_add' %
  822
+                                        (opts.app_label, module_name),
  823
+                                        current_app=self.admin_site.name))
812 824
         else:
813 825
             self.message_user(request, msg)
814 826
             # Figure out where to redirect. If the user has change permission,
815 827
             # redirect to the change-list page for this object. Otherwise,
816 828
             # redirect to the admin index.
817 829
             if self.has_change_permission(request, None):
818  
-                return HttpResponseRedirect('../')
  830
+                post_url = reverse('admin:%s_%s_changelist' %
  831
+                                   (opts.app_label, module_name),
  832
+                                   current_app=self.admin_site.name)
819 833
             else:
820  
-                return HttpResponseRedirect('../../../')
  834
+                post_url = reverse('admin:index',
  835
+                                   current_app=self.admin_site.name)
  836
+            return HttpResponseRedirect(post_url)
821 837
 
822 838
     def response_action(self, request, queryset):
823 839
         """
@@ -990,7 +1006,9 @@ def change_view(self, request, object_id, extra_context=None):
990 1006
             raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
991 1007
 
992 1008
         if request.method == 'POST' and "_saveasnew" in request.POST:
993  
-            return self.add_view(request, form_url='../add/')
  1009
+            return self.add_view(request, form_url=reverse('admin:%s_%s_add' %
  1010
+                                    (opts.app_label, opts.module_name),
  1011
+                                    current_app=self.admin_site.name))
994 1012
 
995 1013
         ModelForm = self.get_form(request, obj)
996 1014
         formsets = []
@@ -1246,8 +1264,11 @@ def delete_view(self, request, object_id, extra_context=None):
1246 1264
             self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})
1247 1265
 
1248 1266
             if not self.has_change_permission(request, None):
1249  
-                return HttpResponseRedirect("../../../../")
1250  
-            return HttpResponseRedirect("../../")
  1267
+                return HttpResponseRedirect(reverse('admin:index',
  1268
+                                                    current_app=self.admin_site.name))
  1269
+            return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
  1270
+                                        (opts.app_label, opts.module_name),
  1271
+                                        current_app=self.admin_site.name))
1251 1272
 
1252 1273
         object_name = force_unicode(opts.verbose_name)
1253 1274
 
@@ -1292,6 +1313,7 @@ def history_view(self, request, object_id, extra_context=None):
1292 1313
             'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
1293 1314
             'object': obj,
1294 1315
             'app_label': app_label,
  1316
+            'opts': opts,
1295 1317
         }
1296 1318
         context.update(extra_context or {})
1297 1319
         return TemplateResponse(request, self.object_history_template or [
10  django/contrib/admin/sites.py
@@ -339,9 +339,11 @@ def index(self, request, extra_context=None):
339 339
                 # Check whether user has any perm for this module.
340 340
                 # If so, add the module to the model_list.
341 341
                 if True in perms.values():
  342
+                    info = (app_label, model._meta.module_name)
342 343
                     model_dict = {
343 344
                         'name': capfirst(model._meta.verbose_name_plural),
344  
-                        'admin_url': mark_safe('%s/%s/' % (app_label, model.__name__.lower())),
  345
+                        'admin_url': reverse('admin:%s_%s_changelist' % info, current_app=self.name),
  346
+                        'add_url': reverse('admin:%s_%s_add' % info, current_app=self.name),
345 347
                         'perms': perms,
346 348
                     }
347 349
                     if app_label in app_dict:
@@ -349,7 +351,7 @@ def index(self, request, extra_context=None):
349 351
                     else:
350 352
                         app_dict[app_label] = {
351 353
                             'name': app_label.title(),
352  
-                            'app_url': app_label + '/',
  354
+                            'app_url': reverse('admin:app_list', kwargs={'app_label': app_label}, current_app=self.name),
353 355
                             'has_module_perms': has_module_perms,
354 356
                             'models': [model_dict],
355 357
                         }
@@ -383,9 +385,11 @@ def app_index(self, request, app_label, extra_context=None):
383 385
                     # Check whether user has any perm for this module.
384 386
                     # If so, add the module to the model_list.
385 387
                     if True in perms.values():
  388
+                        info = (app_label, model._meta.module_name)
386 389
                         model_dict = {
387 390
                             'name': capfirst(model._meta.verbose_name_plural),
388  
-                            'admin_url': '%s/' % model.__name__.lower(),
  391
+                            'admin_url': reverse('admin:%s_%s_changelist' % info, current_app=self.name),
  392
+                            'add_url': reverse('admin:%s_%s_add' % info, current_app=self.name),
389 393
                             'perms': perms,
390 394
                         }
391 395
                         if app_dict:
8  django/contrib/admin/templates/admin/500.html
... ...
@@ -1,7 +1,13 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
3 4
 
4  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans "Home" %}</a> &rsaquo; {% trans "Server error" %}</div>{% endblock %}
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; {% trans 'Server error' %}
  9
+</div>
  10
+{% endblock %}
5 11
 
6 12
 {% block title %}{% trans 'Server error (500)' %}{% endblock %}
7 13
 
20  django/contrib/admin/templates/admin/app_index.html
... ...
@@ -1,15 +1,17 @@
1  
-{% extends "admin/index.html" %} 
2  
-{% load i18n %} 
  1
+{% extends "admin/index.html" %}
  2
+{% load i18n %}
  3
+{% load url from future %}
3 4
 
4 5
 {% if not is_popup %}
5  
-
6 6
 {% block breadcrumbs %}
7  
-<div class="breadcrumbs"><a href="../">
8  
-{% trans "Home" %}</a> &rsaquo; 
  7
+<div class="breadcrumbs">
  8
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  9
+&rsaquo;
9 10
 {% for app in app_list %}
10 11
 {% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}
11  
-{% endfor %}</div>{% endblock %}
12  
-
13  
-{% endif %} 
  12
+{% endfor %}
  13
+</div>
  14
+{% endblock %}
  15
+{% endif %}
14 16
 
15  
-{% block sidebar %}{% endblock %}
  17
+{% block sidebar %}{% endblock %}
19  django/contrib/admin/templates/admin/auth/user/change_password.html
... ...
@@ -1,21 +1,24 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n admin_static admin_modify %}
3 3
 {% load url from future %}
  4
+{% load admin_urls %}
  5
+
4 6
 {% block extrahead %}{{ block.super }}
5 7
 {% url 'admin:jsi18n' as jsi18nurl %}
6 8
 <script type="text/javascript" src="{{ jsi18nurl|default:"../../../../jsi18n/" }}"></script>
7 9
 {% endblock %}
8 10
 {% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %}
9 11
 {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
10  
-{% block breadcrumbs %}{% if not is_popup %}
11  
-<div class="breadcrumbs">
12  
-     <a href="../../../../">{% trans "Home" %}</a> &rsaquo;
13  
-     <a href="../../../">{{ opts.app_label|capfirst|escape }}</a> &rsaquo;
14  
-     <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
15  
-     <a href="../">{{ original|truncatewords:"18" }}</a> &rsaquo;
16  
-     {% trans 'Change password' %}
  12
+{% if not is_popup %}
  13
+{% block breadcrumbs %}
  14
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  15
+&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_label|capfirst|escape }}</a>
  16
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
  17
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}{{ original.pk }}">{{ original|truncatewords:"18" }}</a>
  18
+&rsaquo; {% trans 'Change password' %}
17 19
 </div>
18  
-{% endif %}{% endblock %}
  20
+{% endblock %}
  21
+{% endif %}
19 22
 {% block content %}<div id="content-main">
20 23
 <form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
21 24
 <div>
13  django/contrib/admin/templates/admin/base.html
@@ -32,17 +32,20 @@
32 32
                 {% if docsroot %}
33 33
                     <a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
34 34
                 {% endif %}
35  
-                <a href="{% url 'admin:password_change' %}">
36  
-                {% trans 'Change password' %}</a> /
37  
-                <a href="{% url 'admin:logout' %}">
38  
-                {% trans 'Log out' %}</a>
  35
+                <a href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a> /
  36
+                <a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>
39 37
             {% endblock %}
40 38
         </div>
41 39
         {% endif %}
42 40
         {% block nav-global %}{% endblock %}
43 41
     </div>
44 42
     <!-- END Header -->
45  
-    {% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans 'Home' %}</a>{% if title %} &rsaquo; {{ title }}{% endif %}</div>{% endblock %}
  43
+    {% block breadcrumbs %}
  44
+    <div class="breadcrumbs">
  45
+    <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  46
+    {% if title %} &rsaquo; {{ title }}{% endif %}
  47
+    </div>
  48
+    {% endblock %}
46 49
     {% endif %}
47 50
 
48 51
     {% block messages %}
16  django/contrib/admin/templates/admin/change_form.html
... ...
@@ -1,6 +1,7 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n admin_static admin_modify %}
3 3
 {% load url from future %}
  4
+{% load admin_urls %}
4 5
 
5 6
 {% block extrahead %}{{ block.super }}
6 7
 {% url 'admin:jsi18n' as jsi18nurl %}
@@ -14,14 +15,15 @@
14 15
 
15 16
 {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
16 17
 
17  
-{% block breadcrumbs %}{% if not is_popup %}
18  
-<div class="breadcrumbs">
19  
-     <a href="../../../">{% trans "Home" %}</a> &rsaquo;
20  
-     <a href="../../">{{ app_label|capfirst|escape }}</a> &rsaquo;
21  
-     {% if has_change_permission %}<a href="../">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %} &rsaquo;
22  
-     {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
  18
+{% if not is_popup %}
  19
+{% block breadcrumbs %}
  20
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  21
+&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ app_label|capfirst|escape }}</a>
  22
+&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 %}
  23
+&rsaquo; {% if add %}{% trans 'Add' %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
23 24
 </div>
24  
-{% endif %}{% endblock %}
  25
+{% endblock %}
  26
+{% endif %}
25 27
 
26 28
 {% block content %}<div id="content-main">
27 29
 {% block object-tools %}
24  django/contrib/admin/templates/admin/change_list.html
... ...
@@ -1,6 +1,8 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n admin_static admin_list %}
3 3
 {% load url from future %}
  4
+{% load admin_urls %}
  5
+
4 6
 {% block extrastyle %}
5 7
   {{ block.super }}
6 8
   <link rel="stylesheet" type="text/css" href="{% static "admin/css/changelists.css" %}" />
@@ -36,19 +38,13 @@
36 38
 {% block bodyclass %}change-list{% endblock %}
37 39
 
38 40
 {% if not is_popup %}
39  
-  {% block breadcrumbs %}
40  
-    <div class="breadcrumbs">
41  
-      <a href="../../">
42  
-        {% trans "Home" %}
43  
-      </a>
44  
-       &rsaquo;
45  
-       <a href="../">
46  
-         {{ app_label|capfirst }}
47  
-      </a>
48  
-      &rsaquo;
49  
-      {{ cl.opts.verbose_name_plural|capfirst }}
50  
-    </div>
51  
-  {% endblock %}
  41
+{% block breadcrumbs %}
  42
+<div class="breadcrumbs">
  43
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  44
+&rsaquo; <a href="{% url 'admin:app_list' app_label=cl.opts.app_label %}">{{ app_label|capfirst|escape }}</a>
  45
+&rsaquo; {{ cl.opts.verbose_name_plural|capfirst }}
  46
+</div>
  47
+{% endblock %}
52 48
 {% endif %}
53 49
 
54 50
 {% block coltype %}flex{% endblock %}
@@ -60,7 +56,7 @@
60 56
         <ul class="object-tools">
61 57
           {% block object-tools-items %}
62 58
             <li>
63  
-              <a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">
  59
+              <a href="{% url cl.opts|admin_urlname:'add' %}{% if is_popup %}?_popup=1{% endif %}" class="addlink">
64 60
                 {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
65 61
               </a>
66 62
             </li>
12  django/contrib/admin/templates/admin/delete_confirmation.html
... ...
@@ -1,13 +1,15 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
  4
+{% load admin_urls %}
3 5
 
4 6
 {% block breadcrumbs %}
5 7
 <div class="breadcrumbs">
6  
-     <a href="../../../../">{% trans "Home" %}</a> &rsaquo;
7  
-     <a href="../../../">{{ app_label|capfirst }}</a> &rsaquo; 
8  
-     <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
9  
-     <a href="../">{{ object|truncatewords:"18" }}</a> &rsaquo;
10  
-     {% trans 'Delete' %}
  8
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  9
+&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ app_label|capfirst }}</a>
  10
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst|escape }}</a>
  11
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}{{ object.pk }}">{{ object|truncatewords:"18" }}</a>
  12
+&rsaquo; {% trans 'Delete' %}
11 13
 </div>
12 14
 {% endblock %}
13 15
 
10  django/contrib/admin/templates/admin/delete_selected_confirmation.html
... ...
@@ -1,12 +1,14 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n l10n %}
  3
+{% load url from future %}
  4
+{% load admin_urls %}
3 5
 
4 6
 {% block breadcrumbs %}
5 7
 <div class="breadcrumbs">
6  
-     <a href="../../">{% trans "Home" %}</a> &rsaquo;
7  
-     <a href="../">{{ app_label|capfirst }}</a> &rsaquo;
8  
-     <a href="./">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
9  
-     {% trans 'Delete multiple objects' %}
  8
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  9
+&rsaquo; <a href="{% url 'admin:app_list' app_label=app_label %}">{{ app_label|capfirst|escape }}</a>
  10
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
  11
+&rsaquo; {% trans 'Delete multiple objects' %}
10 12
 </div>
11 13
 {% endblock %}
12 14
 
2  django/contrib/admin/templates/admin/index.html
@@ -26,7 +26,7 @@
26 26
             {% endif %}
27 27
 
28 28
             {% if model.perms.add %}
29  
-                <td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td>
  29
+                <td><a href="{{ model.add_url }}" class="addlink">{% trans 'Add' %}</a></td>
30 30
             {% else %}
31 31
                 <td>&nbsp;</td>
32 32
             {% endif %}
8  django/contrib/admin/templates/admin/invalid_setup.html
... ...
@@ -1,7 +1,13 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
3 4
 
4  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {{ title }}</div>{% endblock %}
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; {{ title }}
  9
+</div>
  10
+{% endblock %}
5 11
 
6 12
 {% block content %}
7 13
 <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>
12  django/contrib/admin/templates/admin/object_history.html
... ...
@@ -1,13 +1,15 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
  4
+{% load admin_urls %}
3 5
 
4 6
 {% block breadcrumbs %}
5 7
 <div class="breadcrumbs">
6  
-    <a href="../../../../">{% trans 'Home' %}</a> &rsaquo; 
7  
-    <a href="../../../">{{ app_label|capfirst }}</a> &rsaquo; 
8  
-    <a href="../../">{{ module_name }}</a> &rsaquo; 
9  
-    <a href="../">{{ object|truncatewords:"18" }}</a> &rsaquo; 
10  
-    {% trans 'History' %}
  8
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  9
+&rsaquo; <a href="{% url 'admin:app_list' app_label=app_label %}">{{ app_label|capfirst|escape }}</a>
  10
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ module_name }}</a>
  11
+&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}{{ object.pk }}">{{ object|truncatewords:"18" }}</a>
  12
+&rsaquo; {% trans 'History' %}
11 13
 </div>
12 14
 {% endblock %}
13 15
 
5  django/contrib/admin/templates/registration/logged_out.html
... ...
@@ -1,12 +1,13 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
3 4
 
4  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a></div>{% endblock %}
  5
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="{% url 'admin:index' %}">{% trans 'Home' %}</a></div>{% endblock %}
5 6
 
6 7
 {% block content %}
7 8
 
8 9
 <p>{% trans "Thanks for spending some quality time with the Web site today." %}</p>
9 10
 
10  
-<p><a href="../">{% trans 'Log in again' %}</a></p>
  11
+<p><a href="{% url 'admin:index' %}">{% trans 'Log in again' %}</a></p>
11 12
 
12 13
 {% endblock %}
9  django/contrib/admin/templates/registration/password_change_done.html
... ...
@@ -1,8 +1,13 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
3 3
 {% load url from future %}
4  
-{% 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 %}
5  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
  4
+{% 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 %}
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; {% trans 'Password change' %}
  9
+</div>
  10
+{% endblock %}
6 11
 
7 12
 {% block title %}{% trans 'Password change successful' %}{% endblock %}
8 13
 
9  django/contrib/admin/templates/registration/password_change_form.html
@@ -2,8 +2,13 @@
2 2
 {% load i18n static %}
3 3
 {% load url from future %}
4 4
 {% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %}
5  
-{% 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 %}
6  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
  5
+{% 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 %}
  6
+{% block breadcrumbs %}
  7
+<div class="breadcrumbs">
  8
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  9
+&rsaquo; {% trans 'Password change' %}
  10
+</div>
  11
+{% endblock %}
7 12
 
8 13
 {% block title %}{% trans 'Password change' %}{% endblock %}
9 14
 
8  django/contrib/admin/templates/registration/password_reset_complete.html
... ...
@@ -1,7 +1,13 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
3 4
 
4  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; {% trans 'Password reset' %}
  9
+</div>
  10
+{% endblock %}
5 11
 
6 12
 {% block title %}{% trans 'Password reset complete' %}{% endblock %}
7 13
 
8  django/contrib/admin/templates/registration/password_reset_confirm.html
... ...
@@ -1,7 +1,13 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
3 4
 
4  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset confirmation' %}</div>{% endblock %}
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; {% trans 'Password reset confirmation' %}
  9
+</div>
  10
+{% endblock %}
5 11
 
6 12
 {% block title %}{% trans 'Password reset' %}{% endblock %}
7 13
 
8  django/contrib/admin/templates/registration/password_reset_done.html
... ...
@@ -1,7 +1,13 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
3 4
 
4  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; {% trans 'Password reset' %}
  9
+</div>
  10
+{% endblock %}
5 11
 
6 12
 {% block title %}{% trans 'Password reset successful' %}{% endblock %}
7 13
 
8  django/contrib/admin/templates/registration/password_reset_form.html
... ...
@@ -1,7 +1,13 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
3 4
 
4  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; {% trans 'Password reset' %}
  9
+</div>
  10
+{% endblock %}
5 11
 
6 12
 {% block title %}{% trans "Password reset" %}{% endblock %}
7 13
 
8  django/contrib/admin/templatetags/admin_urls.py
... ...
@@ -0,0 +1,8 @@
  1
+from django.core.urlresolvers import reverse, NoReverseMatch
  2
+from django import template
  3
+
  4
+register = template.Library()
  5
+
  6
+@register.filter
  7
+def admin_urlname(value, arg):
  8
+    return 'admin:%s_%s_%s' % (value.app_label, value.module_name, arg)
12  django/contrib/admindocs/templates/admin_doc/bookmarklets.html
... ...
@@ -1,6 +1,14 @@
1 1
 {% extends "admin/base_site.html" %}
2  
-
3  
-{% block breadcrumbs %}{% load i18n %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> &rsaquo; <a href="../">{% trans "Documentation" %}</a> &rsaquo; {% trans "Bookmarklets" %}</div>{% endblock %}
  2
+{% load i18n %}
  3
+{% load url from future %}
  4
+
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
  9
+&rsaquo; {% trans 'Bookmarklets' %}
  10
+</div>
  11
+{% endblock %}
4 12
 {% block title %}{% trans "Documentation bookmarklets" %}{% endblock %}
5 13
 
6 14
 {% block content %}
9  django/contrib/admindocs/templates/admin_doc/index.html
... ...
@@ -1,6 +1,13 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
3  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="{{ root_path }}">Home</a> &rsaquo; Documentation</div>{% endblock %}
  3
+{% load url from future %}
  4
+
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; {% trans 'Documentation' %}</a>
  9
+</div>
  10
+{% endblock %}
4 11
 {% block title %}Documentation{% endblock %}
5 12
 
6 13
 {% block content %}
8  django/contrib/admindocs/templates/admin_doc/missing_docutils.html
... ...
@@ -1,6 +1,12 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
3  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %}
  3
+{% load url from future %}
  4
+
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; {% trans 'Documentation' %}</a>
  9
+</div>
4 10
 {% block title %}Please install docutils{% endblock %}
5 11
 
6 12
 {% block content %}
13  django/contrib/admindocs/templates/admin_doc/model_detail.html
... ...
@@ -1,5 +1,7 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
  4
+
3 5
 {% block extrahead %}
4 6
 {{ block.super }}
5 7
 <style type="text/css">
@@ -8,7 +10,14 @@
8 10
 </style>
9 11
 {% endblock %}
10 12
 
11  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Models</a> &rsaquo; {{ name }}</div>{% endblock %}
  13
+{% block breadcrumbs %}
  14
+<div class="breadcrumbs">
  15
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  16
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
  17
+&rsaquo; <a href="{% url 'django-admindocs-models-index' %}">{% trans 'Models' %}</a>
  18
+&rsaquo; {{ name }}
  19
+</div>
  20
+{% endblock %}
12 21
 
13 22
 {% block title %}Model: {{ name }}{% endblock %}
14 23
 
@@ -41,6 +50,6 @@
41 50
 </table>
42 51
 </div>
43 52
 
44  
-<p class="small"><a href="../">&lsaquo; Back to Models Documentation</a></p>
  53
+<p class="small"><a href="{% url 'django-admindocs-models-index' %}">&lsaquo; Back to Models Documentation</a></p>
45 54
 </div>
46 55
 {% endblock %}
13  django/contrib/admindocs/templates/admin_doc/model_index.html
... ...
@@ -1,7 +1,16 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
  4
+
3 5
 {% block coltype %}colSM{% endblock %}
4  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Models</div>{% endblock %}
  6
+
  7
+{% block breadcrumbs %}
  8
+<div class="breadcrumbs">
  9
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  10
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
  11
+&rsaquo; {% trans 'Models' %}
  12
+</div>
  13
+{% endblock %}
5 14
 
6 15
 {% block title %}Models{% endblock %}
7 16
 
@@ -19,7 +28,7 @@ <h2 id="app-{{ group.grouper }}">{{ group.grouper|capfirst }}</h2>
19 28
 <table class="xfull">
20 29
 {% for model in group.list %}
21 30
 <tr>
22  
-<th><a href="{{ model.app_label }}.{{ model.object_name.lower }}/">{{ model.object_name }}</a></th>
  31
+<th><a href="{% url 'django-admindocs-models-detail' app_label=model.app_label model_name=model.object_name.lower %}">{{ model.object_name }}</a></th>
23 32
 </tr>
24 33
 {% endfor %}
25 34
 </table>
13  django/contrib/admindocs/templates/admin_doc/template_detail.html
... ...
@@ -1,6 +1,15 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
3  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; Templates &rsaquo; {{ name }}</div>{% endblock %}
  3
+{% load url from future %}
  4
+
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
  9
+&rsaquo; {% trans 'Templates' %}
  10
+&rsaquo; {{ name }}
  11
+</div>
  12
+{% endblock %}
4 13
 
5 14
 {% block title %}Template: {{ name }}{% endblock %}
6 15
 
@@ -17,5 +26,5 @@
17 26
     </ol>
18 27
 {% endfor %}
19 28
 
20  
-<p class="small"><a href="../../">&lsaquo; Back to Documentation</a></p>
  29
+<p class="small"><a href="{% url 'django-admindocs-docroot' %}">&lsaquo; Back to Documentation</a></p>
21 30
 {% endblock %}
10  django/contrib/admindocs/templates/admin_doc/template_filter_index.html
... ...
@@ -1,7 +1,15 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
  4
+
3 5
 {% block coltype %}colSM{% endblock %}
4  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; filters</div>{% endblock %}
  6
+{% block breadcrumbs %}
  7
+<div class="breadcrumbs">
  8
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  9
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
  10
+&rsaquo; {% trans 'Filters' %}
  11
+</div>
  12
+{% endblock %}
5 13
 {% block title %}Template filters{% endblock %}
6 14
 
7 15
 {% block content %}
10  django/contrib/admindocs/templates/admin_doc/template_tag_index.html
... ...
@@ -1,7 +1,15 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
  4
+
3 5
 {% block coltype %}colSM{% endblock %}
4  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Tags</div>{% endblock %}
  6
+{% block breadcrumbs %}
  7
+<div class="breadcrumbs">
  8
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  9
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
  10
+&rsaquo; {% trans 'Tags' %}
  11
+</div>
  12
+{% endblock %}
5 13
 {% block title %}Template tags{% endblock %}
6 14
 
7 15
 {% block content %}
13  django/contrib/admindocs/templates/admin_doc/view_detail.html
... ...
@@ -1,6 +1,15 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
3  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Views</a> &rsaquo; {{ name }}</div>{% endblock %}
  3
+{% load url from future %}
  4
+
  5
+{% block breadcrumbs %}
  6
+<div class="breadcrumbs">
  7
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  8
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
  9
+&rsaquo; <a href="{% url 'django-admindocs-views-index' %}">{% trans 'Views' %}</a>
  10
+&rsaquo; {{ name }}
  11
+</div>
  12
+{% endblock %}
4 13
 {% block title %}View: {{ name }}{% endblock %}
5 14
 
6 15
 {% block content %}
@@ -21,5 +30,5 @@ <h2 class="subhead">{{ summary|striptags }}</h2>
21 30
 <p>{{ meta.Templates }}</p>
22 31
 {% endif %}
23 32
 
24  
-<p class="small"><a href="../">&lsaquo; Back to Views Documentation</a></p>
  33
+<p class="small"><a href="{% url 'django-admindocs-views-index' %}">&lsaquo; Back to Views Documentation</a></p>
25 34
 {% endblock %}
14  django/contrib/admindocs/templates/admin_doc/view_index.html
... ...
@@ -1,7 +1,15 @@
1 1
 {% extends "admin/base_site.html" %}
2 2
 {% load i18n %}
  3
+{% load url from future %}
  4
+
3 5
 {% block coltype %}colSM{% endblock %}
4  
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">Home</a> &rsaquo; <a href="../">Documentation</a> &rsaquo; Views</div>{% endblock %}
  6
+{% block breadcrumbs %}
  7
+<div class="breadcrumbs">
  8
+<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  9
+&rsaquo; <a href="{% url 'django-admindocs-docroot' %}">{% trans 'Documentation' %}</a>
  10
+&rsaquo; {% trans 'Views' %}
  11
+</div>
  12
+{% endblock %}
5 13
 {% block title %}Views{% endblock %}
6 14
 
7 15
 {% block content %}
@@ -29,8 +37,8 @@ <h2 id="site{{ site_views.grouper.id }}">Views by URL on {{ site_views.grouper.n
29 37
 
30 38
 {% for view in site_views.list|dictsort:"url" %}
31 39
 {% ifchanged %}
32  
-<h3><a href="{{ view.module }}.{{ view.name }}/">{{ view.url }}</a></h3>
33  
-<p class="small quiet">View function: {{ view.module }}.{{ view.name }}</p>
  40
+<h3><a href="{% url 'django-admindocs-views-detail' view=view.full_name %}">{{ view.url }}</a></h3>
  41
+<p class="small quiet">View function: {{ view.full_name }}</p>
34 42
 <p>{{ view.title }}</p>
35 43
 <hr />
36 44
 {% endifchanged %}
3  django/contrib/admindocs/views.py
@@ -136,8 +136,7 @@ def view_index(request):
136 136
             site_obj = GenericSite()
137 137
         for (func, regex) in view_functions:
138 138
             views.append({
139  
-                'name': getattr(func, '__name__', func.__class__.__name__),
140  
-                'module': func.__module__,
  139
+                'full_name': '%s.%s' % (func.__module__, getattr(func, '__name__', func.__class__.__name__)),
141 140
                 'site_id': settings_mod.SITE_ID,
142 141
                 'site': site_obj,
143 142
                 'url': simplify_regex(regex),
15  docs/ref/contrib/admin/index.txt
@@ -1960,3 +1960,18 @@ if you specifically wanted the admin view from the admin instance named
1960 1960
 
1961 1961
 For more details, see the documentation on :ref:`reversing namespaced URLs
1962 1962
 <topics-http-reversing-url-namespaces>`.
  1963
+
  1964
+To allow easier reversing of the admin urls in templates, Django provides an 
  1965
+``admin_url`` filter which takes an action as argument:
  1966
+
  1967
+.. code-block:: html+django
  1968
+
  1969
+    {% load admin_urls %}
  1970
+    {% load url from future %}
  1971
+    <a href="{% url opts|admin_urlname:'add' %}">Add user</a>
  1972
+    <a href="{% url opts|admin_urlname:'delete' user.pk %}">Delete this user</a>
  1973
+
  1974
+The action in the examples above match the last part of the URL names for
  1975
+:class:`ModelAdmin` instances described above. The ``opts`` variable can be any
  1976
+object which has an ``app_label`` and ``module_name`` and is usually supplied 
  1977
+by the admin views for the current model.
1  tests/regressiontests/admin_custom_urls/__init__.py
... ...
@@ -0,0 +1 @@
  1
+#
44  tests/regressiontests/admin_custom_urls/fixtures/actions.json
... ...
@@ -0,0 +1,44 @@
  1
+[
  2
+  {
  3
+    "pk": "delete", 
  4
+    "model": "admin_custom_urls.action", 
  5
+    "fields": {
  6
+      "description": "Remove things."
  7
+    }
  8
+  }, 
  9
+  {
  10
+    "pk": "rename", 
  11
+    "model": "admin_custom_urls.action", 
  12
+    "fields": {
  13
+      "description": "Gives things other names."
  14
+    }
  15
+  }, 
  16
+  {
  17
+    "pk": "add", 
  18
+    "model": "admin_custom_urls.action", 
  19
+    "fields": {
  20
+      "description": "Add things."
  21
+    }
  22
+  }, 
  23
+  {
  24
+    "pk": "path/to/file/", 
  25
+    "model": "admin_custom_urls.action", 
  26
+    "fields": {
  27
+      "description": "An action with '/' in its name."
  28
+    }
  29
+  }, 
  30
+  {
  31
+    "pk": "path/to/html/document.html", 
  32
+    "model": "admin_custom_urls.action", 
  33
+    "fields": {
  34
+      "description": "An action with a name similar to a HTML doc path."
  35
+    }
  36
+  }, 
  37
+  {
  38
+    "pk": "javascript:alert('Hello world');\">Click here</a>", 
  39
+    "model": "admin_custom_urls.action", 
  40
+    "fields": {
  41
+      "description": "An action with a name suspected of being a XSS attempt"
  42
+    }
  43
+  }
  44
+]
20  tests/regressiontests/admin_custom_urls/fixtures/users.json
... ...
@@ -0,0 +1,20 @@
  1
+[
  2
+  {
  3
+    "pk": 100,
  4
+    "model": "auth.user",
  5
+    "fields": {
  6
+      "username": "super",
  7
+      "first_name": "Super",
  8
+      "last_name": "User",
  9
+      "is_active": true,
  10
+      "is_superuser": true,
  11
+      "is_staff": true,
  12
+      "last_login": "2007-05-30 13:20:10",
  13
+      "groups": [],
  14
+      "user_permissions": [],
  15
+      "password": "sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158",
  16
+      "email": "super@example.com",
  17
+      "date_joined": "2007-05-30 13:20:10"
  18
+    }
  19
+  }
  20
+]
50  tests/regressiontests/admin_custom_urls/models.py
... ...
@@ -0,0 +1,50 @@
  1
+from functools import update_wrapper
  2
+
  3
+from django.contrib import admin
  4
+from django.db import models
  5
+
  6
+
  7
+class Action(models.Model):
  8
+    name = models.CharField(max_length=50, primary_key=True)
  9
+    description = models.CharField(max_length=70)
  10
+
  11
+    def __unicode__(self):
  12
+        return self.name
  13
+
  14
+
  15
+class ActionAdmin(admin.ModelAdmin):
  16
+    """
  17
+    A ModelAdmin for the Action model that changes the URL of the add_view
  18
+    to '<app name>/<model name>/!add/'
  19
+    The Action model has a CharField PK.
  20
+    """
  21
+
  22
+    list_display = ('name', 'description')
  23
+
  24
+    def remove_url(self, name):
  25
+        """
  26
+        Remove all entries named 'name' from the ModelAdmin instance URL
  27
+        patterns list
  28
+        """
  29
+        return filter(lambda e: e.name != name, super(ActionAdmin, self).get_urls())
  30
+
  31
+    def get_urls(self):
  32
+        # Add the URL of our custom 'add_view' view to the front of the URLs
  33
+        # list.  Remove the existing one(s) first
  34
+        from django.conf.urls.defaults import patterns, url
  35
+
  36
+        def wrap(view):
  37
+            def wrapper(*args, **kwargs):
  38
+                return self.admin_site.admin_view(view)(*args, **kwargs)
  39
+            return update_wrapper(wrapper, view)
  40
+
  41
+        info = self.model._meta.app_label, self.model._meta.module_name
  42
+
  43
+        view_name = '%s_%s_add' % info
  44
+
  45
+        return patterns('',
  46
+            url(r'^!add/$', wrap(self.add_view), name=view_name),
  47
+        ) + self.remove_url(view_name)
  48
+
  49
+
  50
+admin.site.register(Action, ActionAdmin)
72  tests/regressiontests/admin_custom_urls/tests.py
... ...
@@ -0,0 +1,72 @@
  1
+from django.core.urlresolvers import reverse
  2
+from django.template.response import TemplateResponse
  3
+from django.test import TestCase
  4
+
  5
+from models import Action
  6
+
  7
+
  8
+class AdminCustomUrlsTest(TestCase):
  9
+    fixtures = ['users.json', 'actions.json']
  10
+
  11
+    def setUp(self):
  12
+        self.client.login(username='super', password='secret')
  13
+
  14
+    def tearDown(self):
  15
+        self.client.logout()
  16
+
  17
+    def testBasicAddGet(self):
  18
+        """
  19
+        A smoke test to ensure GET on the add_view works.
  20
+        """
  21
+        response = self.client.get('/custom_urls/admin/admin_custom_urls/action/!add/')
  22
+        self.assertIsInstance(response, TemplateResponse)
  23
+        self.assertEqual(response.status_code, 200)
  24
+
  25
+    def testAddWithGETArgs(self):
  26
+        response = self.client.get('/custom_urls/admin/admin_custom_urls/action/!add/', {'name': 'My Action'})
  27
+        self.assertEqual(response.status_code, 200)
  28
+        self.assertTrue(
  29
+            'value="My Action"' in response.content,
  30
+            "Couldn't find an input with the right value in the response."
  31
+        )
  32
+
  33
+    def testBasicAddPost(self):
  34
+        """
  35
+        A smoke test to ensure POST on add_view works.
  36
+        """
  37
+        post_data = {
  38
+            '_popup': u'1',
  39
+            "name": u'Action added through a popup',
  40
+            "description": u"Description of added action",
  41
+        }
  42
+        response = self.client.post('/custom_urls/admin/admin_custom_urls/action/!add/', post_data)
  43
+        self.assertEqual(response.status_code, 200)
  44
+        self.assertContains(response, 'dismissAddAnotherPopup')
  45
+        self.assertContains(response, 'Action added through a popup')
  46
+
  47
+    def testAdminUrlsNoClash(self):
  48
+        """
  49
+        Test that some admin URLs work correctly. The model has a CharField
  50
+        PK and the add_view URL has been customized.
  51
+        """
  52
+        # Should get the change_view for model instance with PK 'add', not show
  53
+        # the add_view
  54
+        response = self.client.get('/custom_urls/admin/admin_custom_urls/action/add/')
  55
+        self.assertEqual(response.status_code, 200)
  56
+        self.assertContains(response, 'Change action')
  57
+
  58
+        # Ditto, but use reverse() to build the URL
  59
+        path = reverse('admin:%s_action_change' % Action._meta.app_label,
  60
+                args=('add',))
  61
+        response = self.client.get(path)
  62
+        self.assertEqual(response.status_code, 200)
  63
+        self.assertContains(response, 'Change action')
  64
+
  65
+        # Should correctly get the change_view for the model instance with the
  66
+        # funny-looking PK
  67
+        path = reverse('admin:%s_action_change' % Action._meta.app_label,
  68
+                args=("path/to/html/document.html",))
  69
+        response = self.client.get(path)
  70
+        self.assertEqual(response.status_code, 200)
  71
+        self.assertContains(response, 'Change action')
  72
+        self.assertContains(response, 'value="path/to/html/document.html"')
7  tests/regressiontests/admin_custom_urls/urls.py
... ...
@@ -0,0 +1,7 @@
  1
+from django.conf.urls.defaults import *
  2
+from django.contrib import admin
  3
+
  4
+urlpatterns = patterns('',
  5
+    (r'^admin/', include(admin.site.urls)),
  6
+)
  7
+
4  tests/regressiontests/admin_views/tests.py
@@ -595,7 +595,7 @@ def test_save_as_display(self):
595 595
         self.assertTrue(response.context['save_as'])
596 596
         post_data = {'_saveasnew':'', 'name':'John M', 'gender':3, 'alive':'checked'}
597 597
         response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)