Code

Ticket #6903: 6903.4.diff

File 6903.4.diff, 13.0 KB (added by oinopion, 13 months ago)

Updated earlier patch by julien

Line 
1diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
2index 34583eb..a8526b3 100644
3--- a/django/contrib/admin/options.py
4+++ b/django/contrib/admin/options.py
5@@ -14,6 +14,7 @@ from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_o
6 from django.contrib.admin import validation
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, FieldError
12 from django.core.paginator import Paginator
13@@ -38,6 +39,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' % (' inline' if x == HORIZONTAL else '')
28@@ -963,10 +972,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@@ -1149,7 +1165,9 @@ 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+            changelist_filters = request.POST.get('_changelist_filters')
56             form = ModelForm(request.POST, request.FILES, instance=obj)
57             if form.is_valid():
58                 form_validated = True
59@@ -1188,6 +1206,13 @@ class ModelAdmin(BaseModelAdmin):
60                                   queryset=inline.get_queryset(request))
61                 formsets.append(formset)
62 
63+            referer = request.META.get('HTTP_REFERER')
64+            if referer:
65+                referer = urllib_parse.urlparse(referer)
66+                changelist_url = reverse('admin:%s_%s_changelist' % (opts.app_label, opts.model_name))
67+                if is_safe_url(url=referer.geturl(), host=request.get_host()) and referer.path.startswith(changelist_url):
68+                    changelist_filters = referer.query
69+
70         adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
71             self.get_prepopulated_fields(request, obj),
72             self.get_readonly_fields(request, obj),
73@@ -1214,6 +1239,7 @@ class ModelAdmin(BaseModelAdmin):
74             'inline_admin_formsets': inline_admin_formsets,
75             'errors': helpers.AdminErrorList(form, formsets),
76             'app_label': opts.app_label,
77+            'changelist_filters': changelist_filters,
78         }
79         context.update(extra_context or {})
80         return self.render_change_form(request, context, change=True, obj=obj, form_url=form_url)
81@@ -1406,12 +1432,22 @@ class ModelAdmin(BaseModelAdmin):
82                                        'obj': force_text(obj_display)},
83                               messages.SUCCESS)
84 
85+            changelist_url = reverse('admin:%s_%s_changelist' %
86+                                     (opts.app_label, opts.model_name),
87+                                     current_app=self.admin_site.name)
88+
89+            changelist_filters = request.POST.get('_changelist_filters')
90+            if changelist_filters:
91+                return HttpResponseRedirect('%s?%s' % (changelist_url, changelist_filters))
92+
93             if not self.has_change_permission(request, None):
94                 return HttpResponseRedirect(reverse('admin:index',
95                                                     current_app=self.admin_site.name))
96-            return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
97-                                        (opts.app_label, opts.model_name),
98-                                        current_app=self.admin_site.name))
99+            return HttpResponseRedirect(changelist_url)
100+        else:
101+            changelist_filters = request.GET.get('_changelist_filters')
102+            if changelist_filters:
103+                changelist_filters = unquote(changelist_filters)
104 
105         object_name = force_text(opts.verbose_name)
106 
107@@ -1429,6 +1465,7 @@ class ModelAdmin(BaseModelAdmin):
108             "protected": protected,
109             "opts": opts,
110             "app_label": app_label,
111+            "changelist_filters": changelist_filters,
112         }
113         context.update(extra_context or {})
114 
115diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
116index 4accf80..6870be0 100644
117--- a/django/contrib/admin/templates/admin/change_form.html
118+++ b/django/contrib/admin/templates/admin/change_form.html
119@@ -38,6 +38,7 @@
120 <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 %}
121 <div>
122 {% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
123+{% if changelist_filters %}<input type="hidden" name="_changelist_filters" value="{{ changelist_filters }}" />{% endif %}
124 {% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
125 {% if errors %}
126     <p class="errornote">
127diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html
128index c1a7115..065fa14 100644
129--- a/django/contrib/admin/templates/admin/delete_confirmation.html
130+++ b/django/contrib/admin/templates/admin/delete_confirmation.html
131@@ -36,6 +36,7 @@
132     <form action="" method="post">{% csrf_token %}
133     <div>
134     <input type="hidden" name="post" value="yes" />
135+    {% if changelist_filters %}<input type="hidden" name="_changelist_filters" value="{{ changelist_filters }}" />{% endif %}
136     <input type="submit" value="{% trans "Yes, I'm sure" %}" />
137     </div>
138     </form>
139diff --git a/django/contrib/admin/templates/admin/submit_line.html b/django/contrib/admin/templates/admin/submit_line.html
140index 38a97a1..6ebd155 100644
141--- a/django/contrib/admin/templates/admin/submit_line.html
142+++ b/django/contrib/admin/templates/admin/submit_line.html
143@@ -1,7 +1,14 @@
144 {% load i18n admin_urls %}
145 <div class="submit-row">
146 {% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
147-{% if show_delete_link %}<p class="deletelink-box"><a href="{% url opts|admin_urlname:'delete' original.pk|admin_urlquote %}" class="deletelink">{% trans "Delete" %}</a></p>{% endif %}
148+{% if show_delete_link %}
149+    {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url%}
150+    <p class="deletelink-box">
151+        <a href="{{ delete_url }}{% if changelist_filters %}?_changelist_filters={{ changelist_filters|admin_urlquote }}{% endif %}" class="deletelink">
152+            {% trans "Delete" %}
153+        </a>
154+    </p>
155+{% endif %}
156 {% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" />{%endif%}
157 {% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}
158 {% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
159diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py
160index cecc6ed..bccf7f2 100644
161--- a/django/contrib/admin/templatetags/admin_modify.py
162+++ b/django/contrib/admin/templatetags/admin_modify.py
163@@ -37,10 +37,12 @@ def submit_row(context):
164                             not is_popup and (not save_as or context['add']),
165         'show_save_and_continue': not is_popup and context['has_change_permission'],
166         'is_popup': is_popup,
167-        'show_save': True
168+        'show_save': True,
169     }
170     if context.get('original') is not None:
171         ctx['original'] = context['original']
172+    if context.get('changelist_filters'):
173+        ctx['changelist_filters'] = context['changelist_filters']
174     return ctx
175 
176 @register.filter
177diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
178index c0925e0..e853e52 100644
179--- a/tests/admin_views/tests.py
180+++ b/tests/admin_views/tests.py
181@@ -4157,3 +4157,97 @@ class AdminUserMessageTest(TestCase):
182         self.assertContains(response,
183                             '<li class="extra_tag info">Test tags</li>',
184                             html=True)
185+
186+
187+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
188+class AdminChangeListRedirectionTests(TestCase):
189+    urls = "admin_views.urls"
190+    fixtures = ['admin-views-users']
191+
192+    def setUp(self):
193+        Person.objects.create(name="Chris", gender=1, alive=True, age=25)
194+        Person.objects.create(name="Jack", gender=2, alive=False, age=47)
195+        Person.objects.create(name="Bob", gender=1, alive=True, age=36)
196+
197+        self.client.login(username='super', password='secret')
198+
199+    def get_data(self):
200+        return {
201+            'name': 'Joe', 'gender': '1', 'alive': '1', 'age': '58'
202+        }
203+
204+    def tearDown(self):
205+        self.client.logout()
206+
207+    def get_filters_querystring(self):
208+        return urlencode({
209+                'alive': 1,
210+            })
211+
212+    def get_filtered_changelist_url(self):
213+        return "%s%s?%s" % ('http://testserver', reverse('admin:admin_views_person_changelist'), self.get_filters_querystring())
214+
215+    def test_change_redirect(self):
216+        """
217+        Ensure that we're correctly redirected to the filtered changelist
218+        after an existing object is saved.
219+        Refs #6903.
220+        """
221+        person = Person.objects.get(name='Chris')
222+        change_url = reverse('admin:admin_views_person_change', args=(person.pk,))
223+
224+        # Simulate clicking an object from a filtered changelist
225+        response = self.client.get(change_url, HTTP_REFERER=self.get_filtered_changelist_url())
226+        self.assertContains(response, '_changelist_filters')
227+        self.assertContains(response, self.get_filters_querystring())
228+
229+        # Save the object
230+        data = self.get_data()
231+        data['_save'] = 1
232+        data['_changelist_filters'] = self.get_filters_querystring()
233+        response = self.client.post(change_url, data)
234+
235+        # Check that we return to the filtered changelist
236+        self.assertRedirects(response, self.get_filtered_changelist_url())
237+
238+    def test_add_redirect(self):
239+        """
240+        Ensure that we're correctly redirected to the non-filtered changelist
241+        after a new object is added.
242+        Refs #6903.
243+        """
244+        add_url = reverse('admin:admin_views_person_add')
245+
246+        response = self.client.get(add_url, HTTP_REFERER=self.get_filtered_changelist_url())
247+        self.assertNotContains(response, '_changelist_filters')
248+        self.assertNotContains(response, self.get_filters_querystring())
249+
250+        # Add a new object
251+        data = self.get_data()
252+        data['_save'] = 1
253+        data['_changelist_filters'] = self.get_filters_querystring()
254+        response = self.client.post(add_url, data)
255+
256+        # Check that we return to the non-filtered changelist
257+        self.assertRedirects(response, reverse('admin:admin_views_person_changelist'))
258+
259+    def test_delete_redirect(self):
260+        """
261+        Ensure that we're correctly redirected to the filtered changelist
262+        after an existing object is deleted.
263+        Refs #6903.
264+        """
265+        person = Person.objects.get(name='Chris')
266+        delete_url = reverse('admin:admin_views_person_delete', args=(person.pk,))
267+
268+        response = self.client.get(delete_url + '?_changelist_filters=%s' % self.get_filters_querystring())
269+        self.assertContains(response, '_changelist_filters')
270+        self.assertContains(response, self.get_filters_querystring())
271+
272+        # Delete the object
273+        data = {'post': 'yes', '_changelist_filters': self.get_filters_querystring()}
274+        response = self.client.post(delete_url, data)
275+
276+        # Check that we return to the filtered changelist
277+        self.assertRedirects(response, self.get_filtered_changelist_url())
278+