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

Also fixes #18310.
This commit is contained in:
Ramiro Morales 2012-10-18 20:58:52 -03:00
parent db598dd8a0
commit 0b908b92a2
4 changed files with 190 additions and 48 deletions

View File

@ -1,4 +1,6 @@
from functools import update_wrapper, partial from functools import update_wrapper, partial
import warnings
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.forms.formsets import all_valid from django.forms.formsets import all_valid
@ -6,7 +8,7 @@ from django.forms.models import (modelform_factory, modelformset_factory,
inlineformset_factory, BaseInlineFormSet) inlineformset_factory, BaseInlineFormSet)
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets, helpers 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.admin.templatetags.admin_static import static
from django.contrib import messages from django.contrib import messages
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
@ -763,21 +765,49 @@ class ModelAdmin(BaseModelAdmin):
"admin/change_form.html" "admin/change_form.html"
], context, current_app=self.admin_site.name) ], 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. Determines the HttpResponse for the add_view stage.
"""
opts = obj._meta
pk_value = obj._get_pk_val()
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_text(opts.verbose_name), 'obj': force_text(obj)} :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.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)}
# Here, we distinguish between different save types by checking for # Here, we distinguish between different save types by checking for
# the presence of keys in request.POST. # the presence of keys in request.POST.
if "_continue" 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: if "_popup" in request.POST:
post_url_continue += "?_popup=1" url += "?_popup=1"
return HttpResponseRedirect(post_url_continue % pk_value) return HttpResponseRedirect(url)
if "_popup" in request.POST: if "_popup" in request.POST:
return HttpResponse( return HttpResponse(
@ -786,72 +816,104 @@ class ModelAdmin(BaseModelAdmin):
# escape() calls force_text. # escape() calls force_text.
(escape(pk_value), escapejs(obj))) (escape(pk_value), escapejs(obj)))
elif "_addanother" in request.POST: elif "_addanother" in request.POST:
self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(opts.verbose_name))) msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict
return HttpResponseRedirect(request.path) 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: else:
msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict
self.message_user(request, msg) self.message_user(request, msg)
# Figure out where to redirect. If the user has change permission, # Figure out where to redirect. If the user has change permission,
# redirect to the change-list page for this object. Otherwise, # redirect to the change-list page for this object. Otherwise,
# redirect to the admin index. # redirect to the admin index.
if self.has_change_permission(request, None): if self.has_change_permission(request, None):
post_url = reverse('admin:%s_%s_changelist' % if hasperm_url is None:
(opts.app_label, opts.module_name), hasperm_url = 'admin:%s_%s_changelist' % (app_label, model_name)
current_app=self.admin_site.name) url = reverse(hasperm_url, current_app=site_name)
else: else:
post_url = reverse('admin:index', if noperm_url is None:
current_app=self.admin_site.name) noperm_url = 'admin:index'
return HttpResponseRedirect(post_url) 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. 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 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(). # Handle proxy models automatically created by .only() or .defer().
# Refs #14529 # Refs #14529
verbose_name = opts.verbose_name
module_name = opts.module_name
if obj._deferred: if obj._deferred:
opts_ = opts.proxy_for_model._meta opts_ = opts.proxy_for_model._meta
verbose_name = opts_.verbose_name 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: if "_continue" in request.POST:
self.message_user(request, msg + ' ' + _("You may edit it again below.")) msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict
if "_popup" in request.REQUEST:
return HttpResponseRedirect(request.path + "?_popup=1")
else:
return HttpResponseRedirect(request.path)
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}
self.message_user(request, msg) self.message_user(request, msg)
return HttpResponseRedirect(reverse('admin:%s_%s_change' % if continue_editing_url is None:
(opts.app_label, module_name), continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name)
args=(pk_value,), url = reverse(continue_editing_url, args=(quote(obj.pk),),
current_app=self.admin_site.name)) 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.') % msg_dict
self.message_user(request, msg)
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: elif "_addanother" in request.POST:
self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(verbose_name))) msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict
return HttpResponseRedirect(reverse('admin:%s_%s_add' % self.message_user(request, msg)
(opts.app_label, module_name), if add_another_url is None:
current_app=self.admin_site.name)) add_another_url = 'admin:%s_%s_add' % (app_label, model_name)
url = reverse(add_another_url, current_app=site_name)
return HttpResponseRedirect(url)
else: else:
msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict
self.message_user(request, msg) self.message_user(request, msg)
# Figure out where to redirect. If the user has change permission, # Figure out where to redirect. If the user has change permission,
# redirect to the change-list page for this object. Otherwise, # redirect to the change-list page for this object. Otherwise,
# redirect to the admin index. # redirect to the admin index.
if self.has_change_permission(request, None): if self.has_change_permission(request, None):
post_url = reverse('admin:%s_%s_changelist' % if hasperm_url is None:
(opts.app_label, module_name), hasperm_url = 'admin:%s_%s_changelist' % (app_label,
current_app=self.admin_site.name) model_name)
url = reverse(hasperm_url, current_app=site_name)
else: else:
post_url = reverse('admin:index', if noperm_url is None:
current_app=self.admin_site.name) noperm_url = 'admin:index'
return HttpResponseRedirect(post_url) url = reverse(noperm_url, current_app=site_name)
return HttpResponseRedirect(url)
def response_action(self, request, queryset): def response_action(self, request, queryset):
""" """

View File

@ -153,7 +153,7 @@ class UserAdmin(admin.ModelAdmin):
'admin/auth/user/change_password.html' 'admin/auth/user/change_password.html'
], context, current_app=self.admin_site.name) ], 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 Determines the HttpResponse for the add_view stage. It mostly defers to
its superclass implementation but is customized because the User model its superclass implementation but is customized because the User model
@ -166,8 +166,7 @@ class UserAdmin(admin.ModelAdmin):
# * We are adding a user in a popup # * We are adding a user in a popup
if '_addanother' not in request.POST and '_popup' not in request.POST: if '_addanother' not in request.POST and '_popup' not in request.POST:
request.POST['_continue'] = 1 request.POST['_continue'] = 1
return super(UserAdmin, self).response_add(request, obj, return super(UserAdmin, self).response_add(request, obj, **kwargs)
post_url_continue)
admin.site.register(Group, GroupAdmin) admin.site.register(Group, GroupAdmin)
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)

View File

@ -50,3 +50,40 @@ class ActionAdmin(admin.ModelAdmin):
admin.site.register(Action, ActionAdmin) 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 File

@ -1,12 +1,14 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
import warnings
from django.contrib.admin.util import quote from django.contrib.admin.util import quote
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings 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',)) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@ -81,3 +83,45 @@ class AdminCustomUrlsTest(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Change action') self.assertContains(response, 'Change action')
self.assertContains(response, 'value="path/to/html/document.html"') 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