Fixed #32969 -- Fixed pickling HttpResponse and subclasses.

This commit is contained in:
Anv3sh 2022-06-16 21:34:13 +05:30 committed by Mariusz Felisiak
parent 901a169198
commit d7f5bfd241
7 changed files with 51 additions and 9 deletions

View File

@ -95,6 +95,7 @@ answer newbie questions, and generally made Django that much better:
Antti Haapala <antti@industrialwebandmagic.com> Antti Haapala <antti@industrialwebandmagic.com>
Antti Kaihola <http://djangopeople.net/akaihola/> Antti Kaihola <http://djangopeople.net/akaihola/>
Anubhav Joshi <anubhav9042@gmail.com> Anubhav Joshi <anubhav9042@gmail.com>
Anvesh Mishra <anveshgreat11@gmail.com>
Aram Dulyan Aram Dulyan
arien <regexbot@gmail.com> arien <regexbot@gmail.com>
Armin Ronacher Armin Ronacher

View File

@ -366,12 +366,31 @@ class HttpResponse(HttpResponseBase):
""" """
streaming = False 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): def __init__(self, content=b"", *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Content is a bytestring. See the `content` property methods. # Content is a bytestring. See the `content` property methods.
self.content = content 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): def __repr__(self):
return "<%(cls)s status_code=%(status_code)d%(content_type)s>" % { return "<%(cls)s status_code=%(status_code)d%(content_type)s>" % {
"cls": self.__class__.__name__, "cls": self.__class__.__name__,

View File

@ -8,7 +8,9 @@ class ContentNotRenderedError(Exception):
class SimpleTemplateResponse(HttpResponse): class SimpleTemplateResponse(HttpResponse):
rendering_attrs = ["template_name", "context_data", "_post_render_callbacks"] non_picklable_attrs = HttpResponse.non_picklable_attrs | frozenset(
["template_name", "context_data", "_post_render_callbacks"]
)
def __init__( def __init__(
self, self,
@ -55,16 +57,11 @@ class SimpleTemplateResponse(HttpResponse):
Raise an exception if trying to pickle an unrendered response. Pickle Raise an exception if trying to pickle an unrendered response. Pickle
only rendered data, not the data used to construct the response. only rendered data, not the data used to construct the response.
""" """
obj_dict = self.__dict__.copy()
if not self._is_rendered: if not self._is_rendered:
raise ContentNotRenderedError( raise ContentNotRenderedError(
"The response content must be rendered before it can be pickled." "The response content must be rendered before it can be pickled."
) )
for attr in self.rendering_attrs: return super().__getstate__()
if attr in obj_dict:
del obj_dict[attr]
return obj_dict
def resolve_template(self, template): def resolve_template(self, template):
"""Accept a template object, path-to-template, or list of paths.""" """Accept a template object, path-to-template, or list of paths."""
@ -145,7 +142,9 @@ class SimpleTemplateResponse(HttpResponse):
class TemplateResponse(SimpleTemplateResponse): class TemplateResponse(SimpleTemplateResponse):
rendering_attrs = SimpleTemplateResponse.rendering_attrs + ["_request"] non_picklable_attrs = SimpleTemplateResponse.non_picklable_attrs | frozenset(
["_request"]
)
def __init__( def __init__(
self, self,

View File

@ -249,7 +249,8 @@ PostgreSQL 12 and higher.
Miscellaneous Miscellaneous
------------- -------------
* ... * The undocumented ``SimpleTemplateResponse.rendering_attrs`` and
``TemplateResponse.rendering_attrs`` are renamed to ``non_picklable_attrs``.
.. _deprecated-features-4.2: .. _deprecated-features-4.2:

View File

@ -20,6 +20,7 @@ rather than the HTML rendered to the end-user.
""" """
import itertools import itertools
import pickle
import tempfile import tempfile
from unittest import mock from unittest import mock
@ -80,6 +81,21 @@ class ClientTest(TestCase):
self.assertEqual(response.context["var"], "\xf2") self.assertEqual(response.context["var"], "\xf2")
self.assertEqual(response.templates[0].name, "GET Template") self.assertEqual(response.templates[0].name, "GET Template")
def test_pickling_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))
async def test_pickling_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))
def test_query_string_encoding(self): def test_query_string_encoding(self):
# WSGI requires latin-1 encoded strings. # WSGI requires latin-1 encoded strings.
response = self.client.get("/get_view/?var=1\ufffd") response = self.client.get("/get_view/?var=1\ufffd")

View File

@ -7,6 +7,7 @@ from . import views
urlpatterns = [ urlpatterns = [
path("upload_view/", views.upload_view, name="upload_view"), path("upload_view/", views.upload_view, name="upload_view"),
path("get_view/", views.get_view, name="get_view"), path("get_view/", views.get_view, name="get_view"),
path("cbv_view/", views.CBView.as_view()),
path("post_view/", views.post_view), path("post_view/", views.post_view),
path("post_then_get_view/", views.post_then_get_view), path("post_then_get_view/", views.post_then_get_view),
path("put_view/", views.put_view), path("put_view/", views.put_view),

View File

@ -18,6 +18,7 @@ from django.shortcuts import render
from django.template import Context, Template from django.template import Context, Template
from django.test import Client from django.test import Client
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
def get_view(request): def get_view(request):
@ -418,3 +419,7 @@ class TwoArgException(Exception):
def two_arg_exception(request): def two_arg_exception(request):
raise TwoArgException("one", "two") raise TwoArgException("one", "two")
class CBView(TemplateView):
template_name = "base.html"