From aaf77c1676e44019abe544911ff7a06eb2690295 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Tue, 20 Sep 2011 18:30:06 +0000 Subject: [PATCH] Converted internal link generation in the admin and admin document generator 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 --- django/contrib/admin/options.py | 42 ++++++++--- django/contrib/admin/sites.py | 10 ++- django/contrib/admin/templates/admin/500.html | 8 ++- .../admin/templates/admin/app_index.html | 20 +++--- .../admin/auth/user/change_password.html | 19 ++--- .../contrib/admin/templates/admin/base.html | 13 ++-- .../admin/templates/admin/change_form.html | 16 +++-- .../admin/templates/admin/change_list.html | 24 +++---- .../templates/admin/delete_confirmation.html | 12 ++-- .../admin/delete_selected_confirmation.html | 10 +-- .../contrib/admin/templates/admin/index.html | 2 +- .../admin/templates/admin/invalid_setup.html | 8 ++- .../admin/templates/admin/object_history.html | 12 ++-- .../templates/registration/logged_out.html | 5 +- .../registration/password_change_done.html | 9 ++- .../registration/password_change_form.html | 9 ++- .../registration/password_reset_complete.html | 8 ++- .../registration/password_reset_confirm.html | 8 ++- .../registration/password_reset_done.html | 8 ++- .../registration/password_reset_form.html | 8 ++- .../contrib/admin/templatetags/admin_urls.py | 8 +++ .../templates/admin_doc/bookmarklets.html | 10 ++- .../admindocs/templates/admin_doc/index.html | 9 ++- .../templates/admin_doc/missing_docutils.html | 8 ++- .../templates/admin_doc/model_detail.html | 13 +++- .../templates/admin_doc/model_index.html | 13 +++- .../templates/admin_doc/template_detail.html | 13 +++- .../admin_doc/template_filter_index.html | 10 ++- .../admin_doc/template_tag_index.html | 10 ++- .../templates/admin_doc/view_detail.html | 13 +++- .../templates/admin_doc/view_index.html | 14 +++- django/contrib/admindocs/views.py | 3 +- docs/ref/contrib/admin/index.txt | 15 ++++ .../admin_custom_urls/__init__.py | 1 + .../admin_custom_urls/fixtures/actions.json | 44 ++++++++++++ .../admin_custom_urls/fixtures/users.json | 20 ++++++ .../admin_custom_urls/models.py | 50 +++++++++++++ .../admin_custom_urls/tests.py | 72 +++++++++++++++++++ .../regressiontests/admin_custom_urls/urls.py | 7 ++ tests/regressiontests/admin_views/tests.py | 4 +- tests/urls.py | 4 +- 41 files changed, 488 insertions(+), 104 deletions(-) create mode 100644 django/contrib/admin/templatetags/admin_urls.py create mode 100644 tests/regressiontests/admin_custom_urls/__init__.py create mode 100644 tests/regressiontests/admin_custom_urls/fixtures/actions.json create mode 100644 tests/regressiontests/admin_custom_urls/fixtures/users.json create mode 100644 tests/regressiontests/admin_custom_urls/models.py create mode 100644 tests/regressiontests/admin_custom_urls/tests.py create mode 100644 tests/regressiontests/admin_custom_urls/urls.py diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 5271e021efc..2825c999888 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 9bb9f4ab464..3a5c12b70ab 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 b30e43170de..eeb9e8da79f 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 120433d708d..4616b16d3d7 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 b18b0aabf38..f79eacd459b 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 4b3c42934f9..3b50adcd6ef 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 56661e9a8e6..0e2803d671f 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 24c6d8cced8..98869cfa5c8 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 47c94c0c70f..4eb3cf69c93 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 c04dedc530b..307dd627df8 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 46ccf0fd0a6..1809bc91828 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 676c025acb0..18e5d952987 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 c6d080c9eff..41c812110f4 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 6b10fa65e30..e508c84e199 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 28319be9d53..9725865d81c 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 2da6774d44d..21145c56528 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 00000000000..792d6005489 --- /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 00000000000..d803393a12a --- /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 00000000000..72d86d70ad1 --- /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 00000000000..f8c83a9024a --- /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 00000000000..cfc6b8583e2 --- /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 00000000000..6c2761a2225 --- /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 668c71076e0..78457d97abc 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 044395039d9..e7c23e5144d 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')), + )