Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #8001 -- Made redirections after add/edit in admin customizable.

Also fixes #18310.
  • Loading branch information...
commit 0b908b92a2ca4fb74a103e96bb75c53c05d0a428 1 parent db598dd
@ramiro ramiro authored
View
146 django/contrib/admin/options.py
@@ -1,4 +1,6 @@
from functools import update_wrapper, partial
+import warnings
+
from django import forms
from django.conf import settings
from django.forms.formsets import all_valid
@@ -6,7 +8,7 @@
inlineformset_factory, BaseInlineFormSet)
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets, helpers
-from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
+from django.contrib.admin.util import quote, unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
from django.contrib.admin.templatetags.admin_static import static
from django.contrib import messages
from django.views.decorators.csrf import csrf_protect
@@ -763,21 +765,49 @@ def render_change_form(self, request, context, add=False, change=False, form_url
"admin/change_form.html"
], context, current_app=self.admin_site.name)
- def response_add(self, request, obj, post_url_continue='../%s/'):
+ def response_add(self, request, obj, post_url_continue='../%s/',
+ continue_editing_url=None, add_another_url=None,
+ hasperm_url=None, noperm_url=None):
"""
Determines the HttpResponse for the add_view stage.
- """
+
+ :param request: HttpRequest instance.
+ :param obj: Object just added.
+ :param post_url_continue: Deprecated/undocumented.
+ :param continue_editing_url: URL where user will be redirected after
+ pressing 'Save and continue editing'.
+ :param add_another_url: URL where user will be redirected after
+ pressing 'Save and add another'.
+ :param hasperm_url: URL to redirect after a successful object creation
+ when the user has change permissions.
+ :param noperm_url: URL to redirect after a successful object creation
+ when the user has no change permissions.
+ """
+ if post_url_continue != '../%s/':
+ warnings.warn("The undocumented 'post_url_continue' argument to "
+ "ModelAdmin.response_add() is deprecated, use the new "
+ "*_url arguments instead.", DeprecationWarning,
+ stacklevel=2)
opts = obj._meta
- pk_value = obj._get_pk_val()
+ pk_value = obj.pk
+ app_label = opts.app_label
+ model_name = opts.module_name
+ site_name = self.admin_site.name
+
+ msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
- msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
# Here, we distinguish between different save types by checking for
# the presence of keys in request.POST.
if "_continue" in request.POST:
- self.message_user(request, msg + ' ' + _("You may edit it again below."))
+ msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
+ self.message_user(request, msg)
+ if continue_editing_url is None:
+ continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name)
+ url = reverse(continue_editing_url, args=(quote(pk_value),),
+ current_app=site_name)
if "_popup" in request.POST:
- post_url_continue += "?_popup=1"
- return HttpResponseRedirect(post_url_continue % pk_value)
+ url += "?_popup=1"
+ return HttpResponseRedirect(url)
if "_popup" in request.POST:
return HttpResponse(
@@ -786,72 +816,104 @@ def response_add(self, request, obj, post_url_continue='../%s/'):
# escape() calls force_text.
(escape(pk_value), escapejs(obj)))
elif "_addanother" in request.POST:
- self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(opts.verbose_name)))
- return HttpResponseRedirect(request.path)
+ msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict
+ self.message_user(request, msg)
+ if add_another_url is None:
+ add_another_url = 'admin:%s_%s_add' % (app_label, model_name)
+ url = reverse(add_another_url, current_app=site_name)
+ return HttpResponseRedirect(url)
else:
+ msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict
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):
- post_url = reverse('admin:%s_%s_changelist' %
- (opts.app_label, opts.module_name),
- current_app=self.admin_site.name)
+ if hasperm_url is None:
+ hasperm_url = 'admin:%s_%s_changelist' % (app_label, model_name)
+ url = reverse(hasperm_url, current_app=site_name)
else:
- post_url = reverse('admin:index',
- current_app=self.admin_site.name)
- return HttpResponseRedirect(post_url)
+ if noperm_url is None:
+ noperm_url = 'admin:index'
+ url = reverse(noperm_url, current_app=site_name)
+ return HttpResponseRedirect(url)
- def response_change(self, request, obj):
+ def response_change(self, request, obj, continue_editing_url=None,
+ save_as_new_url=None, add_another_url=None,
+ hasperm_url=None, noperm_url=None):
"""
Determines the HttpResponse for the change_view stage.
+
+ :param request: HttpRequest instance.
+ :param obj: Object just modified.
+ :param continue_editing_url: URL where user will be redirected after
+ pressing 'Save and continue editing'.
+ :param save_as_new_url: URL where user will be redirected after pressing
+ 'Save as new' (when applicable).
+ :param add_another_url: URL where user will be redirected after pressing
+ 'Save and add another'.
+ :param hasperm_url: URL to redirect after a successful object edition when
+ the user has change permissions.
+ :param noperm_url: URL to redirect after a successful object edition when
+ the user has no change permissions.
"""
opts = obj._meta
+ app_label = opts.app_label
+ model_name = opts.module_name
+ site_name = self.admin_site.name
+ verbose_name = opts.verbose_name
# 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
+ model_name = opts_.module_name
- pk_value = obj._get_pk_val()
+ msg_dict = {'name': force_text(verbose_name), 'obj': force_text(obj)}
- msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_text(verbose_name), 'obj': force_text(obj)}
if "_continue" in request.POST:
- self.message_user(request, msg + ' ' + _("You may edit it again below."))
- if "_popup" in request.REQUEST:
- return HttpResponseRedirect(request.path + "?_popup=1")
- else:
- return HttpResponseRedirect(request.path)
+ msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict
+ self.message_user(request, msg)
+ if continue_editing_url is None:
+ continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name)
+ url = reverse(continue_editing_url, args=(quote(obj.pk),),
+ current_app=site_name)
+ if "_popup" in request.POST:
+ url += "?_popup=1"
+ return HttpResponseRedirect(url)
elif "_saveasnew" in request.POST:
- msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_text(verbose_name), 'obj': obj}
+ msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
self.message_user(request, msg)
- return HttpResponseRedirect(reverse('admin:%s_%s_change' %
- (opts.app_label, module_name),
- args=(pk_value,),
- current_app=self.admin_site.name))
+ if save_as_new_url is None:
+ save_as_new_url = 'admin:%s_%s_change' % (app_label, model_name)
+ url = reverse(save_as_new_url, args=(quote(obj.pk),),
+ current_app=site_name)
+ return HttpResponseRedirect(url)
elif "_addanother" in request.POST:
- self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(verbose_name)))
- return HttpResponseRedirect(reverse('admin:%s_%s_add' %
- (opts.app_label, module_name),
- current_app=self.admin_site.name))
+ msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict
+ self.message_user(request, msg)
+ if add_another_url is None:
+ add_another_url = 'admin:%s_%s_add' % (app_label, model_name)
+ url = reverse(add_another_url, current_app=site_name)
+ return HttpResponseRedirect(url)
else:
+ msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict
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):
- post_url = reverse('admin:%s_%s_changelist' %
- (opts.app_label, module_name),
- current_app=self.admin_site.name)
+ if hasperm_url is None:
+ hasperm_url = 'admin:%s_%s_changelist' % (app_label,
+ model_name)
+ url = reverse(hasperm_url, current_app=site_name)
else:
- post_url = reverse('admin:index',
- current_app=self.admin_site.name)
- return HttpResponseRedirect(post_url)
+ if noperm_url is None:
+ noperm_url = 'admin:index'
+ url = reverse(noperm_url, current_app=site_name)
+ return HttpResponseRedirect(url)
def response_action(self, request, queryset):
"""
View
5 django/contrib/auth/admin.py
@@ -153,7 +153,7 @@ def user_change_password(self, request, id, form_url=''):
'admin/auth/user/change_password.html'
], context, current_app=self.admin_site.name)
- def response_add(self, request, obj, post_url_continue='../%s/'):
+ def response_add(self, request, obj, **kwargs):
"""
Determines the HttpResponse for the add_view stage. It mostly defers to
its superclass implementation but is customized because the User model
@@ -166,8 +166,7 @@ def response_add(self, request, obj, post_url_continue='../%s/'):
# * We are adding a user in a popup
if '_addanother' not in request.POST and '_popup' not in request.POST:
request.POST['_continue'] = 1
- return super(UserAdmin, self).response_add(request, obj,
- post_url_continue)
+ return super(UserAdmin, self).response_add(request, obj, **kwargs)
admin.site.register(Group, GroupAdmin)
admin.site.register(User, UserAdmin)
View
37 tests/regressiontests/admin_custom_urls/models.py
@@ -50,3 +50,40 @@ def wrapper(*args, **kwargs):
admin.site.register(Action, ActionAdmin)
+
+
+class Person(models.Model):
+ nick = models.CharField(max_length=20)
+
+
+class PersonAdmin(admin.ModelAdmin):
+ """A custom ModelAdmin that customizes the deprecated post_url_continue
+ argument to response_add()"""
+ def response_add(self, request, obj, post_url_continue='../%s/continue/',
+ continue_url=None, add_url=None, hasperm_url=None,
+ noperm_url=None):
+ return super(PersonAdmin, self).response_add(request, obj,
+ post_url_continue,
+ continue_url, add_url,
+ hasperm_url, noperm_url)
+
+
+admin.site.register(Person, PersonAdmin)
+
+
+class City(models.Model):
+ name = models.CharField(max_length=20)
+
+
+class CityAdmin(admin.ModelAdmin):
+ """A custom ModelAdmin that redirects to the changelist when the user
+ presses the 'Save and add another' button when adding a model instance."""
+ def response_add(self, request, obj,
+ add_another_url='admin:admin_custom_urls_city_changelist',
+ **kwargs):
+ return super(CityAdmin, self).response_add(request, obj,
+ add_another_url=add_another_url,
+ **kwargs)
+
+
+admin.site.register(City, CityAdmin)
View
46 tests/regressiontests/admin_custom_urls/tests.py
@@ -1,12 +1,14 @@
from __future__ import absolute_import, unicode_literals
+import warnings
+
from django.contrib.admin.util import quote
from django.core.urlresolvers import reverse
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.utils import override_settings
-from .models import Action
+from .models import Action, Person, City
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@@ -81,3 +83,45 @@ def testAdminUrlsNoClash(self):
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Change action')
self.assertContains(response, 'value="path/to/html/document.html"')
+
+
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
+class CustomUrlsWorkflowTests(TestCase):
+ fixtures = ['users.json']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_old_argument_deprecation(self):
+ """Test reporting of post_url_continue deprecation."""
+ post_data = {
+ 'nick': 'johndoe',
+ }
+ cnt = Person.objects.count()
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ response = self.client.post(reverse('admin:admin_custom_urls_person_add'), post_data)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(Person.objects.count(), cnt + 1)
+ # We should get a DeprecationWarning
+ self.assertEqual(len(w), 1)
+ self.assertTrue(isinstance(w[0].message, DeprecationWarning))
+
+ def test_custom_add_another_redirect(self):
+ """Test customizability of post-object-creation redirect URL."""
+ post_data = {
+ 'name': 'Rome',
+ '_addanother': '1',
+ }
+ cnt = City.objects.count()
+ with warnings.catch_warnings(record=True) as w:
+ # POST to the view whose post-object-creation redir URL argument we
+ # are customizing (object creation)
+ response = self.client.post(reverse('admin:admin_custom_urls_city_add'), post_data)
+ self.assertEqual(City.objects.count(), cnt + 1)
+ # Check that it redirected to the URL we set
+ self.assertRedirects(response, reverse('admin:admin_custom_urls_city_changelist'))
+ self.assertEqual(len(w), 0) # We should get no DeprecationWarning

0 comments on commit 0b908b9

Please sign in to comment.
Something went wrong with that request. Please try again.