diff --git a/django/core/handlers/asgi.py b/django/core/handlers/asgi.py index 7b0086fb76..46945b41e6 100644 --- a/django/core/handlers/asgi.py +++ b/django/core/handlers/asgi.py @@ -19,6 +19,7 @@ from django.http import ( HttpResponseServerError, QueryDict, parse_cookie, + parsers, ) from django.urls import set_script_prefix from django.utils.functional import cached_property @@ -111,6 +112,10 @@ class ASGIRequest(HttpRequest): self._stream = body_file # Other bits. self.resolver_match = None + self._parsers = [ + parsers.FormParser(), + parsers.MultiPartParser(), + ] @cached_property def GET(self): diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 9324af083e..5a372625e2 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -3,7 +3,7 @@ from io import IOBase from django.conf import settings from django.core import signals from django.core.handlers import base -from django.http import HttpRequest, QueryDict, parse_cookie +from django.http import HttpRequest, QueryDict, parse_cookie, parsers from django.urls import set_script_prefix from django.utils.encoding import repercent_broken_unicode from django.utils.functional import cached_property @@ -78,6 +78,10 @@ class WSGIRequest(HttpRequest): self._stream = LimitedStream(self.environ["wsgi.input"], content_length) self._read_started = False self.resolver_match = None + self._parsers = [ + parsers.FormParser(), + parsers.MultiPartParser(), + ] def _get_scheme(self): return self.environ.get("wsgi.url_scheme") diff --git a/django/http/request.py b/django/http/request.py index 118cdaeab7..bc9662282e 100644 --- a/django/http/request.py +++ b/django/http/request.py @@ -69,6 +69,10 @@ class HttpRequest: self.resolver_match = None self.content_type = None self.content_params = None + self._parsers = [ + parsers.FormParser(), + parsers.MultiPartParser(), + ] def __repr__(self): if self.method is None or not self.get_full_path(): @@ -417,6 +421,19 @@ class HttpRequest: def readlines(self): return list(self) + @property + def parsers(self): + return self._parsers + + @parsers.setter + def parsers(self, parsers): + # TODO Also check for _data once added + if hasattr(self, "_files"): + raise AttributeError( + "You cannot change parsers after processing the request's content." + ) + self._parsers = parsers + class HttpHeaders(CaseInsensitiveMapping): HTTP_PREFIX = "HTTP_" diff --git a/docs/ref/parsers.txt b/docs/ref/parsers.txt index 5735bf4b1a..fd732f51e5 100644 --- a/docs/ref/parsers.txt +++ b/docs/ref/parsers.txt @@ -56,3 +56,20 @@ Parses HTML form content (). The ``parse()`` method returns a Parses multipart form content and supports file uploads. The method returns a ``QueryDict`` for ``data`` and an ``MultiValueDict`` for ``FILES``. + +HttpRequest.Parsers +=================== + +``HttpRequest.parsers`` returns a list of parsers to be used when parsing a +request's content. By default, the parsers list will be Django's included +parsers (``FormParser, ``MultiPartParser``). + +Parsers can be customised by setting a list of parsers to be used on the +request. This could be in a middleware or in a view, but must be done before +``data`` or ``FILES`` is accessed. For example: + +.. code-block:: python + + def index(request): + request.parsers = [MyCustomParser(), FormParser(), ...] + ... diff --git a/tests/requests_tests/test_parsers.py b/tests/requests_tests/test_parsers.py index 5a1d5653fc..8f23ec3988 100644 --- a/tests/requests_tests/test_parsers.py +++ b/tests/requests_tests/test_parsers.py @@ -1,9 +1,12 @@ -from unittest import TestCase - +from django.core.handlers.wsgi import WSGIRequest +from django.http import HttpRequest from django.http.parsers import BaseParser, FormParser, MultiPartParser +from django.test import SimpleTestCase +from django.test.client import FakePayload +from django.utils.http import urlencode -class TestParsers(TestCase): +class TestParsers(SimpleTestCase): def test_can_handle(self): parser = MultiPartParser() self.assertIs(parser.can_handle("multipart/form-data"), True) @@ -25,3 +28,34 @@ class TestParsers(TestCase): self.assertIs(parser.can_handle("application/json"), False) self.assertTrue(parser.can_handle("text/*"), True) self.assertTrue(parser.can_handle("text/csv"), True) + + def test_request_parser_no_setting(self): + request = HttpRequest() + form, multipart = request.parsers + self.assertIsInstance(form, FormParser) + self.assertIsInstance(multipart, MultiPartParser) + + def test_set_parser(self): + request = HttpRequest() + request.parsers = [FormParser()] + + self.assertEqual(len(request.parsers), 1) + self.assertIsInstance(request.parsers[0], FormParser) + + def test_set_parsers_following_files_access(self): + payload = FakePayload(urlencode({"key": "value"})) + request = WSGIRequest( + { + "REQUEST_METHOD": "POST", + "CONTENT_LENGTH": len(payload), + "CONTENT_TYPE": "application/x-www-form-urlencoded", + "wsgi.input": payload, + } + ) + # can set parsers + request.parsers = [] + # access files + request.FILES + msg = "You cannot change parsers after processing the request's content." + with self.assertRaisesMessage(AttributeError, msg): + request.parsers = []