mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	[5.0.x] Fixed #12241 -- Preserved query strings when using "Save and continue/add another" in admin.
Co-authored-by: Grady Yu <gradyy@users.noreply.github.com>
Co-authored-by: David Sanders <shang.xiao.sanders@gmail.com>
Co-authored-by: Matthew Newton <matthewn@berkeley.edu>
Backport of fc62e17778 from main
			
			
This commit is contained in:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							bcc6a8ee88
						
					
				
				
					commit
					0bbe6ca2ac
				
			| @@ -3,7 +3,9 @@ import enum | |||||||
| import json | import json | ||||||
| import re | import re | ||||||
| from functools import partial, update_wrapper | from functools import partial, update_wrapper | ||||||
|  | from urllib.parse import parse_qsl | ||||||
| from urllib.parse import quote as urlquote | from urllib.parse import quote as urlquote | ||||||
|  | from urllib.parse import urlparse | ||||||
|  |  | ||||||
| from django import forms | from django import forms | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| @@ -1346,12 +1348,17 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|             context, |             context, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |     def _get_preserved_qsl(self, request, preserved_filters): | ||||||
|  |         query_string = urlparse(request.build_absolute_uri()).query | ||||||
|  |         return parse_qsl(query_string.replace(preserved_filters, "")) | ||||||
|  |  | ||||||
|     def response_add(self, request, obj, post_url_continue=None): |     def response_add(self, request, obj, post_url_continue=None): | ||||||
|         """ |         """ | ||||||
|         Determine the HttpResponse for the add_view stage. |         Determine the HttpResponse for the add_view stage. | ||||||
|         """ |         """ | ||||||
|         opts = obj._meta |         opts = obj._meta | ||||||
|         preserved_filters = self.get_preserved_filters(request) |         preserved_filters = self.get_preserved_filters(request) | ||||||
|  |         preserved_qsl = self._get_preserved_qsl(request, preserved_filters) | ||||||
|         obj_url = reverse( |         obj_url = reverse( | ||||||
|             "admin:%s_%s_change" % (opts.app_label, opts.model_name), |             "admin:%s_%s_change" % (opts.app_label, opts.model_name), | ||||||
|             args=(quote(obj.pk),), |             args=(quote(obj.pk),), | ||||||
| @@ -1409,7 +1416,11 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|             if post_url_continue is None: |             if post_url_continue is None: | ||||||
|                 post_url_continue = obj_url |                 post_url_continue = obj_url | ||||||
|             post_url_continue = add_preserved_filters( |             post_url_continue = add_preserved_filters( | ||||||
|                 {"preserved_filters": preserved_filters, "opts": opts}, |                 { | ||||||
|  |                     "preserved_filters": preserved_filters, | ||||||
|  |                     "preserved_qsl": preserved_qsl, | ||||||
|  |                     "opts": opts, | ||||||
|  |                 }, | ||||||
|                 post_url_continue, |                 post_url_continue, | ||||||
|             ) |             ) | ||||||
|             return HttpResponseRedirect(post_url_continue) |             return HttpResponseRedirect(post_url_continue) | ||||||
| @@ -1425,7 +1436,12 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|             self.message_user(request, msg, messages.SUCCESS) |             self.message_user(request, msg, messages.SUCCESS) | ||||||
|             redirect_url = request.path |             redirect_url = request.path | ||||||
|             redirect_url = add_preserved_filters( |             redirect_url = add_preserved_filters( | ||||||
|                 {"preserved_filters": preserved_filters, "opts": opts}, redirect_url |                 { | ||||||
|  |                     "preserved_filters": preserved_filters, | ||||||
|  |                     "preserved_qsl": preserved_qsl, | ||||||
|  |                     "opts": opts, | ||||||
|  |                 }, | ||||||
|  |                 redirect_url, | ||||||
|             ) |             ) | ||||||
|             return HttpResponseRedirect(redirect_url) |             return HttpResponseRedirect(redirect_url) | ||||||
|  |  | ||||||
| @@ -1471,6 +1487,7 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|  |  | ||||||
|         opts = self.opts |         opts = self.opts | ||||||
|         preserved_filters = self.get_preserved_filters(request) |         preserved_filters = self.get_preserved_filters(request) | ||||||
|  |         preserved_qsl = self._get_preserved_qsl(request, preserved_filters) | ||||||
|  |  | ||||||
|         msg_dict = { |         msg_dict = { | ||||||
|             "name": opts.verbose_name, |             "name": opts.verbose_name, | ||||||
| @@ -1487,7 +1504,12 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|             self.message_user(request, msg, messages.SUCCESS) |             self.message_user(request, msg, messages.SUCCESS) | ||||||
|             redirect_url = request.path |             redirect_url = request.path | ||||||
|             redirect_url = add_preserved_filters( |             redirect_url = add_preserved_filters( | ||||||
|                 {"preserved_filters": preserved_filters, "opts": opts}, redirect_url |                 { | ||||||
|  |                     "preserved_filters": preserved_filters, | ||||||
|  |                     "preserved_qsl": preserved_qsl, | ||||||
|  |                     "opts": opts, | ||||||
|  |                 }, | ||||||
|  |                 redirect_url, | ||||||
|             ) |             ) | ||||||
|             return HttpResponseRedirect(redirect_url) |             return HttpResponseRedirect(redirect_url) | ||||||
|  |  | ||||||
| @@ -1524,7 +1546,12 @@ class ModelAdmin(BaseModelAdmin): | |||||||
|                 current_app=self.admin_site.name, |                 current_app=self.admin_site.name, | ||||||
|             ) |             ) | ||||||
|             redirect_url = add_preserved_filters( |             redirect_url = add_preserved_filters( | ||||||
|                 {"preserved_filters": preserved_filters, "opts": opts}, redirect_url |                 { | ||||||
|  |                     "preserved_filters": preserved_filters, | ||||||
|  |                     "preserved_qsl": preserved_qsl, | ||||||
|  |                     "opts": opts, | ||||||
|  |                 }, | ||||||
|  |                 redirect_url, | ||||||
|             ) |             ) | ||||||
|             return HttpResponseRedirect(redirect_url) |             return HttpResponseRedirect(redirect_url) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,11 +22,15 @@ def admin_urlquote(value): | |||||||
| def add_preserved_filters(context, url, popup=False, to_field=None): | def add_preserved_filters(context, url, popup=False, to_field=None): | ||||||
|     opts = context.get("opts") |     opts = context.get("opts") | ||||||
|     preserved_filters = context.get("preserved_filters") |     preserved_filters = context.get("preserved_filters") | ||||||
|  |     preserved_qsl = context.get("preserved_qsl") | ||||||
|  |  | ||||||
|     parsed_url = list(urlparse(url)) |     parsed_url = list(urlparse(url)) | ||||||
|     parsed_qs = dict(parse_qsl(parsed_url[4])) |     parsed_qs = dict(parse_qsl(parsed_url[4])) | ||||||
|     merged_qs = {} |     merged_qs = {} | ||||||
|  |  | ||||||
|  |     if preserved_qsl: | ||||||
|  |         merged_qs.update(preserved_qsl) | ||||||
|  |  | ||||||
|     if opts and preserved_filters: |     if opts and preserved_filters: | ||||||
|         preserved_filters = dict(parse_qsl(preserved_filters)) |         preserved_filters = dict(parse_qsl(preserved_filters)) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -328,6 +328,66 @@ class AdminViewBasicTest(AdminViewBasicTestCase): | |||||||
|             msg_prefix="Couldn't find an input with the right value in the response", |             msg_prefix="Couldn't find an input with the right value in the response", | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |     def test_add_query_string_persists(self): | ||||||
|  |         save_options = [ | ||||||
|  |             {"_addanother": "1"},  # "Save and add another". | ||||||
|  |             {"_continue": "1"},  # "Save and continue editing". | ||||||
|  |             {"_saveasnew": "1"},  # "Save as new". | ||||||
|  |         ] | ||||||
|  |         other_options = [ | ||||||
|  |             "", | ||||||
|  |             "_changelist_filters=is_staff__exact%3D0", | ||||||
|  |             f"{IS_POPUP_VAR}=1", | ||||||
|  |             f"{TO_FIELD_VAR}=id", | ||||||
|  |         ] | ||||||
|  |         url = reverse("admin:auth_user_add") | ||||||
|  |         for i, save_option in enumerate(save_options): | ||||||
|  |             for j, other_option in enumerate(other_options): | ||||||
|  |                 with self.subTest(save_option=save_option, other_option=other_option): | ||||||
|  |                     qsl = "username=newuser" | ||||||
|  |                     if other_option: | ||||||
|  |                         qsl = f"{qsl}&{other_option}" | ||||||
|  |                     response = self.client.post( | ||||||
|  |                         f"{url}?{qsl}", | ||||||
|  |                         { | ||||||
|  |                             "username": f"newuser{i}{j}", | ||||||
|  |                             "password1": "newpassword", | ||||||
|  |                             "password2": "newpassword", | ||||||
|  |                             **save_option, | ||||||
|  |                         }, | ||||||
|  |                     ) | ||||||
|  |                     parsed_url = urlparse(response.url) | ||||||
|  |                     self.assertEqual(parsed_url.query, qsl) | ||||||
|  |  | ||||||
|  |     def test_change_query_string_persists(self): | ||||||
|  |         save_options = [ | ||||||
|  |             {"_addanother": "1"},  # "Save and add another". | ||||||
|  |             {"_continue": "1"},  # "Save and continue editing". | ||||||
|  |         ] | ||||||
|  |         other_options = [ | ||||||
|  |             "", | ||||||
|  |             "_changelist_filters=warm%3D1", | ||||||
|  |             f"{IS_POPUP_VAR}=1", | ||||||
|  |             f"{TO_FIELD_VAR}=id", | ||||||
|  |         ] | ||||||
|  |         url = reverse("admin:admin_views_color_change", args=(self.color1.pk,)) | ||||||
|  |         for save_option in save_options: | ||||||
|  |             for other_option in other_options: | ||||||
|  |                 with self.subTest(save_option=save_option, other_option=other_option): | ||||||
|  |                     qsl = "value=blue" | ||||||
|  |                     if other_option: | ||||||
|  |                         qsl = f"{qsl}&{other_option}" | ||||||
|  |                     response = self.client.post( | ||||||
|  |                         f"{url}?{qsl}", | ||||||
|  |                         { | ||||||
|  |                             "value": "gold", | ||||||
|  |                             "warm": True, | ||||||
|  |                             **save_option, | ||||||
|  |                         }, | ||||||
|  |                     ) | ||||||
|  |                     parsed_url = urlparse(response.url) | ||||||
|  |                     self.assertEqual(parsed_url.query, qsl) | ||||||
|  |  | ||||||
|     def test_basic_edit_GET(self): |     def test_basic_edit_GET(self): | ||||||
|         """ |         """ | ||||||
|         A smoke test to ensure GET on the change_view works. |         A smoke test to ensure GET on the change_view works. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user