Code

Ticket #6903: 6903-r11616.diff

File 6903-r11616.diff, 14.6 KB (added by marcob, 5 years ago)

Patch fixed for r11616

Line 
1diff -r 1c267332e8fc django/contrib/admin/options.py
2--- a/django/contrib/admin/options.py   Tue Feb 24 20:51:14 2009 -0200
3+++ b/django/contrib/admin/options.py   Wed Feb 25 12:18:54 2009 -0200
4@@ -14,6 +14,7 @@
5 from django.utils.datastructures import SortedDict
6 from django.utils.functional import update_wrapper
7 from django.utils.html import escape
8+from django.utils.http import urlquote
9 from django.utils.safestring import mark_safe
10 from django.utils.functional import curry
11 from django.utils.text import capfirst, get_text_list
12@@ -29,6 +30,10 @@
13 # returns the <ul> class for a given radio_admin field
14 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
15 
16+# GET parameter for the URL to return to after change/add views. This lets the
17+# admin save the state of the changelist page through the change/add page.
18+RETURN_GET_PARAM = '_return_to'
19+
20 class IncorrectLookupParameters(Exception):
21     pass
22 
23@@ -597,28 +602,41 @@
24         pk_value = obj._get_pk_val()
25         
26         msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
27-        # Here, we distinguish between different save types by checking for
28+
29+        # Below, we distinguish between different save types by checking for
30         # the presence of keys in request.POST.
31+
32+        # The user clicked "save and continue editing". Redirect to admin URL
33+        # for this object, possibly in popup mode if needed.
34         if request.POST.has_key("_continue"):
35             self.message_user(request, msg + ' ' + _("You may edit it again below."))
36             if request.POST.has_key("_popup"):
37                 post_url_continue += "?_popup=1"
38             return HttpResponseRedirect(post_url_continue % pk_value)
39-       
40+
41+        # This was a popup and the user clicked the simple "save" button.
42+        # Return a little script which populates the calling page with the
43+        # saved key.
44         if request.POST.has_key("_popup"):
45             return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
46                 # escape() calls force_unicode.
47                 (escape(pk_value), escape(obj)))
48+
49+        # The user clicked "save and add another", so redirect back to the bare
50+        # "add" page, which will be request.path.
51         elif request.POST.has_key("_addanother"):
52             self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
53             return HttpResponseRedirect(request.path)
54+
55+        # Just a regular "save" click. IF we've been pased an explicit return
56+        # URL in GET, go there. Otherwise, redirect to the plain changelist.
57+        # If the user doesn't have changelist permission, just redirect back
58+        # to the main admin index.
59         else:
60             self.message_user(request, msg)
61-           
62-            # Figure out where to redirect. If the user has change permission,
63-            # redirect to the change-list page for this object. Otherwise,
64-            # redirect to the admin index.
65-            if self.has_change_permission(request, None):
66+            if RETURN_GET_PARAM in request.GET:
67+                post_url = request.GET[RETURN_GET_PARAM]
68+            elif self.has_change_permission(request, None):
69                 post_url = '../'
70             else:
71                 post_url = '../../../'
72@@ -632,23 +655,34 @@
73         pk_value = obj._get_pk_val()
74
75         msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
76+
77+        # Similar redirecting logic to response_add, above.
78+
79+        # "Save and continue editing"
80         if request.POST.has_key("_continue"):
81             self.message_user(request, msg + ' ' + _("You may edit it again below."))
82             if request.REQUEST.has_key('_popup'):
83                 return HttpResponseRedirect(request.path + "?_popup=1")
84             else:
85                 return HttpResponseRedirect(request.path)
86+
87+        # "Save as a new object"
88         elif request.POST.has_key("_saveasnew"):
89             msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj}
90             self.message_user(request, msg)
91             return HttpResponseRedirect("../%s/" % pk_value)
92+
93+        # "Save and add another"
94         elif request.POST.has_key("_addanother"):
95             self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
96             return HttpResponseRedirect("../add/")
97+
98+        # "Save"
99         else:
100             self.message_user(request, msg)
101-            return HttpResponseRedirect("../")
102-   
103+            post_url = request.GET.get(RETURN_GET_PARAM, '../')
104+            return HttpResponseRedirect(post_url)
105+
106     def response_action(self, request, queryset):
107         """
108         Handle an admin action. This is called if a request is POSTed to the
109@@ -711,6 +747,7 @@
110         
111         ModelForm = self.get_form(request)
112         formsets = []
113+        return_to = request.GET.get(RETURN_GET_PARAM, None)
114         if request.method == 'POST':
115             form = ModelForm(request.POST, request.FILES)
116             if form.is_valid():
117@@ -758,7 +795,10 @@
118                     prefix = "%s-%s" % (prefix, prefixes[prefix])
119                 formset = FormSet(instance=self.model(), prefix=prefix)
120                 formsets.append(formset)
121-       
122+
123+        if return_to:
124+            form_url = form_url + '?%s=%s' % (RETURN_GET_PARAM, urlquote(return_to))
125+
126         adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
127         media = self.media + adminForm.media
128         
129@@ -808,6 +848,7 @@
130         
131         ModelForm = self.get_form(request, obj)
132         formsets = []
133+        return_to = request.GET.get(RETURN_GET_PARAM, None)
134         if request.method == 'POST':
135             form = ModelForm(request.POST, request.FILES, instance=obj)
136             if form.is_valid():
137@@ -846,7 +887,12 @@
138                     prefix = "%s-%s" % (prefix, prefixes[prefix])
139                 formset = FormSet(instance=obj, prefix=prefix)
140                 formsets.append(formset)
141-       
142+
143+        if return_to:
144+            form_url = '?%s=%s' % (RETURN_GET_PARAM, urlquote(return_to))
145+        else:
146+            form_url = ''
147+
148         adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
149         media = self.media + adminForm.media
150         
151@@ -870,7 +916,7 @@
152             'app_label': opts.app_label,
153         }
154         context.update(extra_context or {})
155-        return self.render_change_form(request, context, change=True, obj=obj)
156+        return self.render_change_form(request, context, change=True, form_url=form_url, obj=obj)
157     change_view = transaction.commit_on_success(change_view)
158     
159     def changelist_view(self, request, extra_context=None):
160diff -r 1c267332e8fc django/contrib/admin/views/main.py
161--- a/django/contrib/admin/views/main.py        Tue Feb 24 20:51:14 2009 -0200
162+++ b/django/contrib/admin/views/main.py        Wed Feb 25 12:18:54 2009 -0200
163@@ -1,12 +1,12 @@
164 from django.contrib.admin.filterspecs import FilterSpec
165-from django.contrib.admin.options import IncorrectLookupParameters
166+from django.contrib.admin.options import IncorrectLookupParameters, RETURN_GET_PARAM
167 from django.contrib.admin.util import quote
168 from django.core.paginator import Paginator, InvalidPage
169 from django.db import models
170 from django.db.models.query import QuerySet
171 from django.utils.encoding import force_unicode, smart_str
172 from django.utils.translation import ugettext
173-from django.utils.http import urlencode
174+from django.utils.http import urlencode, urlquote
175 import operator
176 
177 try:
178@@ -70,6 +70,7 @@
179         self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name))
180         self.filter_specs, self.has_filters = self.get_filters(request)
181         self.pk_attname = self.lookup_opts.pk.attname
182+        self.this_clist_url = request.get_full_path()
183 
184     def get_filters(self, request):
185         filter_specs = []
186@@ -240,4 +241,4 @@
187         return qs
188 
189     def url_for_result(self, result):
190-        return "%s/" % quote(getattr(result, self.pk_attname))
191+        return "%s/?%s=%s" % (quote(getattr(result, self.pk_attname)), RETURN_GET_PARAM, urlquote(self.this_clist_url))
192diff -r 1c267332e8fc tests/regressiontests/admin_views/fixtures/admin-views-things.xml
193--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
194+++ b/tests/regressiontests/admin_views/fixtures/admin-views-things.xml Wed Feb 25 12:18:54 2009 -0200
195@@ -0,0 +1,12 @@
196+<?xml version="1.0" encoding="utf-8"?>
197+<django-objects version="1.0">
198+    <object pk="1" model="admin_views.thing">
199+        <field type="CharField" name="title">Ball</field>
200+        <field to="admin_views.color" name="color" rel="ManyToOneRel">1</field>
201+    </object>
202+    <object pk="2" model="admin_views.thing">
203+        <field type="CharField" name="title">Carrot</field>
204+        <field to="admin_views.color" name="color" rel="ManyToOneRel">2</field>
205+    </object>
206+</django-objects>
207+
208diff -r 1c267332e8fc tests/regressiontests/admin_views/tests.py
209--- a/tests/regressiontests/admin_views/tests.py        Tue Feb 24 20:51:14 2009 -0200
210+++ b/tests/regressiontests/admin_views/tests.py        Wed Feb 25 12:18:54 2009 -0200
211@@ -12,23 +12,24 @@
212 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
213 from django.utils.cache import get_max_age
214 from django.utils.html import escape
215+from django.utils.http import urlquote
216 
217 # local test models
218 from models import Article, BarAccount, CustomArticle, EmptyModel, \
219     ExternalSubscriber, FooAccount, Gallery, ModelWithStringPrimaryKey, \
220     Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \
221     Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \
222     Category
223 
224 try:
225     set
226 except NameError:
227     from sets import Set as set
228
229 class AdminViewBasicTest(TestCase):
230-    fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
231-   
232-    # Store the bit of the URL where the admin is registered as a class
233+    fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml', 'admin-views-things.xml']
234+
235+    # Store the bit of the URL where the admin is registered as a class
236     # variable. That way we can test a second AdminSite just by subclassing
237     # this test case and changing urlbit.
238     urlbit = 'admin'
239@@ -204,6 +205,58 @@
240         self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)       
241         response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
242         self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
243+
244+    def testFilterPreservedChangeListLinks(self):
245+        """Link to model changeform in changelist should contain filter-preserving GET data if a filter is active"""
246+        changelist_url = '/test_admin/%s/admin_views/thing/' % self.urlbit
247+        filter_spec = '?color__id__exact=1'
248+        should_contain = """<tr class="row1"><th><a href="1/?_return_to=%s%s">Ball</a></th></tr>""" % (changelist_url, urlquote(filter_spec))
249+        # get the changelist page with one active filter
250+        response = self.client.get(changelist_url, {'color__id__exact': 1})
251+        # links in the changelist table should preserve the filter
252+        self.failUnlessEqual(response.status_code, 200)
253+        self.assertContains(response, should_contain)
254+
255+    def testFilterPreservedEditForm(self):
256+        """Model changeform post handler URL should contain filter specified when clicking the changelist link."""
257+        changelist_url = '/test_admin/%s/admin_views/thing/' % self.urlbit
258+        filter_spec = '?color__id__exact=1'
259+        edit_form_url = '%s1/?_return_to=%s%s' % (changelist_url, changelist_url, urlquote(filter_spec))
260+        should_contain = '<form enctype="multipart/form-data" action="?_return_to=%s%s" method="post" id="thing_form">' % (changelist_url, urlquote(filter_spec))
261+        response = self.client.get(edit_form_url)
262+        self.failUnlessEqual(response.status_code, 200)
263+        self.assertContains(response, should_contain)
264+
265+    def testFilterPreservedAfterEditSave(self):
266+        """Redirection target after 'Save' is clicked in model changeform should be the changelist"""
267+        changelist_url = '/test_admin/%s/admin_views/thing' % self.urlbit
268+        filter_spec = '?color__id__exact=1'
269+        action = '%s/1/?_return_to=%s/%s' % (changelist_url, changelist_url, urlquote(filter_spec))
270+        post_data = {
271+            # Model fields
272+            "title": u"Car",
273+            "color": u"1",
274+        }
275+        response = self.client.post(action, post_data)
276+        # It should redirect to the changelist
277+        self.assertRedirects(response, '%s/%s' % (changelist_url, filter_spec))
278+
279+    def testFilterIgnoredEditAddAnother(self):
280+        """Redirection target after 'Save and add another' is clicked in model changeform should be an add form"""
281+        base_url = '/test_admin/%s/admin_views/thing' % self.urlbit
282+        filter_spec = '?color__id__exact=1'
283+        post_to = '%s/1/?_return_to=%s/%s' % (base_url, base_url, urlquote(filter_spec))
284+        post_data = {
285+            # Model fields
286+            "title": u"Car",
287+            "color": u"1",
288+            # Form button used
289+            "_addanother": u""
290+        }
291+        response = self.client.post(post_to, post_data)
292+        # It should redirect to an add form
293+        #self.failUnlessEqual(response.status_code, 302)
294+        self.assertRedirects(response, '%s/%s' % (base_url, 'add/'))
295 
296     def testLogoutAndPasswordChangeURLs(self):
297         response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
298@@ -618,8 +771,9 @@
299 
300     def test_changelist_to_changeform_link(self):
301         "The link from the changelist referring to the changeform of the object should be quoted"
302-        response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/')
303-        should_contain = """<th><a href="%s/">%s</a></th></tr>""" % (quote(self.pk), escape(self.pk))
304+        changelist_url = '/test_admin/admin/admin_views/modelwithstringprimarykey/'
305+        response = self.client.get(changelist_url)
306+        should_contain = """<th><a href="%s/?_return_to=%s">%s</a></th></tr>""" % (quote(self.pk), changelist_url, escape(self.pk))
307         self.assertContains(response, should_contain)
308 
309     def test_recentactions_link(self):