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 %}{% endblock %} +{% block breadcrumbs %} + +{% 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 %} - +{% endblock %} +{% endif %} -{% 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 %} {% endblock %} {% block extrastyle %}{{ block.super }}{% endblock %} {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} -{% block breadcrumbs %}{% if not is_popup %} - -{% endif %}{% endblock %} +{% endblock %} +{% endif %} {% block content %}
{% csrf_token %}{% block form_top %}{% endblock %}
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 %} {% trans 'Documentation' %} / {% endif %} - - {% trans 'Change password' %} / - - {% trans 'Log out' %} + {% trans 'Change password' %} / + {% trans 'Log out' %} {% endblock %}
{% endif %} {% block nav-global %}{% endblock %}
- {% block breadcrumbs %}{% endblock %} + {% block breadcrumbs %} + + {% 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 %} - -{% endif %}{% endblock %} +{% endblock %} +{% endif %} {% block content %}
{% 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 }} @@ -36,19 +38,13 @@ {% block bodyclass %}change-list{% endblock %} {% if not is_popup %} - {% block breadcrumbs %} - - {% endblock %} +{% block breadcrumbs %} + +{% endblock %} {% endif %} {% block coltype %}flex{% endblock %} @@ -60,7 +56,7 @@
-

‹ Back to Models Documentation

+

‹ Back to Models Documentation

{% 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 %}{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} {% block title %}Models{% endblock %} @@ -19,7 +28,7 @@ {% for model in group.list %} - + {% endfor %}
{{ model.object_name }}{{ model.object_name }}
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 %}{% endblock %} +{% load url from future %} + +{% block breadcrumbs %} + +{% endblock %} {% block title %}Template: {{ name }}{% endblock %} @@ -17,5 +26,5 @@ {% endfor %} -

‹ Back to Documentation

+

‹ Back to Documentation

{% 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 %}{% endblock %} +{% block breadcrumbs %} + +{% 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 %}{% endblock %} +{% block breadcrumbs %} + +{% 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 %}{% endblock %} +{% load url from future %} + +{% block breadcrumbs %} + +{% endblock %} {% block title %}View: {{ name }}{% endblock %} {% block content %} @@ -21,5 +30,5 @@

{{ meta.Templates }}

{% endif %} -

‹ Back to Views Documentation

+

‹ Back to Views Documentation

{% 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 %}{% endblock %} +{% block breadcrumbs %} + +{% endblock %} {% block title %}Views{% endblock %} {% block content %} @@ -29,8 +37,8 @@ {% for view in site_views.list|dictsort:"url" %} {% ifchanged %} -

{{ view.url }}

-

View function: {{ view.module }}.{{ view.name }}

+

{{ view.url }}

+

View function: {{ view.full_name }}

{{ view.title }}


{% 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 `. + +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 %} + Add user + Delete this user + +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", + "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 '//!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 = 'Articles ›' + change_list_link = '› Articles' 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')), + )