Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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

Also fixes #18310.
  • Loading branch information...
commit 0b908b92a2ca4fb74a103e96bb75c53c05d0a428 1 parent db598dd
Ramiro Morales authored October 18, 2012
146  django/contrib/admin/options.py
... ...
@@ -1,4 +1,6 @@
1 1
 from functools import update_wrapper, partial
  2
+import warnings
  3
+
2 4
 from django import forms
3 5
 from django.conf import settings
4 6
 from django.forms.formsets import all_valid
@@ -6,7 +8,7 @@
6 8
     inlineformset_factory, BaseInlineFormSet)
7 9
 from django.contrib.contenttypes.models import ContentType
8 10
 from django.contrib.admin import widgets, helpers
9  
-from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
  11
+from django.contrib.admin.util import quote, unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
10 12
 from django.contrib.admin.templatetags.admin_static import static
11 13
 from django.contrib import messages
12 14
 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
763 765
             "admin/change_form.html"
764 766
         ], context, current_app=self.admin_site.name)
765 767
 
766  
-    def response_add(self, request, obj, post_url_continue='../%s/'):
  768
+    def response_add(self, request, obj, post_url_continue='../%s/',
  769
+                     continue_editing_url=None, add_another_url=None,
  770
+                     hasperm_url=None, noperm_url=None):
767 771
         """
768 772
         Determines the HttpResponse for the add_view stage.
769  
-        """
  773
+
  774
+        :param request: HttpRequest instance.
  775
+        :param obj: Object just added.
  776
+        :param post_url_continue: Deprecated/undocumented.
  777
+        :param continue_editing_url: URL where user will be redirected after
  778
+                                     pressing 'Save and continue editing'.
  779
+        :param add_another_url: URL where user will be redirected after
  780
+                                pressing 'Save and add another'.
  781
+        :param hasperm_url: URL to redirect after a successful object creation
  782
+                            when the user has change permissions.
  783
+        :param noperm_url: URL to redirect after a successful object creation
  784
+                           when the user has no change permissions.
  785
+        """
  786
+        if post_url_continue != '../%s/':
  787
+            warnings.warn("The undocumented 'post_url_continue' argument to "
  788
+                          "ModelAdmin.response_add() is deprecated, use the new "
  789
+                          "*_url arguments instead.", DeprecationWarning,
  790
+                          stacklevel=2)
770 791
         opts = obj._meta
771  
-        pk_value = obj._get_pk_val()
  792
+        pk_value = obj.pk
  793
+        app_label = opts.app_label
  794
+        model_name = opts.module_name
  795
+        site_name = self.admin_site.name
  796
+
  797
+        msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
772 798
 
773  
-        msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
774 799
         # Here, we distinguish between different save types by checking for
775 800
         # the presence of keys in request.POST.
776 801
         if "_continue" in request.POST:
777  
-            self.message_user(request, msg + ' ' + _("You may edit it again below."))
  802
+            msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
  803
+            self.message_user(request, msg)
  804
+            if continue_editing_url is None:
  805
+                continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name)
  806
+            url = reverse(continue_editing_url, args=(quote(pk_value),),
  807
+                          current_app=site_name)
778 808
             if "_popup" in request.POST:
779  
-                post_url_continue += "?_popup=1"
780  
-            return HttpResponseRedirect(post_url_continue % pk_value)
  809
+                url += "?_popup=1"
  810
+            return HttpResponseRedirect(url)
781 811
 
782 812
         if "_popup" in request.POST:
783 813
             return HttpResponse(
@@ -786,72 +816,104 @@ def response_add(self, request, obj, post_url_continue='../%s/'):
786 816
                 # escape() calls force_text.
787 817
                 (escape(pk_value), escapejs(obj)))
788 818
         elif "_addanother" in request.POST:
789  
-            self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(opts.verbose_name)))
790  
-            return HttpResponseRedirect(request.path)
  819
+            msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict
  820
+            self.message_user(request, msg)
  821
+            if add_another_url is None:
  822
+                add_another_url = 'admin:%s_%s_add' % (app_label, model_name)
  823
+            url = reverse(add_another_url, current_app=site_name)
  824
+            return HttpResponseRedirect(url)
791 825
         else:
  826
+            msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict
792 827
             self.message_user(request, msg)
793 828
 
794 829
             # Figure out where to redirect. If the user has change permission,
795 830
             # redirect to the change-list page for this object. Otherwise,
796 831
             # redirect to the admin index.
797 832
             if self.has_change_permission(request, None):
798  
-                post_url = reverse('admin:%s_%s_changelist' %
799  
-                                   (opts.app_label, opts.module_name),
800  
-                                   current_app=self.admin_site.name)
  833
+                if hasperm_url is None:
  834
+                    hasperm_url = 'admin:%s_%s_changelist' % (app_label, model_name)
  835
+                url = reverse(hasperm_url, current_app=site_name)
801 836
             else:
802  
-                post_url = reverse('admin:index',
803  
-                                   current_app=self.admin_site.name)
804  
-            return HttpResponseRedirect(post_url)
  837
+                if noperm_url is None:
  838
+                    noperm_url = 'admin:index'
  839
+                url = reverse(noperm_url, current_app=site_name)
  840
+            return HttpResponseRedirect(url)
805 841
 
806  
-    def response_change(self, request, obj):
  842
+    def response_change(self, request, obj, continue_editing_url=None,
  843
+                        save_as_new_url=None, add_another_url=None,
  844
+                        hasperm_url=None, noperm_url=None):
807 845
         """
