diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 12487dcc9b..75c256963b 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1785,7 +1785,7 @@ class ModelAdmin(BaseModelAdmin): } if request.method == 'POST': formset_params.update({ - 'data': request.POST, + 'data': request.POST.copy(), 'files': request.FILES, 'save_as_new': '_saveasnew' in request.POST }) diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py index f84652f8b4..f3c69023be 100644 --- a/django/contrib/auth/admin.py +++ b/django/contrib/auth/admin.py @@ -205,6 +205,7 @@ class UserAdmin(admin.ModelAdmin): # * The user has pressed the 'Save and add another' button # * We are adding a user in a popup if '_addanother' not in request.POST and IS_POPUP_VAR not in request.POST: + request.POST = request.POST.copy() request.POST['_continue'] = 1 return super(UserAdmin, self).response_add(request, obj, post_url_continue) diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index 87d80bdde5..616185f021 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -289,6 +289,7 @@ class MultiPartParser(object): if retval: break + self._post._mutable = False return self._post, self._files def handle_file_complete(self, old_field_name, counters): diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 7bf206c678..59b7fb54f9 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -634,6 +634,10 @@ Miscellaneous * The undocumented ``django.utils.functional.lazy_property`` is removed. +* For consistency with non-multipart requests, ``MultiPartParser.parse()`` now + leaves ``request.POST`` immutable. If you're modifying that ``QueryDict``, + you must now first copy it, e.g. ``request.POST.copy()``. + .. _deprecated-features-1.11: Features deprecated in 1.11 diff --git a/tests/requests/tests.py b/tests/requests/tests.py index 0942619715..27dc439e5f 100644 --- a/tests/requests/tests.py +++ b/tests/requests/tests.py @@ -533,6 +533,25 @@ class RequestsTests(SimpleTestCase): self.assertEqual(request.read(13), b'--boundary\r\nC') self.assertEqual(request.POST, {'name': ['value']}) + def test_POST_immutable_for_mutipart(self): + """ + MultiPartParser.parse() leaves request.POST immutable. + """ + payload = FakePayload("\r\n".join([ + '--boundary', + 'Content-Disposition: form-data; name="name"', + '', + 'value', + '--boundary--', + ])) + request = WSGIRequest({ + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', + 'CONTENT_LENGTH': len(payload), + 'wsgi.input': payload, + }) + self.assertFalse(request.POST._mutable) + def test_POST_connection_error(self): """ If wsgi.input.read() raises an exception while trying to read() the