diff options
| author | Jake Howard <git@theorangeone.net> | 2024-07-26 12:34:42 +0100 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2024-09-09 12:02:18 +0200 |
| commit | e161bd4657177f0e723a14a6e414884363b31a5d (patch) | |
| tree | fbc9c862c2caad8cfa36537d108e1a9ddc281474 /django/http/request.py | |
| parent | 826ef006681eae1e9b4bd0e4f18fa13713025cba (diff) | |
Fixed #35631 -- Added HttpRequest.get_preferred_type().
Diffstat (limited to 'django/http/request.py')
| -rw-r--r-- | django/http/request.py | 83 |
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()] |
