diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index 9c29ed056..36d356493 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -8,17 +8,17 @@ on:
jobs:
pre-commit:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 0
- - uses: actions/setup-python@v2
+ - uses: actions/setup-python@v4
with:
- python-version: 3.9
+ python-version: "3.10"
- - uses: pre-commit/action@v2.0.0
+ - uses: pre-commit/action@v3.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index e8375291d..c2975a418 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,7 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements
* Python 3.6+
-* Django 4.1, 4.0, 3.2, 3.1, 3.0
+* Django 4.2, 4.1, 4.0, 3.2, 3.1, 3.0
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.
diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md
index 27f7c5adb..5e0b6a153 100644
--- a/docs/api-guide/permissions.md
+++ b/docs/api-guide/permissions.md
@@ -165,7 +165,7 @@ This permission is suitable if you want your API to only be accessible to a subs
## IsAuthenticatedOrReadOnly
-The `IsAuthenticatedOrReadOnly` will allow authenticated users to perform any request. Requests for unauthorised users will only be permitted if the request method is one of the "safe" methods; `GET`, `HEAD` or `OPTIONS`.
+The `IsAuthenticatedOrReadOnly` will allow authenticated users to perform any request. Requests for unauthenticated users will only be permitted if the request method is one of the "safe" methods; `GET`, `HEAD` or `OPTIONS`.
This permission is suitable if you want to your API to allow read permissions to anonymous users, and only allow write permissions to authenticated users.
diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md
index 82dd16881..c3fa52f21 100644
--- a/docs/api-guide/reverse.md
+++ b/docs/api-guide/reverse.md
@@ -54,5 +54,5 @@ As with the `reverse` function, you should **include the request as a keyword ar
api_root = reverse_lazy('api-root', request=request)
[cite]: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5
-[reverse]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse
-[reverse-lazy]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse-lazy
+[reverse]: https://docs.djangoproject.com/en/stable/ref/urlresolvers/#reverse
+[reverse-lazy]: https://docs.djangoproject.com/en/stable/ref/urlresolvers/#reverse-lazy
diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md
index bb8466a2c..2a1e3e6b3 100644
--- a/docs/api-guide/validators.md
+++ b/docs/api-guide/validators.md
@@ -53,7 +53,7 @@ If we open up the Django shell using `manage.py shell` we can now
The interesting bit here is the `reference` field. We can see that the uniqueness constraint is being explicitly enforced by a validator on the serializer field.
-Because of this more explicit style REST framework includes a few validator classes that are not available in core Django. These classes are detailed below.
+Because of this more explicit style REST framework includes a few validator classes that are not available in core Django. These classes are detailed below. REST framework validators, like their Django counterparts, implement the `__eq__` method, allowing you to compare instances for equality.
---
@@ -295,13 +295,14 @@ To write a class-based validator, use the `__call__` method. Class-based validat
In some advanced cases you might want a validator to be passed the serializer
field it is being used with as additional context. You can do so by setting
-a `requires_context = True` attribute on the validator. The `__call__` method
+a `requires_context = True` attribute on the validator class. The `__call__` method
will then be called with the `serializer_field`
or `serializer` as an additional argument.
- requires_context = True
+ class MultipleOf:
+ requires_context = True
- def __call__(self, value, serializer_field):
- ...
+ def __call__(self, value, serializer_field):
+ ...
[cite]: https://docs.djangoproject.com/en/stable/ref/validators/
diff --git a/docs/community/3.12-announcement.md b/docs/community/3.12-announcement.md
index 4a589e39c..3bfeb6576 100644
--- a/docs/community/3.12-announcement.md
+++ b/docs/community/3.12-announcement.md
@@ -143,6 +143,16 @@ class PublisherSearchView(generics.ListAPIView):
---
+## Deprecations
+
+### `serializers.NullBooleanField`
+
+`serializers.NullBooleanField` is now pending deprecation, and will be removed in 3.14.
+
+Instead use `serializers.BooleanField` field and set `allow_null=True` which does the same thing.
+
+---
+
## Funding
REST framework is a *collaboratively funded project*. If you use
diff --git a/docs/community/3.14-announcement.md b/docs/community/3.14-announcement.md
index 0543d0d6d..e7b10ede1 100644
--- a/docs/community/3.14-announcement.md
+++ b/docs/community/3.14-announcement.md
@@ -60,3 +60,13 @@ See Pull Request [#7522](https://github.com/encode/django-rest-framework/pull/75
## Minor fixes and improvements
There are a number of minor fixes and improvements in this release. See the [release notes](release-notes.md) page for a complete listing.
+
+---
+
+## Deprecations
+
+### `serializers.NullBooleanField`
+
+`serializers.NullBooleanField` was moved to pending deprecation in 3.12, and deprecated in 3.13. It has now been removed from the core framework.
+
+Instead use `serializers.BooleanField` field and set `allow_null=True` which does the same thing.
diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md
index 887cae3b4..fba7f63d6 100644
--- a/docs/community/release-notes.md
+++ b/docs/community/release-notes.md
@@ -157,6 +157,7 @@ Date: 28th September 2020
* Fix `PrimaryKeyRelatedField` and `HyperlinkedRelatedField` when source field is actually a property. [#7142]
* `Token.generate_key` is now a class method. [#7502]
* `@action` warns if method is wrapped in a decorator that does not preserve information using `@functools.wraps`. [#7098]
+* Deprecate `serializers.NullBooleanField` in favour of `serializers.BooleanField` with `allow_null=True` [#7122]
---
diff --git a/docs/index.md b/docs/index.md
index cab5511ac..ad241c0a3 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -86,7 +86,7 @@ continued development by **[signing up for a paid plan][funding]**.
REST framework requires the following:
* Python (3.6, 3.7, 3.8, 3.9, 3.10, 3.11)
-* Django (2.2, 3.0, 3.1, 3.2, 4.0, 4.1)
+* Django (3.0, 3.1, 3.2, 4.0, 4.1)
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.
diff --git a/requirements/requirements-packaging.txt b/requirements/requirements-packaging.txt
index a9733185b..81f22a35a 100644
--- a/requirements/requirements-packaging.txt
+++ b/requirements/requirements-packaging.txt
@@ -1,8 +1,8 @@
# Wheel for PyPI installs.
-wheel>=0.35.1,<0.36
+wheel>=0.36.2,<0.40.0
# Twine for secured PyPI uploads.
-twine>=3.2.0,<3.3
+twine>=3.4.2,<4.0.2
# Transifex client for managing translation resources.
transifex-client
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index cc24ce46c..b9e3f9817 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -13,7 +13,7 @@ __title__ = 'Django REST framework'
__version__ = '3.14.0'
__author__ = 'Tom Christie'
__license__ = 'BSD 3-Clause'
-__copyright__ = 'Copyright 2011-2019 Encode OSS Ltd'
+__copyright__ = 'Copyright 2011-2023 Encode OSS Ltd'
# Version synonym
VERSION = __version__
@@ -31,3 +31,7 @@ if django.VERSION < (3, 2):
class RemovedInDRF315Warning(DeprecationWarning):
pass
+
+
+class RemovedInDRF317Warning(PendingDeprecationWarning):
+ pass
diff --git a/rest_framework/authtoken/admin.py b/rest_framework/authtoken/admin.py
index e41eb0002..163328eb0 100644
--- a/rest_framework/authtoken/admin.py
+++ b/rest_framework/authtoken/admin.py
@@ -4,6 +4,7 @@ from django.contrib.admin.views.main import ChangeList
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
from rest_framework.authtoken.models import Token, TokenProxy
@@ -23,6 +24,8 @@ class TokenChangeList(ChangeList):
class TokenAdmin(admin.ModelAdmin):
list_display = ('key', 'user', 'created')
fields = ('user',)
+ search_fields = ('user__username',)
+ search_help_text = _('Username')
ordering = ('-created',)
actions = None # Actions not compatible with mapped IDs.
autocomplete_fields = ("user",)
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 613bd325a..4ce9c79c3 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -4,9 +4,9 @@ import datetime
import decimal
import functools
import inspect
+import logging
import re
import uuid
-from collections import OrderedDict
from collections.abc import Mapping
from django.conf import settings
@@ -17,6 +17,7 @@ from django.core.validators import (
MinValueValidator, ProhibitNullCharactersValidator, RegexValidator,
URLValidator, ip_address_validators
)
+from django.db.models import IntegerChoices, TextChoices
from django.forms import FilePathField as DjangoFilePathField
from django.forms import ImageField as DjangoImageField
from django.utils import timezone
@@ -28,15 +29,22 @@ from django.utils.encoding import is_protected_type, smart_str
from django.utils.formats import localize_input, sanitize_separators
from django.utils.ipv6 import clean_ipv6_address
from django.utils.translation import gettext_lazy as _
-from pytz.exceptions import InvalidTimeError
+
+try:
+ import pytz
+except ImportError:
+ pytz = None
from rest_framework import ISO_8601
from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.settings import api_settings
from rest_framework.utils import html, humanize_datetime, json, representation
from rest_framework.utils.formatting import lazy_format
+from rest_framework.utils.timezone import valid_datetime
from rest_framework.validators import ProhibitSurrogateCharactersValidator
+logger = logging.getLogger("rest_framework.fields")
+
class empty:
"""
@@ -109,27 +117,6 @@ def get_attribute(instance, attrs):
return instance
-def set_value(dictionary, keys, value):
- """
- Similar to Python's built in `dictionary[key] = value`,
- but takes a list of nested keys instead of a single key.
-
- set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2}
- set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2}
- set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}}
- """
- if not keys:
- dictionary.update(value)
- return
-
- for key in keys[:-1]:
- if key not in dictionary:
- dictionary[key] = {}
- dictionary = dictionary[key]
-
- dictionary[keys[-1]] = value
-
-
def to_choices_dict(choices):
"""
Convert choices into key/value dicts.
@@ -142,7 +129,7 @@ def to_choices_dict(choices):
# choices = [1, 2, 3]
# choices = [(1, 'First'), (2, 'Second'), (3, 'Third')]
# choices = [('Category', ((1, 'First'), (2, 'Second'))), (3, 'Third')]
- ret = OrderedDict()
+ ret = {}
for choice in choices:
if not isinstance(choice, (list, tuple)):
# single choice
@@ -165,7 +152,7 @@ def flatten_choices_dict(choices):
flatten_choices_dict({1: '1st', 2: '2nd'}) -> {1: '1st', 2: '2nd'}
flatten_choices_dict({'Group': {1: '1st', 2: '2nd'}}) -> {1: '1st', 2: '2nd'}
"""
- ret = OrderedDict()
+ ret = {}
for key, value in choices.items():
if isinstance(value, dict):
# grouped choices (category, sub choices)
@@ -677,22 +664,27 @@ class BooleanField(Field):
default_empty_html = False
initial = False
TRUE_VALUES = {
- 't', 'T',
- 'y', 'Y', 'yes', 'Yes', 'YES',
- 'true', 'True', 'TRUE',
- 'on', 'On', 'ON',
- '1', 1,
- True
+ 't',
+ 'y',
+ 'yes',
+ 'true',
+ 'on',
+ '1',
+ 1,
+ True,
}
FALSE_VALUES = {
- 'f', 'F',
- 'n', 'N', 'no', 'No', 'NO',
- 'false', 'False', 'FALSE',
- 'off', 'Off', 'OFF',
- '0', 0, 0.0,
- False
+ 'f',
+ 'n',
+ 'no',
+ 'false',
+ 'off',
+ '0',
+ 0,
+ 0.0,
+ False,
}
- NULL_VALUES = {'null', 'Null', 'NULL', '', None}
+ NULL_VALUES = {'null', '', None}
def __init__(self, **kwargs):
if kwargs.get('allow_null', False):
@@ -700,22 +692,28 @@ class BooleanField(Field):
self.initial = None
super().__init__(**kwargs)
+ @staticmethod
+ def _lower_if_str(value):
+ if isinstance(value, str):
+ return value.lower()
+ return value
+
def to_internal_value(self, data):
with contextlib.suppress(TypeError):
- if data in self.TRUE_VALUES:
+ if self._lower_if_str(data) in self.TRUE_VALUES:
return True
- elif data in self.FALSE_VALUES:
+ elif self._lower_if_str(data) in self.FALSE_VALUES:
return False
- elif data in self.NULL_VALUES and self.allow_null:
+ elif self._lower_if_str(data) in self.NULL_VALUES and self.allow_null:
return None
- self.fail('invalid', input=data)
+ self.fail("invalid", input=data)
def to_representation(self, value):
- if value in self.TRUE_VALUES:
+ if self._lower_if_str(value) in self.TRUE_VALUES:
return True
- elif value in self.FALSE_VALUES:
+ elif self._lower_if_str(value) in self.FALSE_VALUES:
return False
- if value in self.NULL_VALUES and self.allow_null:
+ if self._lower_if_str(value) in self.NULL_VALUES and self.allow_null:
return None
return bool(value)
@@ -989,6 +987,11 @@ class DecimalField(Field):
self.max_value = max_value
self.min_value = min_value
+ if self.max_value is not None and not isinstance(self.max_value, decimal.Decimal):
+ logger.warning("max_value in DecimalField should be Decimal type.")
+ if self.min_value is not None and not isinstance(self.min_value, decimal.Decimal):
+ logger.warning("min_value in DecimalField should be Decimal type.")
+
if self.max_digits is not None and self.decimal_places is not None:
self.max_whole_digits = self.max_digits - self.decimal_places
else:
@@ -1154,9 +1157,16 @@ class DateTimeField(Field):
except OverflowError:
self.fail('overflow')
try:
- return timezone.make_aware(value, field_timezone)
- except InvalidTimeError:
- self.fail('make_aware', timezone=field_timezone)
+ dt = timezone.make_aware(value, field_timezone)
+ # When the resulting datetime is a ZoneInfo instance, it won't necessarily
+ # throw given an invalid datetime, so we need to specifically check.
+ if not valid_datetime(dt):
+ self.fail('make_aware', timezone=field_timezone)
+ return dt
+ except Exception as e:
+ if pytz and isinstance(e, pytz.exceptions.InvalidTimeError):
+ self.fail('make_aware', timezone=field_timezone)
+ raise e
elif (field_timezone is None) and timezone.is_aware(value):
return timezone.make_naive(value, datetime.timezone.utc)
return value
@@ -1392,6 +1402,10 @@ class ChoiceField(Field):
if data == '' and self.allow_blank:
return ''
+ if isinstance(data, (IntegerChoices, TextChoices)) and str(data) != \
+ str(data.value):
+ data = data.value
+
try:
return self.choice_strings_to_values[str(data)]
except KeyError:
@@ -1400,6 +1414,11 @@ class ChoiceField(Field):
def to_representation(self, value):
if value in ('', None):
return value
+
+ if isinstance(value, (IntegerChoices, TextChoices)) and str(value) != \
+ str(value.value):
+ value = value.value
+
return self.choice_strings_to_values.get(str(value), value)
def iter_options(self):
@@ -1423,7 +1442,8 @@ class ChoiceField(Field):
# Allows us to deal with eg. integer choices while supporting either
# integer or string input, but still get the correct datatype out.
self.choice_strings_to_values = {
- str(key): key for key in self.choices
+ str(key.value) if isinstance(key, (IntegerChoices, TextChoices))
+ and str(key) != str(key.value) else str(key): key for key in self.choices
}
choices = property(_get_choices, _set_choices)
@@ -1643,7 +1663,7 @@ class ListField(Field):
def run_child_validation(self, data):
result = []
- errors = OrderedDict()
+ errors = {}
for idx, item in enumerate(data):
try:
@@ -1707,7 +1727,7 @@ class DictField(Field):
def run_child_validation(self, data):
result = {}
- errors = OrderedDict()
+ errors = {}
for key, value in data.items():
key = str(key)
diff --git a/rest_framework/filters.py b/rest_framework/filters.py
index 1ffd9edc0..17e6975eb 100644
--- a/rest_framework/filters.py
+++ b/rest_framework/filters.py
@@ -3,6 +3,7 @@ Provides generic filtering backends that can be used to filter the results
returned by list views.
"""
import operator
+import warnings
from functools import reduce
from django.core.exceptions import ImproperlyConfigured
@@ -12,6 +13,7 @@ from django.template import loader
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
+from rest_framework import RemovedInDRF317Warning
from rest_framework.compat import coreapi, coreschema, distinct
from rest_framework.settings import api_settings
@@ -29,6 +31,8 @@ class BaseFilterBackend:
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+ if coreapi is not None:
+ warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning)
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
return []
@@ -146,6 +150,8 @@ class SearchFilter(BaseFilterBackend):
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+ if coreapi is not None:
+ warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning)
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
return [
coreapi.Field(
@@ -306,6 +312,8 @@ class OrderingFilter(BaseFilterBackend):
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+ if coreapi is not None:
+ warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning)
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
return [
coreapi.Field(
diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py
index 015587897..364ca5b14 100644
--- a/rest_framework/metadata.py
+++ b/rest_framework/metadata.py
@@ -6,8 +6,6 @@ some fairly ad-hoc information about the view.
Future implementations might use JSON schema or other definitions in order
to return this information in a more standardized way.
"""
-from collections import OrderedDict
-
from django.core.exceptions import PermissionDenied
from django.http import Http404
from django.utils.encoding import force_str
@@ -59,11 +57,12 @@ class SimpleMetadata(BaseMetadata):
})
def determine_metadata(self, request, view):
- metadata = OrderedDict()
- metadata['name'] = view.get_view_name()
- metadata['description'] = view.get_view_description()
- metadata['renders'] = [renderer.media_type for renderer in view.renderer_classes]
- metadata['parses'] = [parser.media_type for parser in view.parser_classes]
+ metadata = {
+ "name": view.get_view_name(),
+ "description": view.get_view_description(),
+ "renders": [renderer.media_type for renderer in view.renderer_classes],
+ "parses": [parser.media_type for parser in view.parser_classes],
+ }
if hasattr(view, 'get_serializer'):
actions = self.determine_actions(request, view)
if actions:
@@ -106,25 +105,27 @@ class SimpleMetadata(BaseMetadata):
# If this is a `ListSerializer` then we want to examine the
# underlying child serializer instance instead.
serializer = serializer.child
- return OrderedDict([
- (field_name, self.get_field_info(field))
+ return {
+ field_name: self.get_field_info(field)
for field_name, field in serializer.fields.items()
if not isinstance(field, serializers.HiddenField)
- ])
+ }
def get_field_info(self, field):
"""
Given an instance of a serializer field, return a dictionary
of metadata about it.
"""
- field_info = OrderedDict()
- field_info['type'] = self.label_lookup[field]
- field_info['required'] = getattr(field, 'required', False)
+ field_info = {
+ "type": self.label_lookup[field],
+ "required": getattr(field, "required", False),
+ }
attrs = [
'read_only', 'label', 'help_text',
'min_length', 'max_length',
- 'min_value', 'max_value'
+ 'min_value', 'max_value',
+ 'max_digits', 'decimal_places'
]
for attr in attrs:
diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index f5c5b913b..7303890b0 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -4,16 +4,19 @@ be used for paginated responses.
"""
import contextlib
+import warnings
from base64 import b64decode, b64encode
-from collections import OrderedDict, namedtuple
+from collections import namedtuple
from urllib import parse
from django.core.paginator import InvalidPage
from django.core.paginator import Paginator as DjangoPaginator
+from django.db.models import Q
from django.template import loader
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
+from rest_framework import RemovedInDRF317Warning
from rest_framework.compat import coreapi, coreschema
from rest_framework.exceptions import NotFound
from rest_framework.response import Response
@@ -151,6 +154,8 @@ class BasePagination:
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+ if coreapi is not None:
+ warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning)
return []
def get_schema_operation_parameters(self, view):
@@ -224,12 +229,12 @@ class PageNumberPagination(BasePagination):
return page_number
def get_paginated_response(self, data):
- return Response(OrderedDict([
- ('count', self.page.paginator.count),
- ('next', self.get_next_link()),
- ('previous', self.get_previous_link()),
- ('results', data)
- ]))
+ return Response({
+ 'count': self.page.paginator.count,
+ 'next': self.get_next_link(),
+ 'previous': self.get_previous_link(),
+ 'results': data,
+ })
def get_paginated_response_schema(self, schema):
return {
@@ -310,6 +315,8 @@ class PageNumberPagination(BasePagination):
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+ if coreapi is not None:
+ warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning)
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
fields = [
coreapi.Field(
@@ -394,12 +401,12 @@ class LimitOffsetPagination(BasePagination):
return list(queryset[self.offset:self.offset + self.limit])
def get_paginated_response(self, data):
- return Response(OrderedDict([
- ('count', self.count),
- ('next', self.get_next_link()),
- ('previous', self.get_previous_link()),
- ('results', data)
- ]))
+ return Response({
+ 'count': self.count,
+ 'next': self.get_next_link(),
+ 'previous': self.get_previous_link(),
+ 'results': data
+ })
def get_paginated_response_schema(self, schema):
return {
@@ -524,6 +531,8 @@ class LimitOffsetPagination(BasePagination):
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+ if coreapi is not None:
+ warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning)
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
return [
coreapi.Field(
@@ -620,7 +629,7 @@ class CursorPagination(BasePagination):
queryset = queryset.order_by(*self.ordering)
# If we have a cursor with a fixed position then filter by that.
- if current_position is not None:
+ if str(current_position) != 'None':
order = self.ordering[0]
is_reversed = order.startswith('-')
order_attr = order.lstrip('-')
@@ -631,7 +640,12 @@ class CursorPagination(BasePagination):
else:
kwargs = {order_attr + '__gt': current_position}
- queryset = queryset.filter(**kwargs)
+ filter_query = Q(**kwargs)
+ # If some records contain a null for the ordering field, don't lose them.
+ # When reverse ordering, nulls will come last and need to be included.
+ if (reverse and not is_reversed) or is_reversed:
+ filter_query |= Q(**{order_attr + '__isnull': True})
+ queryset = queryset.filter(filter_query)
# If we have an offset cursor then offset the entire page by that amount.
# We also always fetch an extra item in order to determine if there is a
@@ -704,7 +718,7 @@ class CursorPagination(BasePagination):
# The item in this position and the item following it
# have different positions. We can use this position as
# our marker.
- has_item_with_unique_position = True
+ has_item_with_unique_position = position is not None
break
# The item in this position has the same position as the item
@@ -757,7 +771,7 @@ class CursorPagination(BasePagination):
# The item in this position and the item following it
# have different positions. We can use this position as
# our marker.
- has_item_with_unique_position = True
+ has_item_with_unique_position = position is not None
break
# The item in this position has the same position as the item
@@ -795,6 +809,10 @@ class CursorPagination(BasePagination):
"""
Return a tuple of strings, that may be used in an `order_by` method.
"""
+ # The default case is to check for an `ordering` attribute
+ # on this pagination instance.
+ ordering = self.ordering
+
ordering_filters = [
filter_cls for filter_cls in getattr(view, 'filter_backends', [])
if hasattr(filter_cls, 'get_ordering')
@@ -805,26 +823,19 @@ class CursorPagination(BasePagination):
# then we defer to that filter to determine the ordering.
filter_cls = ordering_filters[0]
filter_instance = filter_cls()
- ordering = filter_instance.get_ordering(request, queryset, view)
- assert ordering is not None, (
- 'Using cursor pagination, but filter class {filter_cls} '
- 'returned a `None` ordering.'.format(
- filter_cls=filter_cls.__name__
- )
- )
- else:
- # The default case is to check for an `ordering` attribute
- # on this pagination instance.
- ordering = self.ordering
- assert ordering is not None, (
- 'Using cursor pagination, but no ordering attribute was declared '
- 'on the pagination class.'
- )
- assert '__' not in ordering, (
- 'Cursor pagination does not support double underscore lookups '
- 'for orderings. Orderings should be an unchanging, unique or '
- 'nearly-unique field on the model, such as "-created" or "pk".'
- )
+ ordering_from_filter = filter_instance.get_ordering(request, queryset, view)
+ if ordering_from_filter:
+ ordering = ordering_from_filter
+
+ assert ordering is not None, (
+ 'Using cursor pagination, but no ordering attribute was declared '
+ 'on the pagination class.'
+ )
+ assert '__' not in ordering, (
+ 'Cursor pagination does not support double underscore lookups '
+ 'for orderings. Orderings should be an unchanging, unique or '
+ 'nearly-unique field on the model, such as "-created" or "pk".'
+ )
assert isinstance(ordering, (str, list, tuple)), (
'Invalid ordering. Expected string or tuple, but got {type}'.format(
@@ -883,14 +894,14 @@ class CursorPagination(BasePagination):
attr = instance[field_name]
else:
attr = getattr(instance, field_name)
- return str(attr)
+ return None if attr is None else str(attr)
def get_paginated_response(self, data):
- return Response(OrderedDict([
- ('next', self.get_next_link()),
- ('previous', self.get_previous_link()),
- ('results', data)
- ]))
+ return Response({
+ 'next': self.get_next_link(),
+ 'previous': self.get_previous_link(),
+ 'results': data,
+ })
def get_paginated_response_schema(self, schema):
return {
@@ -927,6 +938,8 @@ class CursorPagination(BasePagination):
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
+ if coreapi is not None:
+ warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning)
assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`'
fields = [
coreapi.Field(
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 62da685fb..4409bce77 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -1,6 +1,6 @@
import contextlib
import sys
-from collections import OrderedDict
+from operator import attrgetter
from urllib import parse
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
@@ -71,6 +71,7 @@ class PKOnlyObject:
instance, but still want to return an object with a .pk attribute,
in order to keep the same interface as a regular model instance.
"""
+
def __init__(self, pk):
self.pk = pk
@@ -197,13 +198,9 @@ class RelatedField(Field):
if cutoff is not None:
queryset = queryset[:cutoff]
- return OrderedDict([
- (
- self.to_representation(item),
- self.display_value(item)
- )
- for item in queryset
- ])
+ return {
+ self.to_representation(item): self.display_value(item) for item in queryset
+ }
@property
def choices(self):
@@ -464,7 +461,11 @@ class SlugRelatedField(RelatedField):
self.fail('invalid')
def to_representation(self, obj):
- return getattr(obj, self.slug_field)
+ slug = self.slug_field
+ if "__" in slug:
+ # handling nested relationship if defined
+ slug = slug.replace('__', '.')
+ return attrgetter(slug)(obj)
class ManyRelatedField(Field):
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 8de0a77a1..db1fdd128 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -9,7 +9,7 @@ REST framework also provides an HTML renderer that renders the browsable API.
import base64
import contextlib
-from collections import OrderedDict
+import datetime
from urllib import parse
from django import forms
@@ -507,6 +507,9 @@ class BrowsableAPIRenderer(BaseRenderer):
return self.render_form_for_serializer(serializer)
def render_form_for_serializer(self, serializer):
+ if isinstance(serializer, serializers.ListSerializer):
+ return None
+
if hasattr(serializer, 'initial_data'):
serializer.is_valid()
@@ -556,10 +559,13 @@ class BrowsableAPIRenderer(BaseRenderer):
context['indent'] = 4
# strip HiddenField from output
+ is_list_serializer = isinstance(serializer, serializers.ListSerializer)
+ serializer = serializer.child if is_list_serializer else serializer
data = serializer.data.copy()
for name, field in serializer.fields.items():
if isinstance(field, serializers.HiddenField):
data.pop(name, None)
+ data = [data] if is_list_serializer else data
content = renderer.render(data, accepted, context)
# Renders returns bytes, but CharField expects a str.
content = content.decode()
@@ -653,7 +659,7 @@ class BrowsableAPIRenderer(BaseRenderer):
raw_data_patch_form = self.get_raw_data_form(data, view, 'PATCH', request)
raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form
- response_headers = OrderedDict(sorted(response.items()))
+ response_headers = dict(sorted(response.items()))
renderer_content_type = ''
if renderer:
renderer_content_type = '%s' % renderer.media_type
@@ -1057,6 +1063,7 @@ class OpenAPIRenderer(BaseRenderer):
def ignore_aliases(self, data):
return True
Dumper.add_representer(SafeString, Dumper.represent_str)
+ Dumper.add_representer(datetime.timedelta, encoders.CustomScalar.represent_timedelta)
return yaml.dump(data, default_flow_style=False, sort_keys=False, Dumper=Dumper).encode('utf-8')
diff --git a/rest_framework/routers.py b/rest_framework/routers.py
index 722fc50a6..fa5d16922 100644
--- a/rest_framework/routers.py
+++ b/rest_framework/routers.py
@@ -14,7 +14,7 @@ For example, you might have a `urls.py` that looks something like this:
urlpatterns = router.urls
"""
import itertools
-from collections import OrderedDict, namedtuple
+from collections import namedtuple
from django.core.exceptions import ImproperlyConfigured
from django.urls import NoReverseMatch, path, re_path
@@ -321,7 +321,7 @@ class APIRootView(views.APIView):
def get(self, request, *args, **kwargs):
# Return a plain {"name": "hyperlink"} response.
- ret = OrderedDict()
+ ret = {}
namespace = request.resolver_match.namespace
for key, url_name in self.api_root_dict.items():
if namespace:
@@ -365,7 +365,7 @@ class DefaultRouter(SimpleRouter):
"""
Return a basic root view.
"""
- api_root_dict = OrderedDict()
+ api_root_dict = {}
list_name = self.routes[0].name
for prefix, viewset, basename in self.registry:
api_root_dict[prefix] = list_name.format(basename=basename)
diff --git a/rest_framework/schemas/coreapi.py b/rest_framework/schemas/coreapi.py
index 908518e9d..582aba196 100644
--- a/rest_framework/schemas/coreapi.py
+++ b/rest_framework/schemas/coreapi.py
@@ -1,11 +1,11 @@
import warnings
-from collections import Counter, OrderedDict
+from collections import Counter
from urllib import parse
from django.db import models
from django.utils.encoding import force_str
-from rest_framework import exceptions, serializers
+from rest_framework import RemovedInDRF317Warning, exceptions, serializers
from rest_framework.compat import coreapi, coreschema, uritemplate
from rest_framework.settings import api_settings
@@ -54,7 +54,7 @@ to customise schema structure.
"""
-class LinkNode(OrderedDict):
+class LinkNode(dict):
def __init__(self):
self.links = []
self.methods_counter = Counter()
@@ -118,6 +118,8 @@ class SchemaGenerator(BaseSchemaGenerator):
def __init__(self, title=None, url=None, description=None, patterns=None, urlconf=None, version=None):
assert coreapi, '`coreapi` must be installed for schema support.'
+ if coreapi is not None:
+ warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning)
assert coreschema, '`coreschema` must be installed for schema support.'
super().__init__(title, url, description, patterns, urlconf)
@@ -268,11 +270,11 @@ def field_to_schema(field):
)
elif isinstance(field, serializers.Serializer):
return coreschema.Object(
- properties=OrderedDict([
- (key, field_to_schema(value))
+ properties={
+ key: field_to_schema(value)
for key, value
in field.fields.items()
- ]),
+ },
title=title,
description=description
)
@@ -351,6 +353,9 @@ class AutoSchema(ViewInspector):
will be added to auto-generated fields, overwriting on `Field.name`
"""
super().__init__()
+ if coreapi is not None:
+ warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning)
+
if manual_fields is None:
manual_fields = []
self._manual_fields = manual_fields
@@ -549,7 +554,7 @@ class AutoSchema(ViewInspector):
if not update_with:
return fields
- by_name = OrderedDict((f.name, f) for f in fields)
+ by_name = {f.name: f for f in fields}
for f in update_with:
by_name[f.name] = f
fields = list(by_name.values())
@@ -592,6 +597,9 @@ class ManualSchema(ViewInspector):
* `description`: String description for view. Optional.
"""
super().__init__()
+ if coreapi is not None:
+ warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning)
+
assert all(isinstance(f, coreapi.Field) for f in fields), "`fields` must be a list of coreapi.Field instances"
self._fields = fields
self._description = description
@@ -613,4 +621,6 @@ class ManualSchema(ViewInspector):
def is_enabled():
"""Is CoreAPI Mode enabled?"""
+ if coreapi is not None:
+ warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning)
return issubclass(api_settings.DEFAULT_SCHEMA_CLASS, AutoSchema)
diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py
index ea220c631..d8707e1e1 100644
--- a/rest_framework/schemas/openapi.py
+++ b/rest_framework/schemas/openapi.py
@@ -1,6 +1,5 @@
import re
import warnings
-from collections import OrderedDict
from decimal import Decimal
from operator import attrgetter
from urllib.parse import urljoin
@@ -340,7 +339,7 @@ class AutoSchema(ViewInspector):
return paginator.get_schema_operation_parameters(view)
def map_choicefield(self, field):
- choices = list(OrderedDict.fromkeys(field.choices)) # preserve order and remove duplicates
+ choices = list(dict.fromkeys(field.choices)) # preserve order and remove duplicates
if all(isinstance(choice, bool) for choice in choices):
type = 'boolean'
elif all(isinstance(choice, int) for choice in choices):
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 815e97e29..976a34836 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -15,7 +15,7 @@ import contextlib
import copy
import inspect
import traceback
-from collections import OrderedDict, defaultdict
+from collections import defaultdict
from collections.abc import Mapping
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
@@ -315,7 +315,7 @@ class SerializerMetaclass(type):
for name, f in base._declared_fields.items() if name not in known
]
- return OrderedDict(base_fields + fields)
+ return dict(base_fields + fields)
def __new__(cls, name, bases, attrs):
attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs)
@@ -353,6 +353,26 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.')
}
+ def set_value(self, dictionary, keys, value):
+ """
+ Similar to Python's built in `dictionary[key] = value`,
+ but takes a list of nested keys instead of a single key.
+
+ set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2}
+ set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2}
+ set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}}
+ """
+ if not keys:
+ dictionary.update(value)
+ return
+
+ for key in keys[:-1]:
+ if key not in dictionary:
+ dictionary[key] = {}
+ dictionary = dictionary[key]
+
+ dictionary[keys[-1]] = value
+
@cached_property
def fields(self):
"""
@@ -400,20 +420,20 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
if hasattr(self, 'initial_data'):
# initial_data may not be a valid type
if not isinstance(self.initial_data, Mapping):
- return OrderedDict()
+ return {}
- return OrderedDict([
- (field_name, field.get_value(self.initial_data))
+ return {
+ field_name: field.get_value(self.initial_data)
for field_name, field in self.fields.items()
if (field.get_value(self.initial_data) is not empty) and
not field.read_only
- ])
+ }
- return OrderedDict([
- (field.field_name, field.get_initial())
+ return {
+ field.field_name: field.get_initial()
for field in self.fields.values()
if not field.read_only
- ])
+ }
def get_value(self, dictionary):
# We override the default field access in order to support
@@ -448,7 +468,7 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
if (field.read_only) and (field.default != empty) and (field.source != '*') and ('.' not in field.source)
]
- defaults = OrderedDict()
+ defaults = {}
for field in fields:
try:
default = field.get_default()
@@ -481,8 +501,8 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='invalid')
- ret = OrderedDict()
- errors = OrderedDict()
+ ret = {}
+ errors = {}
fields = self._writable_fields
for field in fields:
@@ -499,7 +519,7 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
except SkipField:
pass
else:
- set_value(ret, field.source_attrs, validated_value)
+ self.set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
@@ -510,7 +530,7 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
"""
Object instance -> Dict of primitive datatypes.
"""
- ret = OrderedDict()
+ ret = {}
fields = self._readable_fields
for field in fields:
@@ -596,6 +616,12 @@ class ListSerializer(BaseSerializer):
self.min_length = kwargs.pop('min_length', None)
assert self.child is not None, '`child` is a required argument.'
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
+
+ instance = kwargs.get('instance', [])
+ data = kwargs.get('data', [])
+ if instance and data:
+ assert len(data) == len(instance), 'Data and instance should have same length'
+
super().__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
@@ -670,7 +696,13 @@ class ListSerializer(BaseSerializer):
ret = []
errors = []
- for item in data:
+ for idx, item in enumerate(data):
+ if (
+ hasattr(self, 'instance')
+ and self.instance
+ and len(self.instance) > idx
+ ):
+ self.child.instance = self.instance[idx]
try:
validated = self.child.run_validation(item)
except ValidationError as exc:
@@ -1068,7 +1100,7 @@ class ModelSerializer(Serializer):
)
# Determine the fields that should be included on the serializer.
- fields = OrderedDict()
+ fields = {}
for field_name in field_names:
# If the field is explicitly declared on the class then use that.
@@ -1553,16 +1585,16 @@ class ModelSerializer(Serializer):
# which may map onto a model field. Any dotted field name lookups
# cannot map to a field, and must be a traversal, so we're not
# including those.
- field_sources = OrderedDict(
- (field.field_name, field.source) for field in self._writable_fields
+ field_sources = {
+ field.field_name: field.source for field in self._writable_fields
if (field.source != '*') and ('.' not in field.source)
- )
+ }
# Special Case: Add read_only fields with defaults.
- field_sources.update(OrderedDict(
- (field.field_name, field.source) for field in self.fields.values()
+ field_sources.update({
+ field.field_name: field.source for field in self.fields.values()
if (field.read_only) and (field.default != empty) and (field.source != '*') and ('.' not in field.source)
- ))
+ })
# Invert so we can find the serializer field names that correspond to
# the model field names in the unique_together sets. This also allows
diff --git a/rest_framework/static/rest_framework/js/jquery-3.6.4.min.js b/rest_framework/static/rest_framework/js/jquery-3.6.4.min.js
new file mode 100644
index 000000000..0de648ed3
--- /dev/null
+++ b/rest_framework/static/rest_framework/js/jquery-3.6.4.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.6.4 | (c) OpenJS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),v={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.4",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.cssHas=ce(function(){try{return C.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),d.cssHas||y.push(":has"),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType&&e.documentElement||e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"