Commit 0b908b92 authored by Ramiro Morales's avatar Ramiro Morales
Browse files

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

Also fixes #18310.
parent db598dd8
Loading
Loading
Loading
Loading
+104 −42
Original line number Diff line number Diff line
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 @@ from django.forms.models import (modelform_factory, modelformset_factory,
    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 @@ class ModelAdmin(BaseModelAdmin):
            "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 @@ class ModelAdmin(BaseModelAdmin):
                # 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):
        """
+2 −3
Original line number Diff line number Diff line
@@ -153,7 +153,7 @@ class UserAdmin(admin.ModelAdmin):
            '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 @@ class UserAdmin(admin.ModelAdmin):
        # * 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)
+37 −0
Original line number Diff line number Diff line
@@ -50,3 +50,40 @@ class ActionAdmin(admin.ModelAdmin):


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)
+45 −1
Original line number Diff line number Diff line
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 @@ class AdminCustomUrlsTest(TestCase):
        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