diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..2d9e2c5ee --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Keep GitHub Actions up to date with GitHub's Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + groups: + github-actions: + patterns: + - "*" # Group all Action updates into a single larger pull request + schedule: + interval: weekly diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 48b6e7202..c3c587cbf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,11 +20,12 @@ jobs: - '3.9' - '3.10' - '3.11' + - '3.12' steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' @@ -61,9 +62,9 @@ jobs: name: Test documentation links runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.9' diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 36d356493..892235175 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -11,14 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.10" - - uses: pre-commit/action@v3.0.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} + - uses: pre-commit/action@v3.0.1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 64ec37770..8939dd3db 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -9,19 +9,25 @@ repos: - id: check-symlinks - id: check-toml - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: 3.9.0 + rev: 7.0.0 hooks: - id: flake8 additional_dependencies: - flake8-tidy-imports - repo: https://github.com/adamchainz/blacken-docs - rev: 1.13.0 + rev: 1.16.0 hooks: - id: blacken-docs exclude: ^(?!docs).*$ additional_dependencies: - black==23.1.0 +- repo: https://github.com/codespell-project/codespell + # Configuration for codespell is in .codespellrc + rev: v2.2.6 + hooks: + - id: codespell + exclude: locale|kickstarter-announcement.md|coreapi-0.1.1.js diff --git a/README.md b/README.md index 0146b9e22..56933a4c8 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements * Python 3.6+ -* Django 5.0, 4.2, 4.1, 4.0, 3.2, 3.1, 3.0. +* Django 5.0, 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/fields.md b/docs/api-guide/fields.md index 0766fe3ef..ea944e1ff 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -303,7 +303,7 @@ Corresponds to `django.db.models.fields.DecimalField`. * `min_value` Validate that the number provided is no less than this value. * `localize` Set to `True` to enable localization of input and output based on the current locale. This will also force `coerce_to_string` to `True`. Defaults to `False`. Note that data formatting is enabled if you have set `USE_L10N=True` in your settings file. * `rounding` Sets the rounding mode used when quantizing to the configured precision. Valid values are [`decimal` module rounding modes][python-decimal-rounding-modes]. Defaults to `None`. -* `normalize_output` Will normalize the decimal value when serialized. This will strip all trailing zeroes and change the value's precision to the minimum required precision to be able to represent the value without loosing data. Defaults to `False`. +* `normalize_output` Will normalize the decimal value when serialized. This will strip all trailing zeroes and change the value's precision to the minimum required precision to be able to represent the value without losing data. Defaults to `False`. #### Example usage diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index 2a93e4cdc..e181d4c61 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -173,11 +173,9 @@ If you want the date field to be entirely hidden from the user, then use `Hidden # Advanced field defaults Validators that are applied across multiple fields in the serializer can sometimes require a field input that should not be provided by the API client, but that *is* available as input to the validator. +For this purposes use `HiddenField`. This field will be present in `validated_data` but *will not* be used in the serializer output representation. -Two patterns that you may want to use for this sort of validation include: - -* Using `HiddenField`. This field will be present in `validated_data` but *will not* be used in the serializer output representation. -* Using a standard field with `read_only=True`, but that also includes a `default=…` argument. This field *will* be used in the serializer output representation, but cannot be set directly by the user. +**Note:** Using a `read_only=True` field is excluded from writable fields so it won't use a `default=…` argument. Look [3.8 announcement](https://www.django-rest-framework.org/community/3.8-announcement/#altered-the-behaviour-of-read_only-plus-default-on-field). REST framework includes a couple of defaults that may be useful in this context. @@ -189,7 +187,7 @@ A default class that can be used to represent the current user. In order to use default=serializers.CurrentUserDefault() ) -#### CreateOnlyDefault +#### CreateOnlyDefault A default class that can be used to *only set a default argument during create operations*. During updates the field is omitted. diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 41ba1743c..43007e95d 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -311,7 +311,7 @@ You may need to provide custom `ViewSet` classes that do not have the full set o To create a base viewset class that provides `create`, `list` and `retrieve` operations, inherit from `GenericViewSet`, and mixin the required actions: - from rest_framework import mixins + from rest_framework import mixins, viewsets class CreateListRetrieveViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, diff --git a/docs/community/3.11-announcement.md b/docs/community/3.11-announcement.md index 5a1f2c8e3..2fc37a764 100644 --- a/docs/community/3.11-announcement.md +++ b/docs/community/3.11-announcement.md @@ -64,7 +64,7 @@ In some circumstances a Validator class or a Default class may need to access th * Uniqueness validators need to be able to determine the name of the field to which they are applied, in order to run an appropriate database query. * The `CurrentUserDefault` needs to be able to determine the context with which the serializer was instantiated, in order to return the current user instance. -Previous our approach to this was that implementations could include a `set_context` method, which would be called prior to validation. However this approach had issues with potential race conditions. We have now move this approach into a pending deprecation state. It will continue to function, but will be escalated to a deprecated state in 3.12, and removed entirely in 3.13. +Our previous approach to this was that implementations could include a `set_context` method, which would be called prior to validation. However this approach had issues with potential race conditions. We have now move this approach into a pending deprecation state. It will continue to function, but will be escalated to a deprecated state in 3.12, and removed entirely in 3.13. Instead, validators or defaults which require the serializer context, should include a `requires_context = True` attribute on the class. diff --git a/docs/community/3.14-announcement.md b/docs/community/3.14-announcement.md index e7b10ede1..991c6fc5a 100644 --- a/docs/community/3.14-announcement.md +++ b/docs/community/3.14-announcement.md @@ -28,10 +28,10 @@ Our requirements are now: * Python 3.6+ * Django 4.1, 4.0, 3.2, 3.1, 3.0 -## `raise_exceptions` argument for `is_valid` is now keyword-only. +## `raise_exception` argument for `is_valid` is now keyword-only. Calling `serializer_instance.is_valid(True)` is no longer acceptable syntax. -If you'd like to use the `raise_exceptions` argument, you must use it as a +If you'd like to use the `raise_exception` argument, you must use it as a keyword argument. See Pull Request [#7952](https://github.com/encode/django-rest-framework/pull/7952) for more details. diff --git a/docs/community/3.5-announcement.md b/docs/community/3.5-announcement.md index 91bfce428..43a628dd4 100644 --- a/docs/community/3.5-announcement.md +++ b/docs/community/3.5-announcement.md @@ -64,14 +64,10 @@ from rest_framework.schemas import get_schema_view from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer schema_view = get_schema_view( - title='Example API', - renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer] + title="Example API", renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer] ) -urlpatterns = [ - path('swagger/', schema_view), - ... -] +urlpatterns = [path("swagger/", schema_view), ...] ``` There have been a large number of fixes to the schema generation. These should diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index aa4018228..ab591db3b 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -131,7 +131,7 @@ Date: 22nd September 2022 * Stop calling `set_context` on Validators. [[#8589](https://github.com/encode/django-rest-framework/pull/8589)] * Return `NotImplemented` from `ErrorDetails.__ne__`. [[#8538](https://github.com/encode/django-rest-framework/pull/8538)] * Don't evaluate `DateTimeField.default_timezone` when a custom timezone is set. [[#8531](https://github.com/encode/django-rest-framework/pull/8531)] -* Make relative URLs clickable in Browseable API. [[#8464](https://github.com/encode/django-rest-framework/pull/8464)] +* Make relative URLs clickable in Browsable API. [[#8464](https://github.com/encode/django-rest-framework/pull/8464)] * Support `ManyRelatedField` falling back to the default value when the attribute specified by dot notation doesn't exist. Matches `ManyRelatedField.get_attribute` to `Field.get_attribute`. [[#7574](https://github.com/encode/django-rest-framework/pull/7574)] * Make `schemas.openapi.get_reference` public. [[#7515](https://github.com/encode/django-rest-framework/pull/7515)] * Make `ReturnDict` support `dict` union operators on Python 3.9 and later. [[#8302](https://github.com/encode/django-rest-framework/pull/8302)] @@ -149,7 +149,7 @@ Date: 15th December 2021 Date: 13th December 2021 -* Django 4.0 compatability. [#8178] +* Django 4.0 compatibility. [#8178] * Add `max_length` and `min_length` options to `ListSerializer`. [#8165] * Add `get_request_serializer` and `get_response_serializer` hooks to `AutoSchema`. [#7424] * Fix OpenAPI representation of null-able read only fields. [#8116] @@ -1030,7 +1030,7 @@ See the [release announcement][3.6-release]. * description.py codes and tests removal. ([#4153][gh4153]) * Wrap guardian.VERSION in tuple. ([#4149][gh4149]) * Refine validator for fields with kwargs. ([#4146][gh4146]) -* Fix None values representation in childs of ListField, DictField. ([#4118][gh4118]) +* Fix None values representation in children of ListField, DictField. ([#4118][gh4118]) * Resolve TimeField representation for midnight value. ([#4107][gh4107]) * Set proper status code in AdminRenderer for the redirection after POST/DELETE requests. ([#4106][gh4106]) * TimeField render returns None instead of 00:00:00. ([#4105][gh4105]) @@ -1038,7 +1038,7 @@ See the [release announcement][3.6-release]. * Prevent raising exception when limit is 0. ([#4098][gh4098]) * TokenAuthentication: Allow custom keyword in the header. ([#4097][gh4097]) * Handle incorrectly padded HTTP basic auth header. ([#4090][gh4090]) -* LimitOffset pagination crashes Browseable API when limit=0. ([#4079][gh4079]) +* LimitOffset pagination crashes Browsable API when limit=0. ([#4079][gh4079]) * Fixed DecimalField arbitrary precision support. ([#4075][gh4075]) * Added support for custom CSRF cookie names. ([#4049][gh4049]) * Fix regression introduced by #4035. ([#4041][gh4041]) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 2304dfe45..3a4ba5848 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -152,6 +152,11 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [drf-standardized-errors][drf-standardized-errors] - DRF exception handler to standardize error responses for all API endpoints. * [drf-api-action][drf-api-action] - uses the power of DRF also as a library functions +### Customization + +* [drf-redesign][drf-redesign] - A project that gives a fresh look to the browse-able API using Bootstrap 5. +* [drf-material][drf-material] - A project that gives a sleek and elegant look to the browsable API using Material Design. + [cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework [new-repo]: https://github.com/new @@ -243,3 +248,5 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [django-requestlogs]: https://github.com/Raekkeri/django-requestlogs [drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors [drf-api-action]: https://github.com/Ori-Roza/drf-api-action +[drf-redesign]: https://github.com/youzarsiph/drf-redesign +[drf-material]: https://github.com/youzarsiph/drf-material diff --git a/docs/img/rfm.png b/docs/img/rfm.png new file mode 100644 index 000000000..7c82621af Binary files /dev/null and b/docs/img/rfm.png differ diff --git a/docs/img/rfr.png b/docs/img/rfr.png new file mode 100644 index 000000000..1854511d0 Binary files /dev/null and b/docs/img/rfr.png differ diff --git a/docs/index.md b/docs/index.md index a7f1444a3..07d233107 100644 --- a/docs/index.md +++ b/docs/index.md @@ -87,7 +87,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 (3.0, 3.1, 3.2, 4.0, 4.1, 4.2) +* Django (3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0) We **highly recommend** and only officially support the latest patch release of each Python and Django series. diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md index 2cdf2a884..9a95edfc6 100644 --- a/docs/topics/browsable-api.md +++ b/docs/topics/browsable-api.md @@ -15,6 +15,18 @@ If you include fully-qualified URLs in your resource output, they will be 'urliz By default, the API will return the format specified by the headers, which in the case of the browser is HTML. The format can be specified using `?format=` in the request, so you can look at the raw JSON response in a browser by adding `?format=json` to the URL. There are helpful extensions for viewing JSON in [Firefox][ffjsonview] and [Chrome][chromejsonview]. +## Authentication + +To quickly add authentication to the browesable api, add a routes named `"login"` and `"logout"` under the namespace `"rest_framework"`. DRF provides default routes for this which you can add to your urlconf: + +```python +urlpatterns = [ + # ... + url(r"^api-auth/", include("rest_framework.urls", namespace="rest_framework")) +] +``` + + ## Customizing The browsable API is built with [Twitter's Bootstrap][bootstrap] (v 3.4.1), making it easy to customize the look-and-feel. @@ -65,6 +77,27 @@ For more specific CSS tweaks than simply overriding the default bootstrap theme --- +### Third party packages for customization + +You can use a third party package for customization, rather than doing it by yourself. Here is 2 packages for customizing the API: + +* [rest-framework-redesign][rest-framework-redesign] - A package for customizing the API using Bootstrap 5. Modern and sleek design, it comes with the support for dark mode. +* [rest-framework-material][rest-framework-material] - Material design for Django REST Framework. + +--- + +![Django REST Framework Redesign][rfr] + +*Screenshot of the rest-framework-redesign* + +--- + +![Django REST Framework Material][rfm] + +*Screenshot of the rest-framework-material* + +--- + ### Blocks All of the blocks available in the browsable API base template that can be used in your `api.html`. @@ -162,3 +195,7 @@ There are [a variety of packages for autocomplete widgets][autocomplete-packages [bcomponentsnav]: https://getbootstrap.com/2.3.2/components.html#navbar [autocomplete-packages]: https://www.djangopackages.com/grids/g/auto-complete/ [django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light +[rest-framework-redesign]: https://github.com/youzarsiph/rest-framework-redesign +[rest-framework-material]: https://github.com/youzarsiph/rest-framework-material +[rfr]: ../img/rfr.png +[rfm]: ../img/rfm.png diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index f9b6c5e9a..6fa2111e7 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -10,7 +10,7 @@ A `ViewSet` class is only bound to a set of method handlers at the last moment, Let's take our current set of views, and refactor them into view sets. -First of all let's refactor our `UserList` and `UserDetail` classes into a single `UserViewSet` class. We can remove the two view classes, and replace them with a single ViewSet class: +First of all let's refactor our `UserList` and `UserDetail` classes into a single `UserViewSet` class. In the `snippets/views.py` file, we can remove the two view classes and replace them with a single ViewSet class: from rest_framework import viewsets diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 09f249f6e..7b46a44e6 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -105,7 +105,7 @@ Right, we'd better write some views then. Open `tutorial/quickstart/views.py` a """ API endpoint that allows groups to be viewed or edited. """ - queryset = Group.objects.all() + queryset = Group.objects.all().order_by('name') serializer_class = GroupSerializer permission_classes = [permissions.IsAuthenticated] @@ -132,8 +132,6 @@ Okay, now let's wire up the API URLs. On to `tutorial/urls.py`... path('', include(router.urls)), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) ] - - urlpatterns += router.urls Because we're using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class. diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 7e80704e1..472b8ad24 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -169,6 +169,21 @@ else: } +if django.VERSION >= (5, 1): + # Django 5.1+: use the stock ip_address_validators function + # Note: Before Django 5.1, ip_address_validators returns a tuple containing + # 1) the list of validators and 2) the error message. Starting from + # Django 5.1 ip_address_validators only returns the list of validators + from django.core.validators import ip_address_validators +else: + # Django <= 5.1: create a compatibility shim for ip_address_validators + from django.core.validators import \ + ip_address_validators as _ip_address_validators + + def ip_address_validators(protocol, unpack_ipv4): + return _ip_address_validators(protocol, unpack_ipv4)[0] + + # `separators` argument to `json.dumps()` differs between 2.x and 3.x # See: https://bugs.python.org/issue22767 SHORT_SEPARATORS = (',', ':') diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 3b572c09e..864ff7395 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -36,7 +36,7 @@ def api_view(http_method_names=None): # WrappedAPIView.__doc__ = func.doc <--- Not possible to do this # api_view applied without (method_names) - assert not(isinstance(http_method_names, types.FunctionType)), \ + assert not isinstance(http_method_names, types.FunctionType), \ '@api_view missing list of allowed HTTP methods' # api_view applied with eg. string instead of list of strings diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 0b56fa7fb..fda656507 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -16,7 +16,7 @@ from django.core.exceptions import ValidationError as DjangoValidationError from django.core.validators import ( EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator, MinValueValidator, ProhibitNullCharactersValidator, RegexValidator, - URLValidator, ip_address_validators + URLValidator ) from django.forms import FilePathField as DjangoFilePathField from django.forms import ImageField as DjangoImageField @@ -36,6 +36,7 @@ except ImportError: pytz = None from rest_framework import ISO_8601 +from rest_framework.compat import ip_address_validators from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.settings import api_settings from rest_framework.utils import html, humanize_datetime, json, representation @@ -866,7 +867,7 @@ class IPAddressField(CharField): self.protocol = protocol.lower() self.unpack_ipv4 = (self.protocol == 'both') super().__init__(**kwargs) - validators, error_message = ip_address_validators(protocol, self.unpack_ipv4) + validators = ip_address_validators(protocol, self.unpack_ipv4) self.validators.extend(validators) def to_internal_value(self, data): diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 065e72f94..86effe24e 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -21,14 +21,14 @@ from rest_framework.settings import api_settings def search_smart_split(search_terms): - """generator that first splits string by spaces, leaving quoted phrases togheter, + """generator that first splits string by spaces, leaving quoted phrases together, then it splits non-quoted phrases by commas. """ for term in smart_split(search_terms): # trim commas to avoid bad matching for quoted phrases term = term.strip(',') if term.startswith(('"', "'")) and term[0] == term[-1]: - # quoted phrases are kept togheter without any other split + # quoted phrases are kept together without any other split yield unescape_string_literal(term) else: # non-quoted tokens are split by comma, keeping only non-empty ones diff --git a/rest_framework/schemas/generators.py b/rest_framework/schemas/generators.py index d3c6446aa..f59e25c21 100644 --- a/rest_framework/schemas/generators.py +++ b/rest_framework/schemas/generators.py @@ -102,12 +102,12 @@ class EndpointEnumerator: Given a URL conf regex, return a URI template string. """ # ???: Would it be feasible to adjust this such that we generate the - # path, plus the kwargs, plus the type from the convertor, such that we + # path, plus the kwargs, plus the type from the converter, such that we # could feed that straight into the parameter schema object? path = simplify_regex(path_regex) - # Strip Django 2.0 convertors as they are incompatible with uritemplate format + # Strip Django 2.0 converters as they are incompatible with uritemplate format return re.sub(_PATH_PARAMETER_COMPONENT_RE, r'{\g}', path) def should_include_endpoint(self, path, callback): diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index ad19f6df8..c154494e2 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -84,7 +84,7 @@ class SchemaGenerator(BaseSchemaGenerator): continue if components_schemas[k] == components[k]: continue - warnings.warn('Schema component "{}" has been overriden with a different value.'.format(k)) + warnings.warn('Schema component "{}" has been overridden with a different value.'.format(k)) components_schemas.update(components) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 77c181b6c..b1b7b6477 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -76,6 +76,7 @@ LIST_SERIALIZER_KWARGS = ( 'instance', 'data', 'partial', 'context', 'allow_null', 'max_length', 'min_length' ) +LIST_SERIALIZER_KWARGS_REMOVE = ('allow_empty', 'min_length', 'max_length') ALL_FIELDS = '__all__' @@ -145,19 +146,12 @@ class BaseSerializer(Field): kwargs['child'] = cls() return CustomListSerializer(*args, **kwargs) """ - allow_empty = kwargs.pop('allow_empty', None) - max_length = kwargs.pop('max_length', None) - min_length = kwargs.pop('min_length', None) - child_serializer = cls(*args, **kwargs) - list_kwargs = { - 'child': child_serializer, - } - if allow_empty is not None: - list_kwargs['allow_empty'] = allow_empty - if max_length is not None: - list_kwargs['max_length'] = max_length - if min_length is not None: - list_kwargs['min_length'] = min_length + list_kwargs = {} + for key in LIST_SERIALIZER_KWARGS_REMOVE: + value = kwargs.pop(key, None) + if value is not None: + list_kwargs[key] = value + list_kwargs['child'] = cls(*args, **kwargs) list_kwargs.update({ key: value for key, value in kwargs.items() if key in LIST_SERIALIZER_KWARGS @@ -609,12 +603,6 @@ 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) @@ -700,13 +688,7 @@ class ListSerializer(BaseSerializer): ret = [] errors = [] - for idx, item in enumerate(data): - if ( - hasattr(self, 'instance') - and self.instance - and len(self.instance) > idx - ): - self.child.instance = self.instance[idx] + for item in data: try: validated = self.run_child_validation(item) except ValidationError as exc: diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 96b664574..b0d7bacec 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -116,7 +116,7 @@ DEFAULTS = { 'COERCE_DECIMAL_TO_STRING': True, 'UPLOADED_FILES_USE_URL': True, - # Browseable API + # Browsable API 'HTML_SELECT_CUTOFF': 1000, 'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...", diff --git a/rest_framework/static/rest_framework/js/ajax-form.js b/rest_framework/static/rest_framework/js/ajax-form.js index 1483305ff..dda5454c2 100644 --- a/rest_framework/static/rest_framework/js/ajax-form.js +++ b/rest_framework/static/rest_framework/js/ajax-form.js @@ -3,6 +3,12 @@ function replaceDocument(docString) { doc.write(docString); doc.close(); + + if (window.djdt) { + // If Django Debug Toolbar is available, reinitialize it so that + // it can show updated panels from new `docString`. + window.addEventListener("load", djdt.init); + } } function doAjaxSubmit(e) { diff --git a/rest_framework/templates/rest_framework/admin.html b/rest_framework/templates/rest_framework/admin.html index 7c6917e2d..a5edf529e 100644 --- a/rest_framework/templates/rest_framework/admin.html +++ b/rest_framework/templates/rest_framework/admin.html @@ -42,7 +42,7 @@