summaryrefslogtreecommitdiff
path: root/django/http/request.py
diff options
context:
space:
mode:
authorJake Howard <git@theorangeone.net>2024-07-26 12:34:42 +0100
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2024-09-09 12:02:18 +0200
commite161bd4657177f0e723a14a6e414884363b31a5d (patch)
treefbc9c862c2caad8cfa36537d108e1a9ddc281474 /django/http/request.py
parent826ef006681eae1e9b4bd0e4f18fa13713025cba (diff)
Fixed #35631 -- Added HttpRequest.get_preferred_type().
Diffstat (limited to 'django/http/request.py')
-rw-r--r--django/http/request.py83
1 files changed, 71 insertions, 12 deletions
diff --git a/django/http/request.py b/django/http/request.py
index 4c27d576ba..986d4eee89 100644
--- a/django/http/request.py
+++ b/django/http/request.py
@@ -1,5 +1,6 @@
import codecs
import copy
+import operator
from io import BytesIO
from itertools import chain
from urllib.parse import parse_qsl, quote, urlencode, urljoin, urlsplit
@@ -89,14 +90,48 @@ class HttpRequest:
@cached_property
def accepted_types(self):
- """Return a list of MediaType instances."""
- return parse_accept_header(self.headers.get("Accept", "*/*"))
+ """Return a list of MediaType instances, in order of preference."""
+ header_value = self.headers.get("Accept", "*/*")
+ return sorted(
+ (MediaType(token) for token in header_value.split(",") if token.strip()),
+ key=operator.attrgetter("quality", "specificity"),
+ reverse=True,
+ )
- def accepts(self, media_type):
- return any(
- accepted_type.match(media_type) for accepted_type in self.accepted_types
+ def accepted_type(self, media_type):
+ """
+ Return the preferred MediaType instance which matches the given media type.
+ """
+ return next(
+ (
+ accepted_type
+ for accepted_type in self.accepted_types
+ if accepted_type.match(media_type)
+ ),
+ None,
)
+ def get_preferred_type(self, media_types):
+ """Select the preferred media type from the provided options."""
+ if not media_types or not self.accepted_types:
+ return None
+
+ desired_types = [
+ (accepted_type, media_type)
+ for media_type in media_types
+ if (accepted_type := self.accepted_type(media_type)) is not None
+ ]
+
+ if not desired_types:
+ return None
+
+ # Of the desired media types, select the one which is most desirable.
+ return min(desired_types, key=lambda t: self.accepted_types.index(t[0]))[1]
+
+ def accepts(self, media_type):
+ """Does the client accept a response in the given media type?"""
+ return self.accepted_type(media_type) is not None
+
def _set_content_type_params(self, meta):
"""Set content_type, content_params, and encoding."""
self.content_type, self.content_params = parse_header_parameters(
@@ -678,9 +713,37 @@ class MediaType:
if self.is_all_types:
return True
other = MediaType(other)
- if self.main_type == other.main_type and self.sub_type in {"*", other.sub_type}:
- return True
- return False
+ return self.main_type == other.main_type and self.sub_type in {
+ "*",
+ other.sub_type,
+ }
+
+ @cached_property
+ def quality(self):
+ try:
+ quality = float(self.params.get("q", 1))
+ except ValueError:
+ # Discard invalid values.
+ return 1
+
+ # Valid quality values must be between 0 and 1.
+ if quality < 0 or quality > 1:
+ return 1
+
+ return round(quality, 3)
+
+ @property
+ def specificity(self):
+ """
+ Return a value from 0-3 for how specific the media type is.
+ """
+ if self.main_type == "*":
+ return 0
+ elif self.sub_type == "*":
+ return 1
+ elif self.quality == 1:
+ return 2
+ return 3
# It's neither necessary nor appropriate to use
@@ -732,7 +795,3 @@ def validate_host(host, allowed_hosts):
return any(
pattern == "*" or is_same_domain(host, pattern) for pattern in allowed_hosts
)
-
-
-def parse_accept_header(header):
- return [MediaType(token) for token in header.split(",") if token.strip()]