Fixed #32969 -- Fixed pickling HttpResponse and subclasses.
This commit is contained in:
parent
901a169198
commit
d7f5bfd241
1
AUTHORS
1
AUTHORS
|
@ -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
|
||||||
|
|
|
@ -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__,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue