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 re | ||||
| from functools import partial, update_wrapper | ||||
| from urllib.parse import parse_qsl | ||||
| from urllib.parse import quote as urlquote | ||||
| from urllib.parse import urlparse | ||||
|  | ||||
| from django import forms | ||||
| from django.conf import settings | ||||
| @@ -1346,12 +1348,17 @@ class ModelAdmin(BaseModelAdmin): | ||||
|             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): | ||||
|         """ | ||||
|         Determine the HttpResponse for the add_view stage. | ||||
|         """ | ||||
|         opts = obj._meta | ||||
|         preserved_filters = self.get_preserved_filters(request) | ||||
|         preserved_qsl = self._get_preserved_qsl(request, preserved_filters) | ||||
|         obj_url = reverse( | ||||
|             "admin:%s_%s_change" % (opts.app_label, opts.model_name), | ||||
|             args=(quote(obj.pk),), | ||||
| @@ -1409,7 +1416,11 @@ class ModelAdmin(BaseModelAdmin): | ||||
|             if post_url_continue is None: | ||||
|                 post_url_continue = obj_url | ||||
|             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, | ||||
|             ) | ||||
|             return HttpResponseRedirect(post_url_continue) | ||||
| @@ -1425,7 +1436,12 @@ class ModelAdmin(BaseModelAdmin): | ||||
|             self.message_user(request, msg, messages.SUCCESS) | ||||
|             redirect_url = request.path | ||||
|             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) | ||||
|  | ||||
| @@ -1471,6 +1487,7 @@ class ModelAdmin(BaseModelAdmin): | ||||
|  | ||||
|         opts = self.opts | ||||
|         preserved_filters = self.get_preserved_filters(request) | ||||
|         preserved_qsl = self._get_preserved_qsl(request, preserved_filters) | ||||
|  | ||||
|         msg_dict = { | ||||
|             "name": opts.verbose_name, | ||||
| @@ -1487,7 +1504,12 @@ class ModelAdmin(BaseModelAdmin): | ||||
|             self.message_user(request, msg, messages.SUCCESS) | ||||
|             redirect_url = request.path | ||||
|             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) | ||||
|  | ||||
| @@ -1524,7 +1546,12 @@ class ModelAdmin(BaseModelAdmin): | ||||
|                 current_app=self.admin_site.name, | ||||
|             ) | ||||
|             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) | ||||
|  | ||||
|   | ||||
| @@ -22,11 +22,15 @@ def admin_urlquote(value): | ||||
| def add_preserved_filters(context, url, popup=False, to_field=None): | ||||
|     opts = context.get("opts") | ||||
|     preserved_filters = context.get("preserved_filters") | ||||
|     preserved_qsl = context.get("preserved_qsl") | ||||
|  | ||||
|     parsed_url = list(urlparse(url)) | ||||
|     parsed_qs = dict(parse_qsl(parsed_url[4])) | ||||
|     merged_qs = {} | ||||
|  | ||||
|     if preserved_qsl: | ||||
|         merged_qs.update(preserved_qsl) | ||||
|  | ||||
|     if opts and 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", | ||||
|         ) | ||||
|  | ||||
|     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): | ||||
|         """ | ||||
|         A smoke test to ensure GET on the change_view works. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user