mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	Fixed #14611 -- Added query_params argument to RequestFactory and Client classes.
This commit is contained in:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							e76cc93b01
						
					
				
				
					commit
					a03593967f
				
			| @@ -381,13 +381,22 @@ class RequestFactory: | |||||||
|     just as if that view had been hooked up using a URLconf. |     just as if that view had been hooked up using a URLconf. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, *, json_encoder=DjangoJSONEncoder, headers=None, **defaults): |     def __init__( | ||||||
|  |         self, | ||||||
|  |         *, | ||||||
|  |         json_encoder=DjangoJSONEncoder, | ||||||
|  |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|  |         **defaults, | ||||||
|  |     ): | ||||||
|         self.json_encoder = json_encoder |         self.json_encoder = json_encoder | ||||||
|         self.defaults = defaults |         self.defaults = defaults | ||||||
|         self.cookies = SimpleCookie() |         self.cookies = SimpleCookie() | ||||||
|         self.errors = BytesIO() |         self.errors = BytesIO() | ||||||
|         if headers: |         if headers: | ||||||
|             self.defaults.update(HttpHeaders.to_wsgi_names(headers)) |             self.defaults.update(HttpHeaders.to_wsgi_names(headers)) | ||||||
|  |         if query_params: | ||||||
|  |             self.defaults["QUERY_STRING"] = urlencode(query_params, doseq=True) | ||||||
|  |  | ||||||
|     def _base_environ(self, **request): |     def _base_environ(self, **request): | ||||||
|         """ |         """ | ||||||
| @@ -459,18 +468,21 @@ class RequestFactory: | |||||||
|         # Refs comment in `get_bytes_from_wsgi()`. |         # Refs comment in `get_bytes_from_wsgi()`. | ||||||
|         return path.decode("iso-8859-1") |         return path.decode("iso-8859-1") | ||||||
|  |  | ||||||
|     def get(self, path, data=None, secure=False, *, headers=None, **extra): |     def get( | ||||||
|  |         self, path, data=None, secure=False, *, headers=None, query_params=None, **extra | ||||||
|  |     ): | ||||||
|         """Construct a GET request.""" |         """Construct a GET request.""" | ||||||
|         data = {} if data is None else data |         if query_params and data: | ||||||
|  |             raise ValueError("query_params and data arguments are mutually exclusive.") | ||||||
|  |         query_params = data or query_params | ||||||
|  |         query_params = {} if query_params is None else query_params | ||||||
|         return self.generic( |         return self.generic( | ||||||
|             "GET", |             "GET", | ||||||
|             path, |             path, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|             **{ |             query_params=query_params, | ||||||
|                 "QUERY_STRING": urlencode(data, doseq=True), |  | ||||||
|             **extra, |             **extra, | ||||||
|             }, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def post( |     def post( | ||||||
| @@ -481,6 +493,7 @@ class RequestFactory: | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Construct a POST request.""" |         """Construct a POST request.""" | ||||||
| @@ -494,26 +507,37 @@ class RequestFactory: | |||||||
|             content_type, |             content_type, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def head(self, path, data=None, secure=False, *, headers=None, **extra): |     def head( | ||||||
|  |         self, path, data=None, secure=False, *, headers=None, query_params=None, **extra | ||||||
|  |     ): | ||||||
|         """Construct a HEAD request.""" |         """Construct a HEAD request.""" | ||||||
|         data = {} if data is None else data |         if query_params and data: | ||||||
|  |             raise ValueError("query_params and data arguments are mutually exclusive.") | ||||||
|  |         query_params = data or query_params | ||||||
|  |         query_params = {} if query_params is None else query_params | ||||||
|         return self.generic( |         return self.generic( | ||||||
|             "HEAD", |             "HEAD", | ||||||
|             path, |             path, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|             **{ |             query_params=query_params, | ||||||
|                 "QUERY_STRING": urlencode(data, doseq=True), |  | ||||||
|             **extra, |             **extra, | ||||||
|             }, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def trace(self, path, secure=False, *, headers=None, **extra): |     def trace(self, path, secure=False, *, headers=None, query_params=None, **extra): | ||||||
|         """Construct a TRACE request.""" |         """Construct a TRACE request.""" | ||||||
|         return self.generic("TRACE", path, secure=secure, headers=headers, **extra) |         return self.generic( | ||||||
|  |             "TRACE", | ||||||
|  |             path, | ||||||
|  |             secure=secure, | ||||||
|  |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|  |             **extra, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def options( |     def options( | ||||||
|         self, |         self, | ||||||
| @@ -523,11 +547,19 @@ class RequestFactory: | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         "Construct an OPTIONS request." |         "Construct an OPTIONS request." | ||||||
|         return self.generic( |         return self.generic( | ||||||
|             "OPTIONS", path, data, content_type, secure=secure, headers=headers, **extra |             "OPTIONS", | ||||||
|  |             path, | ||||||
|  |             data, | ||||||
|  |             content_type, | ||||||
|  |             secure=secure, | ||||||
|  |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|  |             **extra, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def put( |     def put( | ||||||
| @@ -538,12 +570,20 @@ class RequestFactory: | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Construct a PUT request.""" |         """Construct a PUT request.""" | ||||||
|         data = self._encode_json(data, content_type) |         data = self._encode_json(data, content_type) | ||||||
|         return self.generic( |         return self.generic( | ||||||
|             "PUT", path, data, content_type, secure=secure, headers=headers, **extra |             "PUT", | ||||||
|  |             path, | ||||||
|  |             data, | ||||||
|  |             content_type, | ||||||
|  |             secure=secure, | ||||||
|  |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|  |             **extra, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def patch( |     def patch( | ||||||
| @@ -554,12 +594,20 @@ class RequestFactory: | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Construct a PATCH request.""" |         """Construct a PATCH request.""" | ||||||
|         data = self._encode_json(data, content_type) |         data = self._encode_json(data, content_type) | ||||||
|         return self.generic( |         return self.generic( | ||||||
|             "PATCH", path, data, content_type, secure=secure, headers=headers, **extra |             "PATCH", | ||||||
|  |             path, | ||||||
|  |             data, | ||||||
|  |             content_type, | ||||||
|  |             secure=secure, | ||||||
|  |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|  |             **extra, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def delete( |     def delete( | ||||||
| @@ -570,12 +618,20 @@ class RequestFactory: | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Construct a DELETE request.""" |         """Construct a DELETE request.""" | ||||||
|         data = self._encode_json(data, content_type) |         data = self._encode_json(data, content_type) | ||||||
|         return self.generic( |         return self.generic( | ||||||
|             "DELETE", path, data, content_type, secure=secure, headers=headers, **extra |             "DELETE", | ||||||
|  |             path, | ||||||
|  |             data, | ||||||
|  |             content_type, | ||||||
|  |             secure=secure, | ||||||
|  |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|  |             **extra, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def generic( |     def generic( | ||||||
| @@ -587,6 +643,7 @@ class RequestFactory: | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Construct an arbitrary HTTP request.""" |         """Construct an arbitrary HTTP request.""" | ||||||
| @@ -608,6 +665,8 @@ class RequestFactory: | |||||||
|             ) |             ) | ||||||
|         if headers: |         if headers: | ||||||
|             extra.update(HttpHeaders.to_wsgi_names(headers)) |             extra.update(HttpHeaders.to_wsgi_names(headers)) | ||||||
|  |         if query_params: | ||||||
|  |             extra["QUERY_STRING"] = urlencode(query_params, doseq=True) | ||||||
|         r.update(extra) |         r.update(extra) | ||||||
|         # If QUERY_STRING is absent or empty, we want to extract it from the URL. |         # If QUERY_STRING is absent or empty, we want to extract it from the URL. | ||||||
|         if not r.get("QUERY_STRING"): |         if not r.get("QUERY_STRING"): | ||||||
| @@ -685,6 +744,7 @@ class AsyncRequestFactory(RequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Construct an arbitrary HTTP request.""" |         """Construct an arbitrary HTTP request.""" | ||||||
| @@ -705,18 +765,20 @@ class AsyncRequestFactory(RequestFactory): | |||||||
|                 ] |                 ] | ||||||
|             ) |             ) | ||||||
|             s["_body_file"] = FakePayload(data) |             s["_body_file"] = FakePayload(data) | ||||||
|         if query_string := extra.pop("QUERY_STRING", None): |         if query_params: | ||||||
|  |             s["query_string"] = urlencode(query_params, doseq=True) | ||||||
|  |         elif query_string := extra.pop("QUERY_STRING", None): | ||||||
|             s["query_string"] = query_string |             s["query_string"] = query_string | ||||||
|  |         else: | ||||||
|  |             # If QUERY_STRING is absent or empty, we want to extract it from | ||||||
|  |             # the URL. | ||||||
|  |             s["query_string"] = parsed[4] | ||||||
|         if headers: |         if headers: | ||||||
|             extra.update(HttpHeaders.to_asgi_names(headers)) |             extra.update(HttpHeaders.to_asgi_names(headers)) | ||||||
|         s["headers"] += [ |         s["headers"] += [ | ||||||
|             (key.lower().encode("ascii"), value.encode("latin1")) |             (key.lower().encode("ascii"), value.encode("latin1")) | ||||||
|             for key, value in extra.items() |             for key, value in extra.items() | ||||||
|         ] |         ] | ||||||
|         # If QUERY_STRING is absent or empty, we want to extract it from the |  | ||||||
|         # URL. |  | ||||||
|         if not s.get("query_string"): |  | ||||||
|             s["query_string"] = parsed[4] |  | ||||||
|         return self.request(**s) |         return self.request(**s) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -889,7 +951,14 @@ class ClientMixin: | |||||||
|         return response._json |         return response._json | ||||||
|  |  | ||||||
|     def _follow_redirect( |     def _follow_redirect( | ||||||
|         self, response, *, data="", content_type="", headers=None, **extra |         self, | ||||||
|  |         response, | ||||||
|  |         *, | ||||||
|  |         data="", | ||||||
|  |         content_type="", | ||||||
|  |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|  |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Follow a single redirect contained in response using GET.""" |         """Follow a single redirect contained in response using GET.""" | ||||||
|         response_url = response.url |         response_url = response.url | ||||||
| @@ -934,6 +1003,7 @@ class ClientMixin: | |||||||
|             content_type=content_type, |             content_type=content_type, | ||||||
|             follow=False, |             follow=False, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
| @@ -978,9 +1048,10 @@ class Client(ClientMixin, RequestFactory): | |||||||
|         raise_request_exception=True, |         raise_request_exception=True, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **defaults, |         **defaults, | ||||||
|     ): |     ): | ||||||
|         super().__init__(headers=headers, **defaults) |         super().__init__(headers=headers, query_params=query_params, **defaults) | ||||||
|         self.handler = ClientHandler(enforce_csrf_checks) |         self.handler = ClientHandler(enforce_csrf_checks) | ||||||
|         self.raise_request_exception = raise_request_exception |         self.raise_request_exception = raise_request_exception | ||||||
|         self.exc_info = None |         self.exc_info = None | ||||||
| @@ -1042,15 +1113,23 @@ class Client(ClientMixin, RequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Request a response from the server using GET.""" |         """Request a response from the server using GET.""" | ||||||
|         self.extra = extra |         self.extra = extra | ||||||
|         self.headers = headers |         self.headers = headers | ||||||
|         response = super().get(path, data=data, secure=secure, headers=headers, **extra) |         response = super().get( | ||||||
|  |             path, | ||||||
|  |             data=data, | ||||||
|  |             secure=secure, | ||||||
|  |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|  |             **extra, | ||||||
|  |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = self._handle_redirects( |             response = self._handle_redirects( | ||||||
|                 response, data=data, headers=headers, **extra |                 response, data=data, headers=headers, query_params=query_params, **extra | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1063,6 +1142,7 @@ class Client(ClientMixin, RequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Request a response from the server using POST.""" |         """Request a response from the server using POST.""" | ||||||
| @@ -1074,11 +1154,17 @@ class Client(ClientMixin, RequestFactory): | |||||||
|             content_type=content_type, |             content_type=content_type, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = self._handle_redirects( |             response = self._handle_redirects( | ||||||
|                 response, data=data, content_type=content_type, headers=headers, **extra |                 response, | ||||||
|  |                 data=data, | ||||||
|  |                 content_type=content_type, | ||||||
|  |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|  |                 **extra, | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1090,17 +1176,23 @@ class Client(ClientMixin, RequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Request a response from the server using HEAD.""" |         """Request a response from the server using HEAD.""" | ||||||
|         self.extra = extra |         self.extra = extra | ||||||
|         self.headers = headers |         self.headers = headers | ||||||
|         response = super().head( |         response = super().head( | ||||||
|             path, data=data, secure=secure, headers=headers, **extra |             path, | ||||||
|  |             data=data, | ||||||
|  |             secure=secure, | ||||||
|  |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|  |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = self._handle_redirects( |             response = self._handle_redirects( | ||||||
|                 response, data=data, headers=headers, **extra |                 response, data=data, headers=headers, query_params=query_params, **extra | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1113,6 +1205,7 @@ class Client(ClientMixin, RequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Request a response from the server using OPTIONS.""" |         """Request a response from the server using OPTIONS.""" | ||||||
| @@ -1124,11 +1217,17 @@ class Client(ClientMixin, RequestFactory): | |||||||
|             content_type=content_type, |             content_type=content_type, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = self._handle_redirects( |             response = self._handle_redirects( | ||||||
|                 response, data=data, content_type=content_type, headers=headers, **extra |                 response, | ||||||
|  |                 data=data, | ||||||
|  |                 content_type=content_type, | ||||||
|  |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|  |                 **extra, | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1141,6 +1240,7 @@ class Client(ClientMixin, RequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Send a resource to the server using PUT.""" |         """Send a resource to the server using PUT.""" | ||||||
| @@ -1152,11 +1252,17 @@ class Client(ClientMixin, RequestFactory): | |||||||
|             content_type=content_type, |             content_type=content_type, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = self._handle_redirects( |             response = self._handle_redirects( | ||||||
|                 response, data=data, content_type=content_type, headers=headers, **extra |                 response, | ||||||
|  |                 data=data, | ||||||
|  |                 content_type=content_type, | ||||||
|  |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|  |                 **extra, | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1169,6 +1275,7 @@ class Client(ClientMixin, RequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Send a resource to the server using PATCH.""" |         """Send a resource to the server using PATCH.""" | ||||||
| @@ -1180,11 +1287,17 @@ class Client(ClientMixin, RequestFactory): | |||||||
|             content_type=content_type, |             content_type=content_type, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = self._handle_redirects( |             response = self._handle_redirects( | ||||||
|                 response, data=data, content_type=content_type, headers=headers, **extra |                 response, | ||||||
|  |                 data=data, | ||||||
|  |                 content_type=content_type, | ||||||
|  |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|  |                 **extra, | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1197,6 +1310,7 @@ class Client(ClientMixin, RequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Send a DELETE request to the server.""" |         """Send a DELETE request to the server.""" | ||||||
| @@ -1208,11 +1322,17 @@ class Client(ClientMixin, RequestFactory): | |||||||
|             content_type=content_type, |             content_type=content_type, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = self._handle_redirects( |             response = self._handle_redirects( | ||||||
|                 response, data=data, content_type=content_type, headers=headers, **extra |                 response, | ||||||
|  |                 data=data, | ||||||
|  |                 content_type=content_type, | ||||||
|  |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|  |                 **extra, | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1224,17 +1344,23 @@ class Client(ClientMixin, RequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Send a TRACE request to the server.""" |         """Send a TRACE request to the server.""" | ||||||
|         self.extra = extra |         self.extra = extra | ||||||
|         self.headers = headers |         self.headers = headers | ||||||
|         response = super().trace( |         response = super().trace( | ||||||
|             path, data=data, secure=secure, headers=headers, **extra |             path, | ||||||
|  |             data=data, | ||||||
|  |             secure=secure, | ||||||
|  |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|  |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = self._handle_redirects( |             response = self._handle_redirects( | ||||||
|                 response, data=data, headers=headers, **extra |                 response, data=data, headers=headers, query_params=query_params, **extra | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1244,6 +1370,7 @@ class Client(ClientMixin, RequestFactory): | |||||||
|         data="", |         data="", | ||||||
|         content_type="", |         content_type="", | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
| @@ -1257,6 +1384,7 @@ class Client(ClientMixin, RequestFactory): | |||||||
|                 data=data, |                 data=data, | ||||||
|                 content_type=content_type, |                 content_type=content_type, | ||||||
|                 headers=headers, |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|                 **extra, |                 **extra, | ||||||
|             ) |             ) | ||||||
|             response.redirect_chain = redirect_chain |             response.redirect_chain = redirect_chain | ||||||
| @@ -1278,9 +1406,10 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|         raise_request_exception=True, |         raise_request_exception=True, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **defaults, |         **defaults, | ||||||
|     ): |     ): | ||||||
|         super().__init__(headers=headers, **defaults) |         super().__init__(headers=headers, query_params=query_params, **defaults) | ||||||
|         self.handler = AsyncClientHandler(enforce_csrf_checks) |         self.handler = AsyncClientHandler(enforce_csrf_checks) | ||||||
|         self.raise_request_exception = raise_request_exception |         self.raise_request_exception = raise_request_exception | ||||||
|         self.exc_info = None |         self.exc_info = None | ||||||
| @@ -1341,17 +1470,23 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Request a response from the server using GET.""" |         """Request a response from the server using GET.""" | ||||||
|         self.extra = extra |         self.extra = extra | ||||||
|         self.headers = headers |         self.headers = headers | ||||||
|         response = await super().get( |         response = await super().get( | ||||||
|             path, data=data, secure=secure, headers=headers, **extra |             path, | ||||||
|  |             data=data, | ||||||
|  |             secure=secure, | ||||||
|  |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|  |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = await self._ahandle_redirects( |             response = await self._ahandle_redirects( | ||||||
|                 response, data=data, headers=headers, **extra |                 response, data=data, headers=headers, query_params=query_params, **extra | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1364,6 +1499,7 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Request a response from the server using POST.""" |         """Request a response from the server using POST.""" | ||||||
| @@ -1375,11 +1511,17 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|             content_type=content_type, |             content_type=content_type, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = await self._ahandle_redirects( |             response = await self._ahandle_redirects( | ||||||
|                 response, data=data, content_type=content_type, headers=headers, **extra |                 response, | ||||||
|  |                 data=data, | ||||||
|  |                 content_type=content_type, | ||||||
|  |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|  |                 **extra, | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1391,17 +1533,23 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Request a response from the server using HEAD.""" |         """Request a response from the server using HEAD.""" | ||||||
|         self.extra = extra |         self.extra = extra | ||||||
|         self.headers = headers |         self.headers = headers | ||||||
|         response = await super().head( |         response = await super().head( | ||||||
|             path, data=data, secure=secure, headers=headers, **extra |             path, | ||||||
|  |             data=data, | ||||||
|  |             secure=secure, | ||||||
|  |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|  |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = await self._ahandle_redirects( |             response = await self._ahandle_redirects( | ||||||
|                 response, data=data, headers=headers, **extra |                 response, data=data, headers=headers, query_params=query_params, **extra | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1414,6 +1562,7 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Request a response from the server using OPTIONS.""" |         """Request a response from the server using OPTIONS.""" | ||||||
| @@ -1425,11 +1574,17 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|             content_type=content_type, |             content_type=content_type, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = await self._ahandle_redirects( |             response = await self._ahandle_redirects( | ||||||
|                 response, data=data, content_type=content_type, headers=headers, **extra |                 response, | ||||||
|  |                 data=data, | ||||||
|  |                 content_type=content_type, | ||||||
|  |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|  |                 **extra, | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1442,6 +1597,7 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Send a resource to the server using PUT.""" |         """Send a resource to the server using PUT.""" | ||||||
| @@ -1453,11 +1609,17 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|             content_type=content_type, |             content_type=content_type, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = await self._ahandle_redirects( |             response = await self._ahandle_redirects( | ||||||
|                 response, data=data, content_type=content_type, headers=headers, **extra |                 response, | ||||||
|  |                 data=data, | ||||||
|  |                 content_type=content_type, | ||||||
|  |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|  |                 **extra, | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1470,6 +1632,7 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Send a resource to the server using PATCH.""" |         """Send a resource to the server using PATCH.""" | ||||||
| @@ -1481,11 +1644,17 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|             content_type=content_type, |             content_type=content_type, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = await self._ahandle_redirects( |             response = await self._ahandle_redirects( | ||||||
|                 response, data=data, content_type=content_type, headers=headers, **extra |                 response, | ||||||
|  |                 data=data, | ||||||
|  |                 content_type=content_type, | ||||||
|  |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|  |                 **extra, | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1498,6 +1667,7 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Send a DELETE request to the server.""" |         """Send a DELETE request to the server.""" | ||||||
| @@ -1509,11 +1679,17 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|             content_type=content_type, |             content_type=content_type, | ||||||
|             secure=secure, |             secure=secure, | ||||||
|             headers=headers, |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|             **extra, |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = await self._ahandle_redirects( |             response = await self._ahandle_redirects( | ||||||
|                 response, data=data, content_type=content_type, headers=headers, **extra |                 response, | ||||||
|  |                 data=data, | ||||||
|  |                 content_type=content_type, | ||||||
|  |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|  |                 **extra, | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1525,17 +1701,23 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|         secure=False, |         secure=False, | ||||||
|         *, |         *, | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """Send a TRACE request to the server.""" |         """Send a TRACE request to the server.""" | ||||||
|         self.extra = extra |         self.extra = extra | ||||||
|         self.headers = headers |         self.headers = headers | ||||||
|         response = await super().trace( |         response = await super().trace( | ||||||
|             path, data=data, secure=secure, headers=headers, **extra |             path, | ||||||
|  |             data=data, | ||||||
|  |             secure=secure, | ||||||
|  |             headers=headers, | ||||||
|  |             query_params=query_params, | ||||||
|  |             **extra, | ||||||
|         ) |         ) | ||||||
|         if follow: |         if follow: | ||||||
|             response = await self._ahandle_redirects( |             response = await self._ahandle_redirects( | ||||||
|                 response, data=data, headers=headers, **extra |                 response, data=data, headers=headers, query_params=query_params, **extra | ||||||
|             ) |             ) | ||||||
|         return response |         return response | ||||||
|  |  | ||||||
| @@ -1545,6 +1727,7 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|         data="", |         data="", | ||||||
|         content_type="", |         content_type="", | ||||||
|         headers=None, |         headers=None, | ||||||
|  |         query_params=None, | ||||||
|         **extra, |         **extra, | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
| @@ -1558,6 +1741,7 @@ class AsyncClient(ClientMixin, AsyncRequestFactory): | |||||||
|                 data=data, |                 data=data, | ||||||
|                 content_type=content_type, |                 content_type=content_type, | ||||||
|                 headers=headers, |                 headers=headers, | ||||||
|  |                 query_params=query_params, | ||||||
|                 **extra, |                 **extra, | ||||||
|             ) |             ) | ||||||
|             response.redirect_chain = redirect_chain |             response.redirect_chain = redirect_chain | ||||||
|   | |||||||
| @@ -224,6 +224,17 @@ Tests | |||||||
| * The Django test runner now supports a ``--screenshots`` option to save | * The Django test runner now supports a ``--screenshots`` option to save | ||||||
|   screenshots for Selenium tests. |   screenshots for Selenium tests. | ||||||
|  |  | ||||||
|  | * The :class:`~django.test.RequestFactory`, | ||||||
|  |   :class:`~django.test.AsyncRequestFactory`, :class:`~django.test.Client`, and | ||||||
|  |   :class:`~django.test.AsyncClient` classes now support the ``query_params`` | ||||||
|  |   parameter, which accepts a dictionary of query string keys and values. This | ||||||
|  |   allows setting query strings on any HTTP methods more easily. | ||||||
|  |  | ||||||
|  |   .. code-block:: python | ||||||
|  |  | ||||||
|  |      self.client.post("/items/1", query_params={"action": "delete"}) | ||||||
|  |      await self.async_client.post("/items/1", query_params={"action": "delete"}) | ||||||
|  |  | ||||||
| URLs | URLs | ||||||
| ~~~~ | ~~~~ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -32,6 +32,10 @@ restricted subset of the test client API: | |||||||
|   attributes must be supplied by the test itself if required |   attributes must be supplied by the test itself if required | ||||||
|   for the view to function properly. |   for the view to function properly. | ||||||
|  |  | ||||||
|  | .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |     The ``query_params`` parameter was added. | ||||||
|  |  | ||||||
| Example | Example | ||||||
| ------- | ------- | ||||||
|  |  | ||||||
| @@ -85,6 +89,10 @@ difference being that it returns ``ASGIRequest`` instances rather than | |||||||
| Arbitrary keyword arguments in ``defaults`` are added directly into the ASGI | Arbitrary keyword arguments in ``defaults`` are added directly into the ASGI | ||||||
| scope. | scope. | ||||||
|  |  | ||||||
|  | .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |     The ``query_params`` parameter was added. | ||||||
|  |  | ||||||
| Testing class-based views | Testing class-based views | ||||||
| ========================= | ========================= | ||||||
|  |  | ||||||
|   | |||||||
| @@ -120,7 +120,7 @@ Making requests | |||||||
|  |  | ||||||
| Use the ``django.test.Client`` class to make requests. | Use the ``django.test.Client`` class to make requests. | ||||||
|  |  | ||||||
| .. class:: Client(enforce_csrf_checks=False, raise_request_exception=True, json_encoder=DjangoJSONEncoder, *, headers=None, **defaults) | .. class:: Client(enforce_csrf_checks=False, raise_request_exception=True, json_encoder=DjangoJSONEncoder, *, headers=None, query_params=None, **defaults) | ||||||
|  |  | ||||||
|     A testing HTTP client. Takes several arguments that can customize behavior. |     A testing HTTP client. Takes several arguments that can customize behavior. | ||||||
|  |  | ||||||
| @@ -129,6 +129,9 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|  |  | ||||||
|         client = Client(headers={"user-agent": "curl/7.79.1"}) |         client = Client(headers={"user-agent": "curl/7.79.1"}) | ||||||
|  |  | ||||||
|  |     ``query_params`` allows you to specify the default query string that will | ||||||
|  |     be set on every request. | ||||||
|  |  | ||||||
|     Arbitrary keyword arguments in ``**defaults`` set WSGI |     Arbitrary keyword arguments in ``**defaults`` set WSGI | ||||||
|     :pep:`environ variables <3333#environ-variables>`. For example, to set the |     :pep:`environ variables <3333#environ-variables>`. For example, to set the | ||||||
|     script name:: |     script name:: | ||||||
| @@ -140,8 +143,8 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|         Keyword arguments starting with a ``HTTP_`` prefix are set as headers, |         Keyword arguments starting with a ``HTTP_`` prefix are set as headers, | ||||||
|         but the ``headers`` parameter should be preferred for readability. |         but the ``headers`` parameter should be preferred for readability. | ||||||
|  |  | ||||||
|     The values from the ``headers`` and ``extra`` keyword arguments passed to |     The values from the ``headers``, ``query_params``, and ``extra`` keyword | ||||||
|     :meth:`~django.test.Client.get()`, |     arguments passed to :meth:`~django.test.Client.get()`, | ||||||
|     :meth:`~django.test.Client.post()`, etc. have precedence over |     :meth:`~django.test.Client.post()`, etc. have precedence over | ||||||
|     the defaults passed to the class constructor. |     the defaults passed to the class constructor. | ||||||
|  |  | ||||||
| @@ -155,21 +158,25 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|     The ``json_encoder`` argument allows setting a custom JSON encoder for |     The ``json_encoder`` argument allows setting a custom JSON encoder for | ||||||
|     the JSON serialization that's described in :meth:`post`. |     the JSON serialization that's described in :meth:`post`. | ||||||
|  |  | ||||||
|  |     .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |         The ``query_params`` argument was added. | ||||||
|  |  | ||||||
|     Once you have a ``Client`` instance, you can call any of the following |     Once you have a ``Client`` instance, you can call any of the following | ||||||
|     methods: |     methods: | ||||||
|  |  | ||||||
|     .. method:: Client.get(path, data=None, follow=False, secure=False, *, headers=None, **extra) |     .. method:: Client.get(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra) | ||||||
|  |  | ||||||
|         Makes a GET request on the provided ``path`` and returns a ``Response`` |         Makes a GET request on the provided ``path`` and returns a ``Response`` | ||||||
|         object, which is documented below. |         object, which is documented below. | ||||||
|  |  | ||||||
|         The key-value pairs in the ``data`` dictionary are used to create a GET |         The key-value pairs in the ``query_params`` dictionary are used to set | ||||||
|         data payload. For example: |         query strings. For example: | ||||||
|  |  | ||||||
|         .. code-block:: pycon |         .. code-block:: pycon | ||||||
|  |  | ||||||
|             >>> c = Client() |             >>> c = Client() | ||||||
|             >>> c.get("/customers/details/", {"name": "fred", "age": 7}) |             >>> c.get("/customers/details/", query_params={"name": "fred", "age": 7}) | ||||||
|  |  | ||||||
|         ...will result in the evaluation of a GET request equivalent to: |         ...will result in the evaluation of a GET request equivalent to: | ||||||
|  |  | ||||||
| @@ -177,6 +184,10 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|  |  | ||||||
|             /customers/details/?name=fred&age=7 |             /customers/details/?name=fred&age=7 | ||||||
|  |  | ||||||
|  |         It is also possible to pass these parameters into the ``data`` | ||||||
|  |         parameter. However, ``query_params`` is preferred as it works for any | ||||||
|  |         HTTP method. | ||||||
|  |  | ||||||
|         The ``headers`` parameter can be used to specify headers to be sent in |         The ``headers`` parameter can be used to specify headers to be sent in | ||||||
|         the request. For example: |         the request. For example: | ||||||
|  |  | ||||||
| @@ -185,7 +196,7 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|             >>> c = Client() |             >>> c = Client() | ||||||
|             >>> c.get( |             >>> c.get( | ||||||
|             ...     "/customers/details/", |             ...     "/customers/details/", | ||||||
|             ...     {"name": "fred", "age": 7}, |             ...     query_params={"name": "fred", "age": 7}, | ||||||
|             ...     headers={"accept": "application/json"}, |             ...     headers={"accept": "application/json"}, | ||||||
|             ... ) |             ... ) | ||||||
|  |  | ||||||
| @@ -211,8 +222,8 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|             >>> c = Client() |             >>> c = Client() | ||||||
|             >>> c.get("/customers/details/?name=fred&age=7") |             >>> c.get("/customers/details/?name=fred&age=7") | ||||||
|  |  | ||||||
|         If you provide a URL with both an encoded GET data and a data argument, |         If you provide a URL with both an encoded GET data and either a | ||||||
|         the data argument will take precedence. |         query_params or data argument these arguments will take precedence. | ||||||
|  |  | ||||||
|         If you set ``follow`` to ``True`` the client will follow any redirects |         If you set ``follow`` to ``True`` the client will follow any redirects | ||||||
|         and a ``redirect_chain`` attribute will be set in the response object |         and a ``redirect_chain`` attribute will be set in the response object | ||||||
| @@ -230,7 +241,11 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|         If you set ``secure`` to ``True`` the client will emulate an HTTPS |         If you set ``secure`` to ``True`` the client will emulate an HTTPS | ||||||
|         request. |         request. | ||||||
|  |  | ||||||
|     .. method:: Client.post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, *, headers=None, **extra) |         .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |             The ``query_params`` argument was added. | ||||||
|  |  | ||||||
|  |     .. method:: Client.post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, *, headers=None, query_params=None, **extra) | ||||||
|  |  | ||||||
|         Makes a POST request on the provided ``path`` and returns a |         Makes a POST request on the provided ``path`` and returns a | ||||||
|         ``Response`` object, which is documented below. |         ``Response`` object, which is documented below. | ||||||
| @@ -321,8 +336,8 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|         such as an image, this means you will need to open the file in |         such as an image, this means you will need to open the file in | ||||||
|         ``rb`` (read binary) mode. |         ``rb`` (read binary) mode. | ||||||
|  |  | ||||||
|         The ``headers`` and ``extra`` parameters acts the same as for |         The ``headers``, ``query_params``, and ``extra`` parameters acts the | ||||||
|         :meth:`Client.get`. |         same as for :meth:`Client.get`. | ||||||
|  |  | ||||||
|         If the URL you request with a POST contains encoded parameters, these |         If the URL you request with a POST contains encoded parameters, these | ||||||
|         parameters will be made available in the request.GET data. For example, |         parameters will be made available in the request.GET data. For example, | ||||||
| @@ -330,7 +345,9 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|  |  | ||||||
|         .. code-block:: pycon |         .. code-block:: pycon | ||||||
|  |  | ||||||
|             >>> c.post("/login/?visitor=true", {"name": "fred", "passwd": "secret"}) |             >>> c.post( | ||||||
|  |             ...     "/login/", {"name": "fred", "passwd": "secret"}, query_params={"visitor": "true"} | ||||||
|  |             ... ) | ||||||
|  |  | ||||||
|         ... the view handling this request could interrogate request.POST |         ... the view handling this request could interrogate request.POST | ||||||
|         to retrieve the username and password, and could interrogate request.GET |         to retrieve the username and password, and could interrogate request.GET | ||||||
| @@ -343,14 +360,22 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|         If you set ``secure`` to ``True`` the client will emulate an HTTPS |         If you set ``secure`` to ``True`` the client will emulate an HTTPS | ||||||
|         request. |         request. | ||||||
|  |  | ||||||
|     .. method:: Client.head(path, data=None, follow=False, secure=False, *, headers=None, **extra) |         .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |             The ``query_params`` argument was added. | ||||||
|  |  | ||||||
|  |     .. method:: Client.head(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra) | ||||||
|  |  | ||||||
|         Makes a HEAD request on the provided ``path`` and returns a |         Makes a HEAD request on the provided ``path`` and returns a | ||||||
|         ``Response`` object. This method works just like :meth:`Client.get`, |         ``Response`` object. This method works just like :meth:`Client.get`, | ||||||
|         including the ``follow``, ``secure``, ``headers``, and ``extra`` |         including the ``follow``, ``secure``, ``headers``, ``query_params``, | ||||||
|         parameters, except it does not return a message body. |         and ``extra`` parameters, except it does not return a message body. | ||||||
|  |  | ||||||
|     .. method:: Client.options(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, **extra) |         .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |             The ``query_params`` argument was added. | ||||||
|  |  | ||||||
|  |     .. method:: Client.options(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra) | ||||||
|  |  | ||||||
|         Makes an OPTIONS request on the provided ``path`` and returns a |         Makes an OPTIONS request on the provided ``path`` and returns a | ||||||
|         ``Response`` object. Useful for testing RESTful interfaces. |         ``Response`` object. Useful for testing RESTful interfaces. | ||||||
| @@ -358,10 +383,14 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|         When ``data`` is provided, it is used as the request body, and |         When ``data`` is provided, it is used as the request body, and | ||||||
|         a ``Content-Type`` header is set to ``content_type``. |         a ``Content-Type`` header is set to ``content_type``. | ||||||
|  |  | ||||||
|         The ``follow``, ``secure``, ``headers``, and ``extra`` parameters act |         The ``follow``, ``secure``, ``headers``, ``query_params``, and | ||||||
|         the same as for :meth:`Client.get`. |         ``extra`` parameters act the same as for :meth:`Client.get`. | ||||||
|  |  | ||||||
|     .. method:: Client.put(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, **extra) |         .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |             The ``query_params`` argument was added. | ||||||
|  |  | ||||||
|  |     .. method:: Client.put(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra) | ||||||
|  |  | ||||||
|         Makes a PUT request on the provided ``path`` and returns a |         Makes a PUT request on the provided ``path`` and returns a | ||||||
|         ``Response`` object. Useful for testing RESTful interfaces. |         ``Response`` object. Useful for testing RESTful interfaces. | ||||||
| @@ -369,18 +398,26 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|         When ``data`` is provided, it is used as the request body, and |         When ``data`` is provided, it is used as the request body, and | ||||||
|         a ``Content-Type`` header is set to ``content_type``. |         a ``Content-Type`` header is set to ``content_type``. | ||||||
|  |  | ||||||
|         The ``follow``, ``secure``, ``headers``, and ``extra`` parameters act |         The ``follow``, ``secure``, ``headers``, ``query_params``, and | ||||||
|         the same as for :meth:`Client.get`. |         ``extra`` parameters act the same as for :meth:`Client.get`. | ||||||
|  |  | ||||||
|     .. method:: Client.patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, **extra) |         .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |             The ``query_params`` argument was added. | ||||||
|  |  | ||||||
|  |     .. method:: Client.patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra) | ||||||
|  |  | ||||||
|         Makes a PATCH request on the provided ``path`` and returns a |         Makes a PATCH request on the provided ``path`` and returns a | ||||||
|         ``Response`` object. Useful for testing RESTful interfaces. |         ``Response`` object. Useful for testing RESTful interfaces. | ||||||
|  |  | ||||||
|         The ``follow``, ``secure``, ``headers``, and ``extra`` parameters act |         The ``follow``, ``secure``, ``headers``, ``query_params``, and | ||||||
|         the same as for :meth:`Client.get`. |         ``extra`` parameters act the same as for :meth:`Client.get`. | ||||||
|  |  | ||||||
|     .. method:: Client.delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, **extra) |         .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |             The ``query_params`` argument was added. | ||||||
|  |  | ||||||
|  |     .. method:: Client.delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra) | ||||||
|  |  | ||||||
|         Makes a DELETE request on the provided ``path`` and returns a |         Makes a DELETE request on the provided ``path`` and returns a | ||||||
|         ``Response`` object. Useful for testing RESTful interfaces. |         ``Response`` object. Useful for testing RESTful interfaces. | ||||||
| @@ -388,10 +425,14 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|         When ``data`` is provided, it is used as the request body, and |         When ``data`` is provided, it is used as the request body, and | ||||||
|         a ``Content-Type`` header is set to ``content_type``. |         a ``Content-Type`` header is set to ``content_type``. | ||||||
|  |  | ||||||
|         The ``follow``, ``secure``, ``headers``, and ``extra`` parameters act |         The ``follow``, ``secure``, ``headers``, ``query_params``, and | ||||||
|         the same as for :meth:`Client.get`. |         ``extra`` parameters act the same as for :meth:`Client.get`. | ||||||
|  |  | ||||||
|     .. method:: Client.trace(path, follow=False, secure=False, *, headers=None, **extra) |         .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |             The ``query_params`` argument was added. | ||||||
|  |  | ||||||
|  |     .. method:: Client.trace(path, follow=False, secure=False, *, headers=None, query_params=None, **extra) | ||||||
|  |  | ||||||
|         Makes a TRACE request on the provided ``path`` and returns a |         Makes a TRACE request on the provided ``path`` and returns a | ||||||
|         ``Response`` object. Useful for simulating diagnostic probes. |         ``Response`` object. Useful for simulating diagnostic probes. | ||||||
| @@ -400,8 +441,12 @@ Use the ``django.test.Client`` class to make requests. | |||||||
|         parameter in order to comply with :rfc:`9110#section-9.3.8`, which |         parameter in order to comply with :rfc:`9110#section-9.3.8`, which | ||||||
|         mandates that TRACE requests must not have a body. |         mandates that TRACE requests must not have a body. | ||||||
|  |  | ||||||
|         The ``follow``, ``secure``, ``headers``, and ``extra`` parameters act |         The ``follow``, ``secure``, ``headers``, ``query_params``, and | ||||||
|         the same as for :meth:`Client.get`. |         ``extra`` parameters act the same as for :meth:`Client.get`. | ||||||
|  |  | ||||||
|  |         .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |             The ``query_params`` argument was added. | ||||||
|  |  | ||||||
|     .. method:: Client.login(**credentials) |     .. method:: Client.login(**credentials) | ||||||
|     .. method:: Client.alogin(**credentials) |     .. method:: Client.alogin(**credentials) | ||||||
| @@ -1997,7 +2042,7 @@ If you are testing from an asynchronous function, you must also use the | |||||||
| asynchronous test client. This is available as ``django.test.AsyncClient``, | asynchronous test client. This is available as ``django.test.AsyncClient``, | ||||||
| or as ``self.async_client`` on any test. | or as ``self.async_client`` on any test. | ||||||
|  |  | ||||||
| .. class:: AsyncClient(enforce_csrf_checks=False, raise_request_exception=True, *, headers=None, **defaults) | .. class:: AsyncClient(enforce_csrf_checks=False, raise_request_exception=True, *, headers=None, query_params=None, **defaults) | ||||||
|  |  | ||||||
| ``AsyncClient`` has the same methods and signatures as the synchronous (normal) | ``AsyncClient`` has the same methods and signatures as the synchronous (normal) | ||||||
| test client, with the following exceptions: | test client, with the following exceptions: | ||||||
| @@ -2017,6 +2062,10 @@ test client, with the following exceptions: | |||||||
|  |  | ||||||
|     Support for the ``follow`` parameter was added to the ``AsyncClient``. |     Support for the ``follow`` parameter was added to the ``AsyncClient``. | ||||||
|  |  | ||||||
|  | .. versionchanged:: 5.1 | ||||||
|  |  | ||||||
|  |     The ``query_params`` argument was added. | ||||||
|  |  | ||||||
| Using ``AsyncClient`` any method that makes a request must be awaited:: | Using ``AsyncClient`` any method that makes a request must be awaited:: | ||||||
|  |  | ||||||
|     async def test_my_thing(self): |     async def test_my_thing(self): | ||||||
|   | |||||||
| @@ -1002,6 +1002,36 @@ class ClientTest(TestCase): | |||||||
|             ) |             ) | ||||||
|         self.assertEqual(response.content, b"named_temp_file") |         self.assertEqual(response.content, b"named_temp_file") | ||||||
|  |  | ||||||
|  |     def test_query_params(self): | ||||||
|  |         tests = ( | ||||||
|  |             "get", | ||||||
|  |             "post", | ||||||
|  |             "put", | ||||||
|  |             "patch", | ||||||
|  |             "delete", | ||||||
|  |             "head", | ||||||
|  |             "options", | ||||||
|  |             "trace", | ||||||
|  |         ) | ||||||
|  |         for method in tests: | ||||||
|  |             with self.subTest(method=method): | ||||||
|  |                 client_method = getattr(self.client, method) | ||||||
|  |                 response = client_method("/get_view/", query_params={"example": "data"}) | ||||||
|  |                 self.assertEqual(response.wsgi_request.GET["example"], "data") | ||||||
|  |  | ||||||
|  |     def test_cannot_use_data_and_query_params_together(self): | ||||||
|  |         tests = ["get", "head"] | ||||||
|  |         msg = "query_params and data arguments are mutually exclusive." | ||||||
|  |         for method in tests: | ||||||
|  |             with self.subTest(method=method): | ||||||
|  |                 client_method = getattr(self.client, method) | ||||||
|  |                 with self.assertRaisesMessage(ValueError, msg): | ||||||
|  |                     client_method( | ||||||
|  |                         "/get_view/", | ||||||
|  |                         data={"example": "data"}, | ||||||
|  |                         query_params={"q": "terms"}, | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @override_settings( | @override_settings( | ||||||
|     MIDDLEWARE=["django.middleware.csrf.CsrfViewMiddleware"], |     MIDDLEWARE=["django.middleware.csrf.CsrfViewMiddleware"], | ||||||
| @@ -1127,6 +1157,23 @@ class RequestFactoryTest(SimpleTestCase): | |||||||
|             self.assertEqual(request.headers["x-another-header"], "some other value") |             self.assertEqual(request.headers["x-another-header"], "some other value") | ||||||
|             self.assertIn("HTTP_X_ANOTHER_HEADER", request.META) |             self.assertIn("HTTP_X_ANOTHER_HEADER", request.META) | ||||||
|  |  | ||||||
|  |     def test_request_factory_query_params(self): | ||||||
|  |         tests = ( | ||||||
|  |             "get", | ||||||
|  |             "post", | ||||||
|  |             "put", | ||||||
|  |             "patch", | ||||||
|  |             "delete", | ||||||
|  |             "head", | ||||||
|  |             "options", | ||||||
|  |             "trace", | ||||||
|  |         ) | ||||||
|  |         for method in tests: | ||||||
|  |             with self.subTest(method=method): | ||||||
|  |                 factory = getattr(self.request_factory, method) | ||||||
|  |                 request = factory("/somewhere", query_params={"example": "data"}) | ||||||
|  |                 self.assertEqual(request.GET["example"], "data") | ||||||
|  |  | ||||||
|  |  | ||||||
| @override_settings(ROOT_URLCONF="test_client.urls") | @override_settings(ROOT_URLCONF="test_client.urls") | ||||||
| class AsyncClientTest(TestCase): | class AsyncClientTest(TestCase): | ||||||
| @@ -1183,6 +1230,25 @@ class AsyncClientTest(TestCase): | |||||||
|         response = await self.async_client.get("/post_view/") |         response = await self.async_client.get("/post_view/") | ||||||
|         self.assertContains(response, "Viewing GET page.") |         self.assertContains(response, "Viewing GET page.") | ||||||
|  |  | ||||||
|  |     async def test_query_params(self): | ||||||
|  |         tests = ( | ||||||
|  |             "get", | ||||||
|  |             "post", | ||||||
|  |             "put", | ||||||
|  |             "patch", | ||||||
|  |             "delete", | ||||||
|  |             "head", | ||||||
|  |             "options", | ||||||
|  |             "trace", | ||||||
|  |         ) | ||||||
|  |         for method in tests: | ||||||
|  |             with self.subTest(method=method): | ||||||
|  |                 client_method = getattr(self.async_client, method) | ||||||
|  |                 response = await client_method( | ||||||
|  |                     "/async_get_view/", query_params={"example": "data"} | ||||||
|  |                 ) | ||||||
|  |                 self.assertEqual(response.asgi_request.GET["example"], "data") | ||||||
|  |  | ||||||
|  |  | ||||||
| @override_settings(ROOT_URLCONF="test_client.urls") | @override_settings(ROOT_URLCONF="test_client.urls") | ||||||
| class AsyncRequestFactoryTest(SimpleTestCase): | class AsyncRequestFactoryTest(SimpleTestCase): | ||||||
| @@ -1264,3 +1330,33 @@ class AsyncRequestFactoryTest(SimpleTestCase): | |||||||
|         request = self.request_factory.get("/somewhere/", {"example": "data"}) |         request = self.request_factory.get("/somewhere/", {"example": "data"}) | ||||||
|         self.assertNotIn("Query-String", request.headers) |         self.assertNotIn("Query-String", request.headers) | ||||||
|         self.assertEqual(request.GET["example"], "data") |         self.assertEqual(request.GET["example"], "data") | ||||||
|  |  | ||||||
|  |     def test_request_factory_query_params(self): | ||||||
|  |         tests = ( | ||||||
|  |             "get", | ||||||
|  |             "post", | ||||||
|  |             "put", | ||||||
|  |             "patch", | ||||||
|  |             "delete", | ||||||
|  |             "head", | ||||||
|  |             "options", | ||||||
|  |             "trace", | ||||||
|  |         ) | ||||||
|  |         for method in tests: | ||||||
|  |             with self.subTest(method=method): | ||||||
|  |                 factory = getattr(self.request_factory, method) | ||||||
|  |                 request = factory("/somewhere", query_params={"example": "data"}) | ||||||
|  |                 self.assertEqual(request.GET["example"], "data") | ||||||
|  |  | ||||||
|  |     def test_cannot_use_data_and_query_params_together(self): | ||||||
|  |         tests = ["get", "head"] | ||||||
|  |         msg = "query_params and data arguments are mutually exclusive." | ||||||
|  |         for method in tests: | ||||||
|  |             with self.subTest(method=method): | ||||||
|  |                 factory = getattr(self.request_factory, method) | ||||||
|  |                 with self.assertRaisesMessage(ValueError, msg): | ||||||
|  |                     factory( | ||||||
|  |                         "/somewhere", | ||||||
|  |                         data={"example": "data"}, | ||||||
|  |                         query_params={"q": "terms"}, | ||||||
|  |                     ) | ||||||
|   | |||||||
| @@ -1197,6 +1197,10 @@ class QueryStringTests(SimpleTestCase): | |||||||
|         self.assertEqual(response.context["get-foo"], "whiz") |         self.assertEqual(response.context["get-foo"], "whiz") | ||||||
|         self.assertIsNone(response.context["post-foo"]) |         self.assertIsNone(response.context["post-foo"]) | ||||||
|  |  | ||||||
|  |         response = self.client.post("/request_data/", query_params={"foo": "whiz"}) | ||||||
|  |         self.assertEqual(response.context["get-foo"], "whiz") | ||||||
|  |         self.assertIsNone(response.context["post-foo"]) | ||||||
|  |  | ||||||
|         # POST data provided in the URL augments actual form data |         # POST data provided in the URL augments actual form data | ||||||
|         response = self.client.post("/request_data/?foo=whiz", data={"foo": "bang"}) |         response = self.client.post("/request_data/?foo=whiz", data={"foo": "bang"}) | ||||||
|         self.assertEqual(response.context["get-foo"], "whiz") |         self.assertEqual(response.context["get-foo"], "whiz") | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user