diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index c071b5ca8c3..27711d39099 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -54,9 +54,6 @@ class LimitedStream(IOBase): class WSGIRequest(HttpRequest): - non_picklable_attrs = HttpRequest.non_picklable_attrs | frozenset(["environ"]) - meta_non_picklable_attrs = frozenset(["wsgi.errors", "wsgi.input"]) - def __init__(self, environ): script_name = get_script_name(environ) # If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a @@ -82,13 +79,6 @@ class WSGIRequest(HttpRequest): self._read_started = False self.resolver_match = None - def __getstate__(self): - state = super().__getstate__() - for attr in self.meta_non_picklable_attrs: - if attr in state["META"]: - del state["META"][attr] - return state - def _get_scheme(self): return self.environ.get("wsgi.url_scheme") diff --git a/django/http/request.py b/django/http/request.py index 1cfa0268704..4d1d077ec19 100644 --- a/django/http/request.py +++ b/django/http/request.py @@ -55,8 +55,6 @@ class HttpRequest: _encoding = None _upload_handlers = [] - non_picklable_attrs = frozenset(["resolver_match", "_stream"]) - def __init__(self): # WARNING: The `WSGIRequest` subclass doesn't call `super`. # Any variable assignment made here should also happen in @@ -84,21 +82,6 @@ class HttpRequest: self.get_full_path(), ) - 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 __deepcopy__(self, memo): - obj = copy.copy(self) - for attr in self.non_picklable_attrs: - if hasattr(self, attr): - setattr(obj, attr, copy.deepcopy(getattr(self, attr), memo)) - memo[id(self)] = obj - return obj - @cached_property def headers(self): return HttpHeaders(self.META) diff --git a/django/http/response.py b/django/http/response.py index 465a8553dc6..090be181e21 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -373,10 +373,12 @@ class HttpResponse(HttpResponseBase): [ "resolver_match", # Non-picklable attributes added by test clients. + "asgi_request", "client", "context", "json", "templates", + "wsgi_request", ] ) diff --git a/docs/releases/4.2.1.txt b/docs/releases/4.2.1.txt index 525830a01bd..621fc3ec303 100644 --- a/docs/releases/4.2.1.txt +++ b/docs/releases/4.2.1.txt @@ -29,3 +29,7 @@ Bugfixes * Fixed a regression in Django 4.2 where ``i18n_patterns()`` didn't respect the ``prefix_default_language`` argument when a fallback language of the default 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`). diff --git a/tests/requests_tests/tests.py b/tests/requests_tests/tests.py index ef218afe2f0..d3e9e662246 100644 --- a/tests/requests_tests/tests.py +++ b/tests/requests_tests/tests.py @@ -1,4 +1,4 @@ -import pickle +import copy from io import BytesIO from itertools import chain from urllib.parse import urlencode @@ -233,6 +233,11 @@ class RequestsTests(SimpleTestCase): # left percent-encoded in the path. self.assertEqual(request.path, "/caf%E9/") + def test_wsgirequest_copy(self): + request = WSGIRequest({"REQUEST_METHOD": "get", "wsgi.input": BytesIO(b"")}) + request_copy = copy.copy(request) + self.assertIs(request_copy.environ, request.environ) + def test_limited_stream(self): # Read all of a limited stream stream = LimitedStream(BytesIO(b"test"), 2) @@ -687,19 +692,17 @@ class RequestsTests(SimpleTestCase): with self.assertRaises(UnreadablePostError): request.FILES - def test_pickling_request(self): + def test_copy(self): request = HttpRequest() - request.method = "GET" - request.path = "/testpath/" - request.META = { - "QUERY_STRING": ";some=query&+query=string", - "SERVER_NAME": "example.com", - "SERVER_PORT": 80, - } - request.COOKIES = {"post-key": "post-value"} - dump = pickle.dumps(request) - request_from_pickle = pickle.loads(dump) - self.assertEqual(repr(request), repr(request_from_pickle)) + request_copy = copy.copy(request) + self.assertIs(request_copy.resolver_match, request.resolver_match) + + def test_deepcopy(self): + request = RequestFactory().get("/") + request.session = {} + request_copy = copy.deepcopy(request) + request.session["key"] = "value" + self.assertEqual(request_copy.session, {}) class HostValidationTests(SimpleTestCase):