Code

Ticket #6903: 6903.3.diff

File 6903.3.diff, 10.6 KB (added by julien, 13 months ago)
Line 
1diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
2index f7bfca4..11aff87 100644
3--- a/django/contrib/admin/options.py
4+++ b/django/contrib/admin/options.py
5@@ -12,6 +12,7 @@ from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_o
6     model_format_dict, NestedObjects)
7 from django.contrib.admin.templatetags.admin_static import static
8 from django.contrib import messages
9+from django.utils.http import is_safe_url
10 from django.views.decorators.csrf import csrf_protect
11 from django.core.exceptions import PermissionDenied, ValidationError
12 from django.core.paginator import Paginator
13@@ -35,6 +36,14 @@ from django.utils.translation import ugettext as _
14 from django.utils.translation import ungettext
15 from django.utils.encoding import force_text
16 
17+try:
18+    from urllib import parse as urllib_parse
19+except ImportError:     # Python 2
20+    import urllib as urllib_parse
21+    import urlparse
22+    urllib_parse.urlparse = urlparse.urlparse
23+
24+
25 HORIZONTAL, VERTICAL = 1, 2
26 # returns the <ul> class for a given radio_admin field
27 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
28@@ -909,10 +918,17 @@ class ModelAdmin(BaseModelAdmin):
29         when editing an existing object.
30         """
31         opts = self.model._meta
32+        changelist_url = reverse('admin:%s_%s_changelist' %
33+                                 (opts.app_label, opts.model_name),
34+                                 current_app=self.admin_site.name)
35+
36+        changelist_filters = request.POST.get('_changelist_filters')
37+        if changelist_filters:
38+            return HttpResponseRedirect(
39+                '%s?%s' % (changelist_url, changelist_filters))
40+
41         if self.has_change_permission(request, None):
42-            post_url = reverse('admin:%s_%s_changelist' %
43-                               (opts.app_label, opts.model_name),
44-                               current_app=self.admin_site.name)
45+            post_url = changelist_url
46         else:
47             post_url = reverse('admin:index',
48                                current_app=self.admin_site.name)
49@@ -1095,6 +1111,7 @@ class ModelAdmin(BaseModelAdmin):
50         ModelForm = self.get_form(request, obj)
51         formsets = []
52         inline_instances = self.get_inline_instances(request, obj)
53+        changelist_filters = None
54         if request.method == 'POST':
55             form = ModelForm(request.POST, request.FILES, instance=obj)
56             if form.is_valid():
57@@ -1134,6 +1151,13 @@ class ModelAdmin(BaseModelAdmin):
58                                   queryset=inline.get_queryset(request))
59                 formsets.append(formset)
60 
61+            referer = request.META.get('HTTP_REFERER')
62+            if referer:
63+                referer = urllib_parse.urlparse(referer)
64+                changelist_url = reverse('admin:%s_%s_changelist' % (opts.app_label, opts.model_name))
65+                if is_safe_url(url=referer.geturl(), host=request.get_host()) and referer.path.startswith(changelist_url):
66+                    changelist_filters = referer.query
67+
68         adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
69             self.get_prepopulated_fields(request, obj),
70             self.get_readonly_fields(request, obj),
71@@ -1160,6 +1184,7 @@ class ModelAdmin(BaseModelAdmin):
72             'inline_admin_formsets': inline_admin_formsets,
73             'errors': helpers.AdminErrorList(form, formsets),
74             'app_label': opts.app_label,
75+            'changelist_filters': changelist_filters,
76         }
77         context.update(extra_context or {})
78         return self.render_change_form(request, context, change=True, obj=obj, form_url=form_url)
79@@ -1348,12 +1373,20 @@ class ModelAdmin(BaseModelAdmin):
80 
81             self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_text(opts.verbose_name), 'obj': force_text(obj_display)})
82 
83+            changelist_url = reverse('admin:%s_%s_changelist' %
84+                                     (opts.app_label, opts.model_name),
85+                                     current_app=self.admin_site.name)
86+
87+            changelist_filters = request.POST.get('_changelist_filters')
88+            if changelist_filters:
89+                return HttpResponseRedirect('%s?%s' % (changelist_url, changelist_filters))
90+
91             if not self.has_change_permission(request, None):
92                 return HttpResponseRedirect(reverse('admin:index',
93                                                     current_app=self.admin_site.name))
94-            return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
95-                                        (opts.app_label, opts.model_name),
96-                                        current_app=self.admin_site.name))
97+            return HttpResponseRedirect(changelist_url)
98+        else:
99+            changelist_filters = request.GET.get('_changelist_filters')
100 
101         object_name = force_text(opts.verbose_name)
102 
103@@ -1371,6 +1404,7 @@ class ModelAdmin(BaseModelAdmin):
104             "protected": protected,
105             "opts": opts,
106             "app_label": app_label,
107+            "changelist_filters": changelist_filters,
108         }
109         context.update(extra_context or {})
110 
111diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
112index daf3775..0b2312e 100644
113--- a/django/contrib/admin/templates/admin/change_form.html
114+++ b/django/contrib/admin/templates/admin/change_form.html
115@@ -38,6 +38,7 @@
116 <form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.model_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
117 <div>
118 {% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
119+{% if changelist_filters %}<input type="hidden" name="_changelist_filters" value="{{ changelist_filters }}" />{% endif %}
120 {% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
121 {% if errors %}
122     <p class="errornote">
123diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html
124index c1a7115..065fa14 100644
125--- a/django/contrib/admin/templates/admin/delete_confirmation.html
126+++ b/django/contrib/admin/templates/admin/delete_confirmation.html
127@@ -36,6 +36,7 @@
128     <form action="" method="post">{% csrf_token %}
129     <div>
130     <input type="hidden" name="post" value="yes" />
131+    {% if changelist_filters %}<input type="hidden" name="_changelist_filters" value="{{ changelist_filters }}" />{% endif %}
132     <input type="submit" value="{% trans "Yes, I'm sure" %}" />
133     </div>
134     </form>
135diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
136index bb77932..9199a5e 100644
137--- a/tests/admin_views/tests.py
138+++ b/tests/admin_views/tests.py
139@@ -4024,3 +4024,97 @@ class AdminUserMessageTest(TestCase):
140         self.assertContains(response,
141                             '<li class="extra_tag info">Test tags</li>',
142                             html=True)
143+
144+
145+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
146+class AdminChangeListRedirectionTests(TestCase):
147+    urls = "admin_views.urls"
148+    fixtures = ['admin-views-users']
149+
150+    def setUp(self):
151+        Person.objects.create(name="Chris", gender=1, alive=True, age=25)
152+        Person.objects.create(name="Jack", gender=2, alive=False, age=47)
153+        Person.objects.create(name="Bob", gender=1, alive=True, age=36)
154+
155+        self.client.login(username='super', password='secret')
156+
157+    def get_data(self):
158+        return {
159+            'name': 'Joe', 'gender': '1', 'alive': '1', 'age': '58'
160+        }
161+
162+    def tearDown(self):
163+        self.client.logout()
164+
165+    def get_filters_querystring(self):
166+        return urlencode({
167+                'alive': 1,
168+            })
169+
170+    def get_filtered_changelist_url(self):
171+        return "%s%s?%s" % ('http://testserver', reverse('admin:admin_views_person_changelist'), self.get_filters_querystring())
172+
173+    def test_change_redirect(self):
174+        """
175+        Ensure that we're correctly redirected to the filtered changelist
176+        after an existing object is saved.
177+        Refs #6903.
178+        """
179+        person = Person.objects.get(name='Chris')
180+        change_url = reverse('admin:admin_views_person_change', args=(person.pk,))
181+
182+        # Simulate clicking an object from a filtered changelist
183+        response = self.client.get(change_url, HTTP_REFERER=self.get_filtered_changelist_url())
184+        self.assertContains(response, '_changelist_filters')
185+        self.assertContains(response, self.get_filters_querystring())
186+
187+        # Save the object
188+        data = self.get_data()
189+        data['_save'] = 1
190+        data['_changelist_filters'] = self.get_filters_querystring()
191+        response = self.client.post(change_url, data)
192+
193+        # Check that we return to the filtered changelist
194+        self.assertRedirects(response, self.get_filtered_changelist_url())
195+
196+    def test_add_redirect(self):
197+        """
198+        Ensure that we're correctly redirected to the non-filtered changelist
199+        after a new object is added.
200+        Refs #6903.
201+        """
202+        add_url = reverse('admin:admin_views_person_add')
203+
204+        response = self.client.get(add_url, HTTP_REFERER=self.get_filtered_changelist_url())
205+        self.assertNotContains(response, '_changelist_filters')
206+        self.assertNotContains(response, self.get_filters_querystring())
207+
208+        # Add a new object
209+        data = self.get_data()
210+        data['_save'] = 1
211+        data['_changelist_filters'] = self.get_filters_querystring()
212+        response = self.client.post(add_url, data)
213+
214+        # Check that we return to the non-filtered changelist
215+        self.assertRedirects(response, reverse('admin:admin_views_person_changelist'))
216+
217+    def test_delete_redirect(self):
218+        """
219+        Ensure that we're correctly redirected to the filtered changelist
220+        after an existing object is deleted.
221+        Refs #6903.
222+        """
223+        person = Person.objects.get(name='Chris')
224+        delete_url = reverse('admin:admin_views_person_delete', args=(person.pk,))
225+
226+        response = self.client.get(delete_url + '?_changelist_filters=%s' % self.get_filters_querystring())
227+        self.assertContains(response, '_changelist_filters')
228+        self.assertContains(response, self.get_filters_querystring())
229+
230+        # Delete the object
231+        data = {'post': 'yes', '_changelist_filters': self.get_filters_querystring()}
232+        response = self.client.post(delete_url, data)
233+
234+        # Check that we return to the filtered changelist
235+        self.assertRedirects(response, self.get_filtered_changelist_url())
236+