808 846
         Determines the HttpResponse for the change_view stage.
  847
+
  848
+        :param request: HttpRequest instance.
  849
+        :param obj: Object just modified.
  850
+        :param continue_editing_url: URL where user will be redirected after
  851
+                                     pressing 'Save and continue editing'.
  852
+        :param save_as_new_url: URL where user will be redirected after pressing
  853
+                                'Save as new' (when applicable).
  854
+        :param add_another_url: URL where user will be redirected after pressing
  855
+                                'Save and add another'.
  856
+        :param hasperm_url: URL to redirect after a successful object edition when
  857
+                            the user has change permissions.
  858
+        :param noperm_url: URL to redirect after a successful object edition when
  859
+                           the user has no change permissions.
809 860
         """
810 861
         opts = obj._meta
811 862
 
  863
+        app_label = opts.app_label
  864
+        model_name = opts.module_name
  865
+        site_name = self.admin_site.name
  866
+        verbose_name = opts.verbose_name
812 867
         # Handle proxy models automatically created by .only() or .defer().
813 868
         # Refs #14529
814  
-        verbose_name = opts.verbose_name
815  
-        module_name = opts.module_name
816 869
         if obj._deferred:
817 870
             opts_ = opts.proxy_for_model._meta
818 871
             verbose_name = opts_.verbose_name
819  
-            module_name = opts_.module_name
  872
+            model_name = opts_.module_name
820 873
 
821  
-        pk_value = obj._get_pk_val()
  874
+        msg_dict = {'name': force_text(verbose_name), 'obj': force_text(obj)}
822 875
 
823  
-        msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_text(verbose_name), 'obj': force_text(obj)}
824 876
         if "_continue" in request.POST:
825  
-            self.message_user(request, msg + ' ' + _("You may edit it again below."))
826  
-            if "_popup" in request.REQUEST:
827  
-                return HttpResponseRedirect(request.path + "?_popup=1")
828  
-            else:
829  
-                return HttpResponseRedirect(request.path)
  877
+            msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict
  878
+            self.message_user(request, msg)
  879
+            if continue_editing_url is None:
  880
+                continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name)
  881
+            url = reverse(continue_editing_url, args=(quote(obj.pk),),
  882
+                          current_app=site_name)
  883
+            if "_popup" in request.POST:
  884
+                url += "?_popup=1"
  885
+            return HttpResponseRedirect(url)
830 886
         elif "_saveasnew" in request.POST:
831  
-            msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_text(verbose_name), 'obj': obj}
  887
+            msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
832 888
             self.message_user(request, msg)
833  
-            return HttpResponseRedirect(reverse('admin:%s_%s_change' %
834  
-                                        (opts.app_label, module_name),
835  
-                                        args=(pk_value,),
836  
-                                        current_app=self.admin_site.name))
  889
+            if save_as_new_url is None:
  890
+                save_as_new_url = 'admin:%s_%s_change' % (app_label, model_name)
  891
+            url = reverse(save_as_new_url, args=(quote(obj.pk),),
  892
+                          current_app=site_name)
  893
+            return HttpResponseRedirect(url)
837 894
         elif "_addanother" in request.POST:
838  
-            self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(verbose_name)))
839  
-            return HttpResponseRedirect(reverse('admin:%s_%s_add' %
840  
-                                        (opts.app_label, module_name),
841  
-                                        current_app=self.admin_site.name))
  895
+            msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict
  896
+            self.message_user(request, msg)
  897
+            if add_another_url is None:
  898
+                add_another_url = 'admin:%s_%s_add' % (app_label, model_name)
  899
+            url = reverse(add_another_url, current_app=site_name)
  900
+            return HttpResponseRedirect(url)
842 901
         else:
  902
+            msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict
843 903
             self.message_user(request, msg)
844 904
             # Figure out where to redirect. If the user has change permission,
845 905
             # redirect to the change-list page for this object. Otherwise,
846 906
             # redirect to the admin index.
847 907
             if self.has_change_permission(request, None):
848  
-                post_url = reverse('admin:%s_%s_changelist' %
849  
-                                   (opts.app_label, module_name),
850  
-                                   current_app=self.admin_site.name)
  908
+                if hasperm_url is None:
  909
+                    hasperm_url = 'admin:%s_%s_changelist' % (app_label,
  910
+                                                              model_name)
  911
+                url = reverse(hasperm_url, current_app=site_name)
851 912
             else:
852  
-                post_url = reverse('admin:index',
853  
-                                   current_app=self.admin_site.name)
854  
-            return HttpResponseRedirect(post_url)
  913
+                if noperm_url is None:
  914
+                    noperm_url = 'admin:index'
  915
+                url = reverse(noperm_url, current_app=site_name)
  916
+            return HttpResponseRedirect(url)
855 917
 
856 918
     def response_action(self, request, queryset):
857 919
         """
