Forráskód Böngészése

Fixed #32969 -- Fixed pickling HttpResponse and subclasses.

Anv3sh 2 éve
szülő
commit
d7f5bfd241

+ 1 - 0
AUTHORS

@@ -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

+ 19 - 0
django/http/response.py

@@ -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__,

+ 7 - 8
django/template/response.py

@@ -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,
@@ -55,16 +57,11 @@ 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."
             )
-        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."""
@@ -145,7 +142,9 @@ class SimpleTemplateResponse(HttpResponse):
 
 
 class TemplateResponse(SimpleTemplateResponse):
-    rendering_attrs = SimpleTemplateResponse.rendering_attrs + ["_request"]
+    non_picklable_attrs = SimpleTemplateResponse.non_picklable_attrs | frozenset(
+        ["_request"]
+    )
 
     def __init__(
         self,

+ 2 - 1
docs/releases/4.2.txt

@@ -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:
 

+ 16 - 0
tests/test_client/tests.py

@@ -20,6 +20,7 @@ rather than the HTML rendered to the end-user.
 
 """
 import itertools
+import pickle
 import tempfile
 from unittest import mock
 
@@ -80,6 +81,21 @@ class ClientTest(TestCase):
         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")

+ 1 - 0
tests/test_client/urls.py

@@ -7,6 +7,7 @@ from . import views
 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),

+ 5 - 0
tests/test_client/views.py

@@ -18,6 +18,7 @@ from django.shortcuts import render
 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):
@@ -418,3 +419,7 @@ class TwoArgException(Exception):
 
 def two_arg_exception(request):
     raise TwoArgException("one", "two")
+
+
+class CBView(TemplateView):
+    template_name = "base.html"