From 173034b00589c083793d495e8b07e35be2cb1cf0 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 12 Apr 2023 09:46:18 +0200 Subject: [PATCH] Refs #34482 -- Reverted "Fixed #32969 -- Fixed pickling HttpResponse and subclasses." MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit d7f5bfd241666c0a76e90208da1e9ef81aec44db. Thanks Márton Salomváry for the report. --- django/http/response.py | 19 ------------------- django/template/response.py | 15 ++++++++------- docs/releases/4.2.1.txt | 4 ++-- docs/releases/4.2.txt | 3 --- tests/test_client/tests.py | 22 +++++++++++++--------- 5 files changed, 23 insertions(+), 40 deletions(-) diff --git a/django/http/response.py b/django/http/response.py index 58993801e1..eecd972cd6 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -369,31 +369,12 @@ class HttpResponse(HttpResponseBase): """ streaming = False - non_picklable_attrs = frozenset( - [ - "resolver_match", - # Non-picklable attributes added by test clients. - "asgi_request", - "client", - "context", - "json", - "templates", - "wsgi_request", - ] - ) def __init__(self, content=b"", *args, **kwargs): super().__init__(*args, **kwargs) # Content is a bytestring. See the `content` property methods. self.content = content - def __getstate__(self): - obj_dict = self.__dict__.copy() - for attr in self.non_picklable_attrs: - if attr in obj_dict: - del obj_dict[attr] - return obj_dict - def __repr__(self): return "<%(cls)s status_code=%(status_code)d%(content_type)s>" % { "cls": self.__class__.__name__, diff --git a/django/template/response.py b/django/template/response.py index b4f0e171f1..c38b95e9de 100644 --- a/django/template/response.py +++ b/django/template/response.py @@ -8,9 +8,7 @@ class ContentNotRenderedError(Exception): class SimpleTemplateResponse(HttpResponse): - non_picklable_attrs = HttpResponse.non_picklable_attrs | frozenset( - ["template_name", "context_data", "_post_render_callbacks"] - ) + rendering_attrs = ["template_name", "context_data", "_post_render_callbacks"] def __init__( self, @@ -57,11 +55,16 @@ class SimpleTemplateResponse(HttpResponse): Raise an exception if trying to pickle an unrendered response. Pickle only rendered data, not the data used to construct the response. """ + obj_dict = self.__dict__.copy() if not self._is_rendered: raise ContentNotRenderedError( "The response content must be rendered before it can be pickled." ) - return super().__getstate__() + for attr in self.rendering_attrs: + if attr in obj_dict: + del obj_dict[attr] + + return obj_dict def resolve_template(self, template): """Accept a template object, path-to-template, or list of paths.""" @@ -142,9 +145,7 @@ class SimpleTemplateResponse(HttpResponse): class TemplateResponse(SimpleTemplateResponse): - non_picklable_attrs = SimpleTemplateResponse.non_picklable_attrs | frozenset( - ["_request"] - ) + rendering_attrs = SimpleTemplateResponse.rendering_attrs + ["_request"] def __init__( self, diff --git a/docs/releases/4.2.1.txt b/docs/releases/4.2.1.txt index 621fc3ec30..2bfe21cd47 100644 --- a/docs/releases/4.2.1.txt +++ b/docs/releases/4.2.1.txt @@ -31,5 +31,5 @@ Bugfixes language was used (:ticket:`34455`). * Fixed a regression in Django 4.2 where creating copies and deep copies of - ``HttpRequest`` and its subclasses didn't always work correctly - (:ticket:`34482`, :ticket:`34484`). + ``HttpRequest``, ``HttpResponse``, and their subclasses didn't always work + correctly (:ticket:`34482`, :ticket:`34484`). diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt index a7c20a7d6d..17d0dc956d 100644 --- a/docs/releases/4.2.txt +++ b/docs/releases/4.2.txt @@ -416,9 +416,6 @@ fields modified in the custom ``save()`` methods should be added to the Miscellaneous ------------- -* The undocumented ``SimpleTemplateResponse.rendering_attrs`` and - ``TemplateResponse.rendering_attrs`` are renamed to ``non_picklable_attrs``. - * The undocumented ``django.http.multipartparser.parse_header()`` function is removed. Use ``django.utils.http.parse_header_parameters()`` instead. diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py index 127cc61606..a5e980f3d0 100644 --- a/tests/test_client/tests.py +++ b/tests/test_client/tests.py @@ -19,8 +19,8 @@ testing against the contexts and templates produced by a view, rather than the HTML rendered to the end-user. """ +import copy import itertools -import pickle import tempfile from unittest import mock @@ -81,20 +81,24 @@ class ClientTest(TestCase): self.assertEqual(response.context["var"], "\xf2") self.assertEqual(response.templates[0].name, "GET Template") - def test_pickling_response(self): + def test_copy_response(self): tests = ["/cbv_view/", "/get_view/"] for url in tests: with self.subTest(url=url): response = self.client.get(url) - dump = pickle.dumps(response) - response_from_pickle = pickle.loads(dump) - self.assertEqual(repr(response), repr(response_from_pickle)) + response_copy = copy.copy(response) + self.assertEqual(repr(response), repr(response_copy)) + self.assertIs(response_copy.client, response.client) + self.assertIs(response_copy.resolver_match, response.resolver_match) + self.assertIs(response_copy.wsgi_request, response.wsgi_request) - async def test_pickling_response_async(self): + async def test_copy_response_async(self): response = await self.async_client.get("/async_get_view/") - dump = pickle.dumps(response) - response_from_pickle = pickle.loads(dump) - self.assertEqual(repr(response), repr(response_from_pickle)) + response_copy = copy.copy(response) + self.assertEqual(repr(response), repr(response_copy)) + self.assertIs(response_copy.client, response.client) + self.assertIs(response_copy.resolver_match, response.resolver_match) + self.assertIs(response_copy.asgi_request, response.asgi_request) def test_query_string_encoding(self): # WSGI requires latin-1 encoded strings.