Skip to content

Commit

Permalink
Fixed #32969 -- Fixed pickling HttpResponse and subclasses.
Browse files Browse the repository at this point in the history
  • Loading branch information
Anv3sh authored and felixxm committed Jun 20, 2022
1 parent 901a169 commit d7f5bfd
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 9 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -95,6 +95,7 @@ answer newbie questions, and generally made Django that much better:
Antti Haapala <antti@industrialwebandmagic.com>
Antti Kaihola <http://djangopeople.net/akaihola/>
Anubhav Joshi <anubhav9042@gmail.com>
Anvesh Mishra <anveshgreat11@gmail.com>
Aram Dulyan
arien <regexbot@gmail.com>
Armin Ronacher
Expand Down
19 changes: 19 additions & 0 deletions django/http/response.py
Expand Up @@ -366,12 +366,31 @@ 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__,
Expand Down
15 changes: 7 additions & 8 deletions django/template/response.py
Expand Up @@ -8,7 +8,9 @@ class ContentNotRenderedError(Exception):


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__(
self,
Expand Down Expand Up @@ -55,16 +57,11 @@ def __getstate__(self):
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."
)
for attr in self.rendering_attrs:
if attr in obj_dict:
del obj_dict[attr]

return obj_dict
return super().__getstate__()

def resolve_template(self, template):
"""Accept a template object, path-to-template, or list of paths."""
Expand Down Expand Up @@ -145,7 +142,9 @@ def content(self, value):


class TemplateResponse(SimpleTemplateResponse):
rendering_attrs = SimpleTemplateResponse.rendering_attrs + ["_request"]
non_picklable_attrs = SimpleTemplateResponse.non_picklable_attrs | frozenset(
["_request"]
)

def __init__(
self,
Expand Down
3 changes: 2 additions & 1 deletion docs/releases/4.2.txt
Expand Up @@ -249,7 +249,8 @@ PostgreSQL 12 and higher.
Miscellaneous
-------------

* ...
* The undocumented ``SimpleTemplateResponse.rendering_attrs`` and
``TemplateResponse.rendering_attrs`` are renamed to ``non_picklable_attrs``.

.. _deprecated-features-4.2:

Expand Down
16 changes: 16 additions & 0 deletions tests/test_client/tests.py
Expand Up @@ -20,6 +20,7 @@
"""
import itertools
import pickle
import tempfile
from unittest import mock

Expand Down Expand Up @@ -80,6 +81,21 @@ def test_get_view(self):
self.assertEqual(response.context["var"], "\xf2")
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):
# WSGI requires latin-1 encoded strings.
response = self.client.get("/get_view/?var=1\ufffd")
Expand Down
1 change: 1 addition & 0 deletions tests/test_client/urls.py
Expand Up @@ -7,6 +7,7 @@
urlpatterns = [
path("upload_view/", views.upload_view, name="upload_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_then_get_view/", views.post_then_get_view),
path("put_view/", views.put_view),
Expand Down
5 changes: 5 additions & 0 deletions tests/test_client/views.py
Expand Up @@ -18,6 +18,7 @@
from django.template import Context, Template
from django.test import Client
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView


def get_view(request):
Expand Down Expand Up @@ -418,3 +419,7 @@ def __init__(self, one, two):

def two_arg_exception(request):
raise TwoArgException("one", "two")


class CBView(TemplateView):
template_name = "base.html"

0 comments on commit d7f5bfd

Please sign in to comment.