5  django/contrib/auth/admin.py
@@ -153,7 +153,7 @@ def user_change_password(self, request, id, form_url=''):
153 153
             'admin/auth/user/change_password.html'
154 154
         ], context, current_app=self.admin_site.name)
155 155
 
156  
-    def response_add(self, request, obj, post_url_continue='../%s/'):
  156
+    def response_add(self, request, obj, **kwargs):
157 157
         """
158 158
         Determines the HttpResponse for the add_view stage. It mostly defers to
159 159
         its superclass implementation but is customized because the User model
@@ -166,8 +166,7 @@ def response_add(self, request, obj, post_url_continue='../%s/'):
166 166
         # * We are adding a user in a popup
167 167
         if '_addanother' not in request.POST and '_popup' not in request.POST:
168 168
             request.POST['_continue'] = 1
169  
-        return super(UserAdmin, self).response_add(request, obj,
170  
-                                                   post_url_continue)
  169
+        return super(UserAdmin, self).response_add(request, obj, **kwargs)
171 170
 
172 171
 admin.site.register(Group, GroupAdmin)
173 172
 admin.site.register(User, UserAdmin)
37  tests/regressiontests/admin_custom_urls/models.py
@@ -50,3 +50,40 @@ def wrapper(*args, **kwargs):
50 50
 
51 51
 
52 52
 admin.site.register(Action, ActionAdmin)
  53
+
  54
+
  55
+class Person(models.Model):
  56
+    nick = models.CharField(max_length=20)
  57
+
  58
+
  59
+class PersonAdmin(admin.ModelAdmin):
  60
+    """A custom ModelAdmin that customizes the deprecated post_url_continue
  61
+    argument to response_add()"""
  62
+    def response_add(self, request, obj, post_url_continue='../%s/continue/',
  63
+                     continue_url=None, add_url=None, hasperm_url=None,
  64
+                     noperm_url=None):
  65
+        return super(PersonAdmin, self).response_add(request, obj,
  66
+                                                     post_url_continue,
  67
+                                                     continue_url, add_url,
  68
+                                                     hasperm_url, noperm_url)
  69
+
  70
+
  71
+admin.site.register(Person, PersonAdmin)
  72
+
  73
+
  74
+class City(models.Model):
  75
+    name = models.CharField(max_length=20)
  76
+
  77
+
  78
+class CityAdmin(admin.ModelAdmin):
  79
+    """A custom ModelAdmin that redirects to the changelist when the user
  80
+    presses the 'Save and add another' button when adding a model instance."""
  81
+    def response_add(self, request, obj,
  82
+                     add_another_url='admin:admin_custom_urls_city_changelist',
  83
+                     **kwargs):
  84
+        return super(CityAdmin, self).response_add(request, obj,
  85
+                                                   add_another_url=add_another_url,
  86
+                                                   **kwargs)
  87
+
  88
+
  89
+admin.site.register(City, CityAdmin)
46  tests/regressiontests/admin_custom_urls/tests.py
... ...
@@ -1,12 +1,14 @@
1 1
 from __future__ import absolute_import, unicode_literals
2 2
 
  3
+import warnings
  4
+
3 5
 from django.contrib.admin.util import quote
4 6
 from django.core.urlresolvers import reverse
5 7
 from django.template.response import TemplateResponse
6 8
 from django.test import TestCase
7 9
 from django.test.utils import override_settings
8 10
 
9  
-from .models import Action
  11
+from .models import Action, Person, City
10 12
 
11 13
 
12 14
 @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@@ -81,3 +83,45 @@ def testAdminUrlsNoClash(self):
81 83
         self.assertEqual(response.status_code, 200)
82 84
         self.assertContains(response, 'Change action')
83 85
         self.assertContains(response, 'value="path/to/html/document.html"')
  86
+
  87
+
  88
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
  89
+class CustomUrlsWorkflowTests(TestCase):
  90
+    fixtures = ['users.json']
  91
+
  92
+    def setUp(self):
  93
+        self.client.login(username='super', password='secret')
  94
+
  95
+    def tearDown(self):
  96
+        self.client.logout()
  97
+
  98
+    def test_old_argument_deprecation(self):
  99
+        """Test reporting of post_url_continue deprecation."""
  100
+        post_data = {
  101
+            'nick': 'johndoe',
  102
+        }
  103
+        cnt = Person.objects.count()
  104
+        with warnings.catch_warnings(record=True) as w:
  105
+            warnings.simplefilter("always")
  106
+            response = self.client.post(reverse('admin:admin_custom_urls_person_add'), post_data)
  107
+            self.assertEqual(response.status_code, 302)
  108
+            self.assertEqual(Person.objects.count(), cnt + 1)
  109
+            # We should get a DeprecationWarning
  110
+            self.assertEqual(len(w), 1)
  111
+            self.assertTrue(isinstance(w[0].message, DeprecationWarning))
  112
+
  113
+    def test_custom_add_another_redirect(self):
  114
+        """Test customizability of post-object-creation redirect URL."""
  115
+        post_data = {
  116
+            'name': 'Rome',
  117
+            '_addanother': '1',
  118
+        }
  119
+        cnt = City.objects.count()
  120
+        with warnings.catch_warnings(record=True) as w:
  121
+            # POST to the view whose post-object-creation redir URL argument we
  122
+            # are customizing (object creation)
  123
+            response = self.client.post(reverse('admin:admin_custom_urls_city_add'), post_data)
  124
+            self.assertEqual(City.objects.count(), cnt + 1)
  125
+            # Check that it redirected to the URL we set
  126
+            self.assertRedirects(response, reverse('admin:admin_custom_urls_city_changelist'))
  127
+            self.assertEqual(len(w), 0) # We should get no DeprecationWarning

0 notes on commit 0b908b9

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