From 2de50818296b1b4bae68787626c0236752e35101 Mon Sep 17 00:00:00 2001 From: David Cain Date: Thu, 22 Sep 2022 11:07:43 -0700 Subject: [PATCH 001/178] Use correct class to indicate present deprecation (#8665) `PendingDeprecationWarning` means "we plan to deprecate, but haven't yet." A feature that's to be deleted in the next release is not planned to be deprecated; it **is** deprecated. > Base class for warnings about features which are obsolete and expected > to be deprecated in the future, but are not deprecated at the moment. > > This class is rarely used as emitting a warning about a possible > upcoming deprecation is unusual, and DeprecationWarning is preferred for > already active deprecations. https://docs.python.org/3/library/exceptions.html#PendingDeprecationWarning Co-authored-by: Tom Christie --- rest_framework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index d53b2cb4de..cc24ce46c5 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -29,5 +29,5 @@ default_app_config = 'rest_framework.apps.RestFrameworkConfig' -class RemovedInDRF315Warning(PendingDeprecationWarning): +class RemovedInDRF315Warning(DeprecationWarning): pass From 73f4835a531d7a192946ab8da9e06ef184cb1119 Mon Sep 17 00:00:00 2001 From: Ahzam Ahmed <62069509+AhzamAhmed6@users.noreply.github.com> Date: Mon, 26 Sep 2022 17:05:53 +0500 Subject: [PATCH 002/178] Unnecessary list comprehension (#8670) --- rest_framework/schemas/inspectors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/schemas/inspectors.py b/rest_framework/schemas/inspectors.py index 027472db14..722fc9b2ba 100644 --- a/rest_framework/schemas/inspectors.py +++ b/rest_framework/schemas/inspectors.py @@ -88,7 +88,7 @@ def get_description(self, path, method): view.get_view_description()) def _get_description_section(self, view, header, description): - lines = [line for line in description.splitlines()] + lines = list(description.splitlines()) current_section = '' sections = {'': ''} From 3e51ba4d519e95691f57d3eda714acd49f24dc67 Mon Sep 17 00:00:00 2001 From: Allie Date: Tue, 27 Sep 2022 05:54:52 -0700 Subject: [PATCH 003/178] Update documentation on dependency installation (#8566) We depend on pytz, but until late last year we got it implicitly through depending on Django. Since their release 4.0, however, they no longer depend on pytz; commit 250479dc3 added the dependency directly to our metadata in setup.py, but the documentation about dependencies (most importantly, the instructions for new contributors) was left untouched. This commit updates the new contributor instructions to suggest an "editable installation" of the project at the step that previously had users manually install Django. In this mode, pip fetches and installs the project dependencies automatically (so in the unlikely event we grow another dependency, that doc doesn't need to be changed again) and makes the project available to the virtualenv's python as a normal package, but doesn't require reinstallation for mundane edits. --- docs/community/contributing.md | 2 +- requirements.txt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/community/contributing.md b/docs/community/contributing.md index 2232bd10b9..994226b97a 100644 --- a/docs/community/contributing.md +++ b/docs/community/contributing.md @@ -80,7 +80,7 @@ To run the tests, clone the repository, and then: # Setup the virtual environment python3 -m venv env source env/bin/activate - pip install django + pip install -e . pip install -r requirements.txt # Run the tests diff --git a/requirements.txt b/requirements.txt index 395f3b7a86..c3a1f1187c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ # The base set of requirements for REST framework is actually -# just Django, but for the purposes of development and testing -# there are a number of packages that are useful to install. +# just Django and pytz, but for the purposes of development +# and testing there are a number of packages that are useful +# to install. # Laying these out as separate requirements files, allows us to # only included the relevant sets when running tox, and ensures From 9e398c59ab0e91346267c3fcac75ebd97d5b3346 Mon Sep 17 00:00:00 2001 From: Ahzam Ahmed <62069509+AhzamAhmed6@users.noreply.github.com> Date: Tue, 27 Sep 2022 20:08:40 +0500 Subject: [PATCH 004/178] Minor refactor: Unnecessary use of list() function (#8672) --- rest_framework/schemas/inspectors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/schemas/inspectors.py b/rest_framework/schemas/inspectors.py index 722fc9b2ba..cb880e79d6 100644 --- a/rest_framework/schemas/inspectors.py +++ b/rest_framework/schemas/inspectors.py @@ -88,7 +88,7 @@ def get_description(self, path, method): view.get_view_description()) def _get_description_section(self, view, header, description): - lines = list(description.splitlines()) + lines = description.splitlines() current_section = '' sections = {'': ''} From 79de112d627d9a84e302d54849e95dd7ab677a43 Mon Sep 17 00:00:00 2001 From: manelbdacosta <30473446+manelbdcosta@users.noreply.github.com> Date: Mon, 3 Oct 2022 10:36:51 +0100 Subject: [PATCH 005/178] Minor fix to SerializeMethodField docstring (#8629) --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6374c1ea98..a6924bf238 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1814,7 +1814,7 @@ class SerializerMethodField(Field): For example: - class ExampleSerializer(self): + class ExampleSerializer(Serializer): extra_info = SerializerMethodField() def get_extra_info(self, obj): From ca75300ae9048c8c90d1e57935d22ec99ffbbac7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 3 Oct 2022 11:39:32 +0100 Subject: [PATCH 006/178] Update requirements-testing.txt (#8680) * Update requirements-testing.txt * Update requirements-testing.txt --- requirements/requirements-testing.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt index 313fdedc9b..f6823e2e27 100644 --- a/requirements/requirements-testing.txt +++ b/requirements/requirements-testing.txt @@ -2,3 +2,4 @@ pytest>=6.1,<7.0 pytest-cov>=2.10.1,<3.0 pytest-django>=4.1.0,<5.0 +importlib-metadata<5.0 From e7777cb1ee98c0215a5f1f082a88290eefa3e293 Mon Sep 17 00:00:00 2001 From: Sardorbek Imomaliev Date: Mon, 3 Oct 2022 15:48:18 +0500 Subject: [PATCH 007/178] Add spaces to router example in 6-viewsets-and-routers.md (#8448) --- docs/tutorial/6-viewsets-and-routers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index e12becbd06..b7a7696a72 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -112,8 +112,8 @@ Here's our re-wired `snippets/urls.py` file. # Create a router and register our viewsets with it. router = DefaultRouter() - router.register(r'snippets', views.SnippetViewSet,basename="snippet") - router.register(r'users', views.UserViewSet,basename="user") + router.register(r'snippets', views.SnippetViewSet, basename='snippet') + router.register(r'users', views.UserViewSet, basename='user') # The API URLs are now determined automatically by the router. urlpatterns = [ From 99cf2c415f5a556e924b04421ad7f92f4c62aa09 Mon Sep 17 00:00:00 2001 From: mschmidm <66086922+mschmidm@users.noreply.github.com> Date: Tue, 4 Oct 2022 13:15:51 +0200 Subject: [PATCH 008/178] Docs: Updated browsable-api.md (#8678) - Replace the broken Bootswatch-Link with an Jsdelivr-Link as suggested at https://bootswatch.com/help/ - Updated the stated Bootstrap version - Added a note that the Bootstrap version must match the default one Co-authored-by: Tom Christie --- docs/topics/browsable-api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md index ed70c49018..2cdf2a884c 100644 --- a/docs/topics/browsable-api.md +++ b/docs/topics/browsable-api.md @@ -17,7 +17,7 @@ By default, the API will return the format specified by the headers, which in th ## Customizing -The browsable API is built with [Twitter's Bootstrap][bootstrap] (v 3.3.5), making it easy to customize the look-and-feel. +The browsable API is built with [Twitter's Bootstrap][bootstrap] (v 3.4.1), making it easy to customize the look-and-feel. To customize the default style, create a template called `rest_framework/api.html` that extends from `rest_framework/base.html`. For example: @@ -35,7 +35,7 @@ To replace the default theme, add a `bootstrap_theme` block to your `api.html` a {% endblock %} -Suitable pre-made replacement themes are available at [Bootswatch][bswatch]. To use any of the Bootswatch themes, simply download the theme's `bootstrap.min.css` file, add it to your project, and replace the default one as described above. +Suitable pre-made replacement themes are available at [Bootswatch][bswatch]. To use any of the Bootswatch themes, simply download the theme's `bootstrap.min.css` file, add it to your project, and replace the default one as described above. Make sure that the Bootstrap version of the new theme matches that of the default theme. You can also change the navbar variant, which by default is `navbar-inverse`, using the `bootstrap_navbar_variant` block. The empty `{% block bootstrap_navbar_variant %}{% endblock %}` will use the original Bootstrap navbar style. @@ -44,7 +44,7 @@ Full example: {% extends "rest_framework/base.html" %} {% block bootstrap_theme %} - + {% endblock %} {% block bootstrap_navbar_variant %}{% endblock %} From c10f2266222c434485889b08cc1463acdb8fa169 Mon Sep 17 00:00:00 2001 From: Ahzam Ahmed Date: Wed, 5 Oct 2022 15:02:00 +0500 Subject: [PATCH 009/178] Refactor: Replace try/except with contextlib.suppress() (#8676) --- rest_framework/fields.py | 20 +++++++------------- rest_framework/pagination.py | 17 +++++------------ rest_framework/parsers.py | 13 +++++-------- rest_framework/relations.py | 6 ++---- rest_framework/renderers.py | 12 ++++-------- rest_framework/serializers.py | 6 +++--- rest_framework/utils/encoders.py | 6 +++--- rest_framework/utils/serializer_helpers.py | 5 ++--- 8 files changed, 31 insertions(+), 54 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index a6924bf238..7f4c83b5df 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1,3 +1,4 @@ +import contextlib import copy import datetime import decimal @@ -690,15 +691,13 @@ class BooleanField(Field): NULL_VALUES = {'null', 'Null', 'NULL', '', None} def to_internal_value(self, data): - try: + with contextlib.suppress(TypeError): if data in self.TRUE_VALUES: return True elif data in self.FALSE_VALUES: return False elif data in self.NULL_VALUES and self.allow_null: return None - except TypeError: # Input is an unhashable type - pass self.fail('invalid', input=data) def to_representation(self, value): @@ -1158,19 +1157,14 @@ def to_internal_value(self, value): return self.enforce_timezone(value) for input_format in input_formats: - if input_format.lower() == ISO_8601: - try: + with contextlib.suppress(ValueError, TypeError): + if input_format.lower() == ISO_8601: parsed = parse_datetime(value) if parsed is not None: return self.enforce_timezone(parsed) - except (ValueError, TypeError): - pass - else: - try: - parsed = self.datetime_parser(value, input_format) - return self.enforce_timezone(parsed) - except (ValueError, TypeError): - pass + + parsed = self.datetime_parser(value, input_format) + return self.enforce_timezone(parsed) humanized_format = humanize_datetime.datetime_formats(input_formats) self.fail('invalid', format=humanized_format) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index e815d8d5cf..186e61d9b6 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -2,6 +2,8 @@ Pagination serializers determine the structure of the output that should be used for paginated responses. """ + +import contextlib from base64 import b64decode, b64encode from collections import OrderedDict, namedtuple from urllib import parse @@ -257,15 +259,12 @@ def get_paginated_response_schema(self, schema): def get_page_size(self, request): if self.page_size_query_param: - try: + with contextlib.suppress(KeyError, ValueError): return _positive_int( request.query_params[self.page_size_query_param], strict=True, cutoff=self.max_page_size ) - except (KeyError, ValueError): - pass - return self.page_size def get_next_link(self): @@ -430,15 +429,12 @@ def get_paginated_response_schema(self, schema): def get_limit(self, request): if self.limit_query_param: - try: + with contextlib.suppress(KeyError, ValueError): return _positive_int( request.query_params[self.limit_query_param], strict=True, cutoff=self.max_limit ) - except (KeyError, ValueError): - pass - return self.default_limit def get_offset(self, request): @@ -680,15 +676,12 @@ def paginate_queryset(self, queryset, request, view=None): def get_page_size(self, request): if self.page_size_query_param: - try: + with contextlib.suppress(KeyError, ValueError): return _positive_int( request.query_params[self.page_size_query_param], strict=True, cutoff=self.max_page_size ) - except (KeyError, ValueError): - pass - return self.page_size def get_next_link(self): diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 4ee8e578b8..f0fd2b8844 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -4,7 +4,9 @@ They give us a generic way of being able to handle various media types on the request, such as form content or json encoded data. """ + import codecs +import contextlib from django.conf import settings from django.core.files.uploadhandler import StopFutureHandlers @@ -193,17 +195,12 @@ def get_filename(self, stream, media_type, parser_context): Detects the uploaded file name. First searches a 'filename' url kwarg. Then tries to parse Content-Disposition header. """ - try: + with contextlib.suppress(KeyError): return parser_context['kwargs']['filename'] - except KeyError: - pass - try: + with contextlib.suppress(AttributeError, KeyError, ValueError): meta = parser_context['request'].META disposition, params = parse_header_parameters(meta['HTTP_CONTENT_DISPOSITION']) if 'filename*' in params: return params['filename*'] - else: - return params['filename'] - except (AttributeError, KeyError, ValueError): - pass + return params['filename'] diff --git a/rest_framework/relations.py b/rest_framework/relations.py index bdedd43b86..62da685fb6 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -1,3 +1,4 @@ +import contextlib import sys from collections import OrderedDict from urllib import parse @@ -170,7 +171,7 @@ def use_pk_only_optimization(self): def get_attribute(self, instance): if self.use_pk_only_optimization() and self.source_attrs: # Optimized case, return a mock object only containing the pk attribute. - try: + with contextlib.suppress(AttributeError): attribute_instance = get_attribute(instance, self.source_attrs[:-1]) value = attribute_instance.serializable_value(self.source_attrs[-1]) if is_simple_callable(value): @@ -183,9 +184,6 @@ def get_attribute(self, instance): value = getattr(value, 'pk', value) return PKOnlyObject(pk=value) - except AttributeError: - pass - # Standard case, return the object instance. return super().get_attribute(instance) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index b74df9a0bb..3f47580f15 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -6,7 +6,9 @@ REST framework also provides an HTML renderer that renders the browsable API. """ + import base64 +import contextlib from collections import OrderedDict from urllib import parse @@ -72,11 +74,8 @@ def get_indent(self, accepted_media_type, renderer_context): # then pretty print the result. # Note that we coerce `indent=0` into `indent=None`. base_media_type, params = parse_header_parameters(accepted_media_type) - try: + with contextlib.suppress(KeyError, ValueError, TypeError): return zero_as_none(max(min(int(params['indent']), 8), 0)) - except (KeyError, ValueError, TypeError): - pass - # If 'indent' is provided in the context, then pretty print the result. # E.g. If we're being called by the BrowsableAPIRenderer. return renderer_context.get('indent', None) @@ -488,11 +487,8 @@ def get_rendered_html_form(self, data, view, method, request): return if existing_serializer is not None: - try: + with contextlib.suppress(TypeError): return self.render_form_for_serializer(existing_serializer) - except TypeError: - pass - if has_serializer: if method in ('PUT', 'PATCH'): serializer = view.get_serializer(instance=instance, **kwargs) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 083910174b..7037c07cdc 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -10,6 +10,8 @@ 2. The process of marshalling between python primitives and request and response content is handled by parsers and renderers. """ + +import contextlib import copy import inspect import traceback @@ -1496,12 +1498,10 @@ def _get_model_fields(self, field_names, declared_fields, extra_kwargs): # they can't be nested attribute lookups. continue - try: + with contextlib.suppress(FieldDoesNotExist): field = model._meta.get_field(source) if isinstance(field, DjangoModelField): model_fields[source] = field - except FieldDoesNotExist: - pass return model_fields diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 27293b7252..35a89eb090 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -1,6 +1,8 @@ """ Helper classes for parsers. """ + +import contextlib import datetime import decimal import json # noqa @@ -58,10 +60,8 @@ def default(self, obj): ) elif hasattr(obj, '__getitem__'): cls = (list if isinstance(obj, (list, tuple)) else dict) - try: + with contextlib.suppress(Exception): return cls(obj) - except Exception: - pass elif hasattr(obj, '__iter__'): return tuple(item for item in obj) return super().default(obj) diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index 54068f5fb0..6ca794584b 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -1,3 +1,4 @@ +import contextlib import sys from collections import OrderedDict from collections.abc import Mapping, MutableMapping @@ -103,15 +104,13 @@ def as_form_field(self): # When HTML form input is used and the input is not valid # value will be a JSONString, rather than a JSON primitive. if not getattr(value, 'is_json_string', False): - try: + with contextlib.suppress(TypeError, ValueError): value = json.dumps( self.value, sort_keys=True, indent=4, separators=(',', ': '), ) - except (TypeError, ValueError): - pass return self.__class__(self._field, value, self.errors, self._prefix) From d507cd851c4b1185e3dc720de9ba6a642459d738 Mon Sep 17 00:00:00 2001 From: Markus Legner Date: Fri, 7 Oct 2022 12:58:38 +0200 Subject: [PATCH 010/178] Fix infinite recursion with deepcopy on Request (#8684) --- rest_framework/request.py | 3 ++- tests/test_request.py | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/rest_framework/request.py b/rest_framework/request.py index 93634e667d..194be5f6d4 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -413,7 +413,8 @@ def __getattr__(self, attr): to proxy it to the underlying HttpRequest object. """ try: - return getattr(self._request, attr) + _request = self.__getattribute__("_request") + return getattr(_request, attr) except AttributeError: return self.__getattribute__(attr) diff --git a/tests/test_request.py b/tests/test_request.py index 8f55d00ed4..8c18aea9e6 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -1,6 +1,7 @@ """ Tests for content parsing, and form-overloaded content parsing. """ +import copy import os.path import tempfile @@ -344,3 +345,10 @@ def test_duplicate_request_form_data_access(self): # ensure that request stream was consumed by form parser assert request.content_type.startswith('multipart/form-data') assert response.data == {'a': ['b']} + + +class TestDeepcopy(TestCase): + + def test_deepcopy_works(self): + request = Request(factory.get('/', secure=False)) + copy.deepcopy(request) From 911b207fa13f7e1cd65f62a7b6b16fb4a9854098 Mon Sep 17 00:00:00 2001 From: Norbert Schuler Date: Tue, 11 Oct 2022 12:50:33 +0200 Subject: [PATCH 011/178] Added examples to schema of CursorPagination (#8687) * Added examples to schema of CursorPagination Fix https://github.com/encode/django-rest-framework/issues/8686 Added missing examples for CursorPagination class to disable warnings in https://github.com/tfranzel/drf-spectacular and make it consistent with other pagination classes. * Adapted test case for paginated response schema --- rest_framework/pagination.py | 6 ++++++ tests/test_pagination.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 186e61d9b6..8c434ea4f2 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -898,10 +898,16 @@ def get_paginated_response_schema(self, schema): 'next': { 'type': 'string', 'nullable': True, + 'format': 'uri', + 'example': 'http://api.example.org/accounts/?{cursor_query_param}=cD00ODY%3D"'.format( + cursor_query_param=self.cursor_query_param) }, 'previous': { 'type': 'string', 'nullable': True, + 'format': 'uri', + 'example': 'http://api.example.org/accounts/?{cursor_query_param}=cj0xJnA9NDg3'.format( + cursor_query_param=self.cursor_query_param) }, 'results': schema, }, diff --git a/tests/test_pagination.py b/tests/test_pagination.py index c028f0ea8b..5d7864700d 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -922,10 +922,14 @@ def test_get_paginated_response_schema(self): 'next': { 'type': 'string', 'nullable': True, + 'format': 'uri', + 'example': 'http://api.example.org/accounts/?cursor=cD00ODY%3D"' }, 'previous': { 'type': 'string', 'nullable': True, + 'format': 'uri', + 'example': 'http://api.example.org/accounts/?cursor=cj0xJnA9NDg3' }, 'results': unpaginated_schema, }, From 56946fac8f29aa44ce84391f138d63c4c8a2a285 Mon Sep 17 00:00:00 2001 From: Klaas van Schelven Date: Tue, 11 Oct 2022 14:48:57 +0200 Subject: [PATCH 012/178] Preserve exception messages for wrapped Django exceptions (#8051) * Preserve messages for wrapped Django exceptions * Fix the test * Update test_generics.py * Update test_generics.py Co-authored-by: Tom Christie --- rest_framework/views.py | 4 ++-- tests/test_generics.py | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/rest_framework/views.py b/rest_framework/views.py index 5b06220691..4c30029fdc 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -79,9 +79,9 @@ def exception_handler(exc, context): to be raised. """ if isinstance(exc, Http404): - exc = exceptions.NotFound() + exc = exceptions.NotFound(*(exc.args)) elif isinstance(exc, PermissionDenied): - exc = exceptions.PermissionDenied() + exc = exceptions.PermissionDenied(*(exc.args)) if isinstance(exc, exceptions.APIException): headers = {} diff --git a/tests/test_generics.py b/tests/test_generics.py index 2907d27733..78dc5afb64 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -5,6 +5,7 @@ from django.test import TestCase from rest_framework import generics, renderers, serializers, status +from rest_framework.exceptions import ErrorDetail from rest_framework.response import Response from rest_framework.test import APIRequestFactory from tests.models import ( @@ -519,7 +520,12 @@ def test_get_instance_view_filters_out_name_with_filter_backend(self): request = factory.get('/1') response = instance_view(request, pk=1).render() assert response.status_code == status.HTTP_404_NOT_FOUND - assert response.data == {'detail': 'Not found.'} + assert response.data == { + 'detail': ErrorDetail( + string='No BasicModel matches the given query.', + code='not_found' + ) + } def test_get_instance_view_will_return_single_object_when_filter_does_not_exclude_it(self): """ From 20d347a806baa0dd481b31a7f847386574882d5b Mon Sep 17 00:00:00 2001 From: Shyamkumar Yadav <46707211+shyamkumaryadav@users.noreply.github.com> Date: Wed, 12 Oct 2022 15:53:45 +0530 Subject: [PATCH 013/178] Docs: Convert all tabs into spaces (#8692) --- docs/api-guide/authentication.md | 2 +- docs/api-guide/content-negotiation.md | 2 +- docs/api-guide/reverse.md | 18 +++++++++--------- docs/api-guide/serializers.md | 10 +++++----- docs/api-guide/throttling.md | 4 ++-- docs/community/3.12-announcement.md | 10 +++++----- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index d30b6fed8c..5fa87c0312 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -338,7 +338,7 @@ If the `.authenticate_header()` method is not overridden, the authentication sch The following example will authenticate any incoming request as the user given by the username in a custom request header named 'X-USERNAME'. - from django.contrib.auth.models import User + from django.contrib.auth.models import User from rest_framework import authentication from rest_framework import exceptions diff --git a/docs/api-guide/content-negotiation.md b/docs/api-guide/content-negotiation.md index 3a4b0357fa..81cff6a89b 100644 --- a/docs/api-guide/content-negotiation.md +++ b/docs/api-guide/content-negotiation.md @@ -82,7 +82,7 @@ The default content negotiation class may be set globally, using the `DEFAULT_CO You can also set the content negotiation used for an individual view, or viewset, using the `APIView` class-based views. - from myapp.negotiation import IgnoreClientContentNegotiation + from myapp.negotiation import IgnoreClientContentNegotiation from rest_framework.response import Response from rest_framework.views import APIView diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md index 70df42b8f0..ba37a0f667 100644 --- a/docs/api-guide/reverse.md +++ b/docs/api-guide/reverse.md @@ -32,16 +32,16 @@ You should **include the request as a keyword argument** to the function, for ex from rest_framework.reverse import reverse from rest_framework.views import APIView - from django.utils.timezone import now - - class APIRootView(APIView): - def get(self, request): - year = now().year - data = { - ... - 'year-summary-url': reverse('year-summary', args=[year], request=request) + from django.utils.timezone import now + + class APIRootView(APIView): + def get(self, request): + year = now().year + data = { + ... + 'year-summary-url': reverse('year-summary', args=[year], request=request) } - return Response(data) + return Response(data) ## reverse_lazy diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 91a9d63f30..ca120768e1 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -910,7 +910,7 @@ We can now use this class to serialize single `HighScore` instances: def high_score(request, pk): instance = HighScore.objects.get(pk=pk) serializer = HighScoreSerializer(instance) - return Response(serializer.data) + return Response(serializer.data) Or use it to serialize multiple instances: @@ -918,7 +918,7 @@ Or use it to serialize multiple instances: def all_high_scores(request): queryset = HighScore.objects.order_by('-score') serializer = HighScoreSerializer(queryset, many=True) - return Response(serializer.data) + return Response(serializer.data) #### Read-write `BaseSerializer` classes @@ -949,8 +949,8 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd 'player_name': 'May not be more than 10 characters.' }) - # Return the validated values. This will be available as - # the `.validated_data` property. + # Return the validated values. This will be available as + # the `.validated_data` property. return { 'score': int(score), 'player_name': player_name @@ -1189,7 +1189,7 @@ The [drf-writable-nested][drf-writable-nested] package provides writable nested ## DRF Encrypt Content -The [drf-encrypt-content][drf-encrypt-content] package helps you encrypt your data, serialized through ModelSerializer. It also contains some helper functions. Which helps you to encrypt your data. +The [drf-encrypt-content][drf-encrypt-content] package helps you encrypt your data, serialized through ModelSerializer. It also contains some helper functions. Which helps you to encrypt your data. [cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index b875221978..4c58fa713f 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -50,9 +50,9 @@ The rate descriptions used in `DEFAULT_THROTTLE_RATES` may include `second`, `mi You can also set the throttling policy on a per-view or per-viewset basis, using the `APIView` class-based views. - from rest_framework.response import Response + from rest_framework.response import Response from rest_framework.throttling import UserRateThrottle - from rest_framework.views import APIView + from rest_framework.views import APIView class ExampleView(APIView): throttle_classes = [UserRateThrottle] diff --git a/docs/community/3.12-announcement.md b/docs/community/3.12-announcement.md index 9d2220933c..4a589e39c0 100644 --- a/docs/community/3.12-announcement.md +++ b/docs/community/3.12-announcement.md @@ -30,12 +30,12 @@ in the URL path. For example... -Method | Path | Tags +Method | Path | Tags --------------------------------|-----------------|------------- -`GET`, `PUT`, `PATCH`, `DELETE` | `/users/{id}/` | `['users']` -`GET`, `POST` | `/users/` | `['users']` -`GET`, `PUT`, `PATCH`, `DELETE` | `/orders/{id}/` | `['orders']` -`GET`, `POST` | `/orders/` | `['orders']` +`GET`, `PUT`, `PATCH`, `DELETE` | `/users/{id}/` | `['users']` +`GET`, `POST` | `/users/` | `['users']` +`GET`, `PUT`, `PATCH`, `DELETE` | `/orders/{id}/` | `['orders']` +`GET`, `POST` | `/orders/` | `['orders']` The tags used for a particular view may also be overridden... From 1fd268ae2b03e3328ba7923a47c6a912b2bc0166 Mon Sep 17 00:00:00 2001 From: Shyamkumar Yadav <46707211+shyamkumaryadav@users.noreply.github.com> Date: Fri, 14 Oct 2022 15:52:19 +0530 Subject: [PATCH 014/178] Docs: use `asterisk` for unordered list (#8697) --- docs/api-guide/fields.md | 110 ++++++++++++++++++------------------ docs/api-guide/relations.md | 8 +-- 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index ab1847c0c9..e5b200bf6a 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -78,7 +78,7 @@ Defaults to `False` ### `source` -The name of the attribute that will be used to populate the field. May be a method that only takes a `self` argument, such as `URLField(source='get_absolute_url')`, or may use dotted notation to traverse attributes, such as `EmailField(source='user.email')`. +The name of the attribute that will be used to populate the field. May be a method that only takes a `self` argument, such as `URLField(source='get_absolute_url')`, or may use dotted notation to traverse attributes, such as `EmailField(source='user.email')`. When serializing fields with dotted notation, it may be necessary to provide a `default` value if any object is not present or is empty during attribute traversal. Beware of possible n+1 problems when using source attribute if you are accessing a relational orm model. For example: @@ -171,10 +171,10 @@ Corresponds to `django.db.models.fields.CharField` or `django.db.models.fields.T **Signature:** `CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)` -- `max_length` - Validates that the input contains no more than this number of characters. -- `min_length` - Validates that the input contains no fewer than this number of characters. -- `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. -- `trim_whitespace` - If set to `True` then leading and trailing whitespace is trimmed. Defaults to `True`. +* `max_length` - Validates that the input contains no more than this number of characters. +* `min_length` - Validates that the input contains no fewer than this number of characters. +* `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. +* `trim_whitespace` - If set to `True` then leading and trailing whitespace is trimmed. Defaults to `True`. The `allow_null` option is also available for string fields, although its usage is discouraged in favor of `allow_blank`. It is valid to set both `allow_blank=True` and `allow_null=True`, but doing so means that there will be two differing types of empty value permissible for string representations, which can lead to data inconsistencies and subtle application bugs. @@ -222,11 +222,11 @@ A field that ensures the input is a valid UUID string. The `to_internal_value` m **Signature:** `UUIDField(format='hex_verbose')` -- `format`: Determines the representation format of the uuid value - - `'hex_verbose'` - The canonical hex representation, including hyphens: `"5ce0e9a5-5ffa-654b-cee0-1238041fb31a"` - - `'hex'` - The compact hex representation of the UUID, not including hyphens: `"5ce0e9a55ffa654bcee01238041fb31a"` - - `'int'` - A 128 bit integer representation of the UUID: `"123456789012312313134124512351145145114"` - - `'urn'` - RFC 4122 URN representation of the UUID: `"urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"` +* `format`: Determines the representation format of the uuid value + * `'hex_verbose'` - The canonical hex representation, including hyphens: `"5ce0e9a5-5ffa-654b-cee0-1238041fb31a"` + * `'hex'` - The compact hex representation of the UUID, not including hyphens: `"5ce0e9a55ffa654bcee01238041fb31a"` + * `'int'` - A 128 bit integer representation of the UUID: `"123456789012312313134124512351145145114"` + * `'urn'` - RFC 4122 URN representation of the UUID: `"urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"` Changing the `format` parameters only affects representation values. All formats are accepted by `to_internal_value` ## FilePathField @@ -237,11 +237,11 @@ Corresponds to `django.forms.fields.FilePathField`. **Signature:** `FilePathField(path, match=None, recursive=False, allow_files=True, allow_folders=False, required=None, **kwargs)` -- `path` - The absolute filesystem path to a directory from which this FilePathField should get its choice. -- `match` - A regular expression, as a string, that FilePathField will use to filter filenames. -- `recursive` - Specifies whether all subdirectories of path should be included. Default is `False`. -- `allow_files` - Specifies whether files in the specified location should be included. Default is `True`. Either this or `allow_folders` must be `True`. -- `allow_folders` - Specifies whether folders in the specified location should be included. Default is `False`. Either this or `allow_files` must be `True`. +* `path` - The absolute filesystem path to a directory from which this FilePathField should get its choice. +* `match` - A regular expression, as a string, that FilePathField will use to filter filenames. +* `recursive` - Specifies whether all subdirectories of path should be included. Default is `False`. +* `allow_files` - Specifies whether files in the specified location should be included. Default is `True`. Either this or `allow_folders` must be `True`. +* `allow_folders` - Specifies whether folders in the specified location should be included. Default is `False`. Either this or `allow_files` must be `True`. ## IPAddressField @@ -251,8 +251,8 @@ Corresponds to `django.forms.fields.IPAddressField` and `django.forms.fields.Gen **Signature**: `IPAddressField(protocol='both', unpack_ipv4=False, **options)` -- `protocol` Limits valid inputs to the specified protocol. Accepted values are 'both' (default), 'IPv4' or 'IPv6'. Matching is case insensitive. -- `unpack_ipv4` Unpacks IPv4 mapped addresses like ::ffff:192.0.2.1. If this option is enabled that address would be unpacked to 192.0.2.1. Default is disabled. Can only be used when protocol is set to 'both'. +* `protocol` Limits valid inputs to the specified protocol. Accepted values are 'both' (default), 'IPv4' or 'IPv6'. Matching is case insensitive. +* `unpack_ipv4` Unpacks IPv4 mapped addresses like ::ffff:192.0.2.1. If this option is enabled that address would be unpacked to 192.0.2.1. Default is disabled. Can only be used when protocol is set to 'both'. --- @@ -266,8 +266,8 @@ Corresponds to `django.db.models.fields.IntegerField`, `django.db.models.fields. **Signature**: `IntegerField(max_value=None, min_value=None)` -- `max_value` Validate that the number provided is no greater than this value. -- `min_value` Validate that the number provided is no less than this value. +* `max_value` Validate that the number provided is no greater than this value. +* `min_value` Validate that the number provided is no less than this value. ## FloatField @@ -277,8 +277,8 @@ Corresponds to `django.db.models.fields.FloatField`. **Signature**: `FloatField(max_value=None, min_value=None)` -- `max_value` Validate that the number provided is no greater than this value. -- `min_value` Validate that the number provided is no less than this value. +* `max_value` Validate that the number provided is no greater than this value. +* `min_value` Validate that the number provided is no less than this value. ## DecimalField @@ -288,13 +288,13 @@ Corresponds to `django.db.models.fields.DecimalField`. **Signature**: `DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)` -- `max_digits` The maximum number of digits allowed in the number. It must be either `None` or an integer greater than or equal to `decimal_places`. -- `decimal_places` The number of decimal places to store with the number. -- `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. Note that setting `localize` will force the value to `True`. -- `max_value` Validate that the number provided is no greater than this value. -- `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 quantising to the configured precision. Valid values are [`decimal` module rounding modes][python-decimal-rounding-modes]. Defaults to `None`. +* `max_digits` The maximum number of digits allowed in the number. It must be either `None` or an integer greater than or equal to `decimal_places`. +* `decimal_places` The number of decimal places to store with the number. +* `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. Note that setting `localize` will force the value to `True`. +* `max_value` Validate that the number provided is no greater than this value. +* `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 quantising to the configured precision. Valid values are [`decimal` module rounding modes][python-decimal-rounding-modes]. Defaults to `None`. #### Example usage @@ -380,8 +380,8 @@ The representation is a string following this format `'[DD] [HH:[MM:]]ss[.uuuuuu **Signature:** `DurationField(max_value=None, min_value=None)` -- `max_value` Validate that the duration provided is no greater than this value. -- `min_value` Validate that the duration provided is no less than this value. +* `max_value` Validate that the duration provided is no greater than this value. +* `min_value` Validate that the duration provided is no less than this value. --- @@ -395,10 +395,10 @@ Used by `ModelSerializer` to automatically generate fields if the corresponding **Signature:** `ChoiceField(choices)` -- `choices` - A list of valid values, or a list of `(key, display_name)` tuples. -- `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. -- `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Can be used to ensure that automatically generated ChoiceFields with very large possible selections do not prevent a template from rendering. Defaults to `None`. -- `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"` +* `choices` - A list of valid values, or a list of `(key, display_name)` tuples. +* `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. +* `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Can be used to ensure that automatically generated ChoiceFields with very large possible selections do not prevent a template from rendering. Defaults to `None`. +* `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"` Both the `allow_blank` and `allow_null` are valid options on `ChoiceField`, although it is highly recommended that you only use one and not both. `allow_blank` should be preferred for textual choices, and `allow_null` should be preferred for numeric or other non-textual choices. @@ -408,10 +408,10 @@ A field that can accept a set of zero, one or many values, chosen from a limited **Signature:** `MultipleChoiceField(choices)` -- `choices` - A list of valid values, or a list of `(key, display_name)` tuples. -- `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. -- `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Can be used to ensure that automatically generated ChoiceFields with very large possible selections do not prevent a template from rendering. Defaults to `None`. -- `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"` +* `choices` - A list of valid values, or a list of `(key, display_name)` tuples. +* `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`. +* `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Can be used to ensure that automatically generated ChoiceFields with very large possible selections do not prevent a template from rendering. Defaults to `None`. +* `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"` As with `ChoiceField`, both the `allow_blank` and `allow_null` options are valid, although it is highly recommended that you only use one and not both. `allow_blank` should be preferred for textual choices, and `allow_null` should be preferred for numeric or other non-textual choices. @@ -432,9 +432,9 @@ Corresponds to `django.forms.fields.FileField`. **Signature:** `FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)` - - `max_length` - Designates the maximum length for the file name. - - `allow_empty_file` - Designates if empty files are allowed. -- `use_url` - If set to `True` then URL string values will be used for the output representation. If set to `False` then filename string values will be used for the output representation. Defaults to the value of the `UPLOADED_FILES_USE_URL` settings key, which is `True` unless set otherwise. +* `max_length` - Designates the maximum length for the file name. +* `allow_empty_file` - Designates if empty files are allowed. +* `use_url` - If set to `True` then URL string values will be used for the output representation. If set to `False` then filename string values will be used for the output representation. Defaults to the value of the `UPLOADED_FILES_USE_URL` settings key, which is `True` unless set otherwise. ## ImageField @@ -444,9 +444,9 @@ Corresponds to `django.forms.fields.ImageField`. **Signature:** `ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)` - - `max_length` - Designates the maximum length for the file name. - - `allow_empty_file` - Designates if empty files are allowed. -- `use_url` - If set to `True` then URL string values will be used for the output representation. If set to `False` then filename string values will be used for the output representation. Defaults to the value of the `UPLOADED_FILES_USE_URL` settings key, which is `True` unless set otherwise. +* `max_length` - Designates the maximum length for the file name. +* `allow_empty_file` - Designates if empty files are allowed. +* `use_url` - If set to `True` then URL string values will be used for the output representation. If set to `False` then filename string values will be used for the output representation. Defaults to the value of the `UPLOADED_FILES_USE_URL` settings key, which is `True` unless set otherwise. Requires either the `Pillow` package or `PIL` package. The `Pillow` package is recommended, as `PIL` is no longer actively maintained. @@ -460,10 +460,10 @@ A field class that validates a list of objects. **Signature**: `ListField(child=, allow_empty=True, min_length=None, max_length=None)` -- `child` - A field instance that should be used for validating the objects in the list. If this argument is not provided then objects in the list will not be validated. -- `allow_empty` - Designates if empty lists are allowed. -- `min_length` - Validates that the list contains no fewer than this number of elements. -- `max_length` - Validates that the list contains no more than this number of elements. +* `child` - A field instance that should be used for validating the objects in the list. If this argument is not provided then objects in the list will not be validated. +* `allow_empty` - Designates if empty lists are allowed. +* `min_length` - Validates that the list contains no fewer than this number of elements. +* `max_length` - Validates that the list contains no more than this number of elements. For example, to validate a list of integers you might use something like the following: @@ -484,8 +484,8 @@ A field class that validates a dictionary of objects. The keys in `DictField` ar **Signature**: `DictField(child=, allow_empty=True)` -- `child` - A field instance that should be used for validating the values in the dictionary. If this argument is not provided then values in the mapping will not be validated. -- `allow_empty` - Designates if empty dictionaries are allowed. +* `child` - A field instance that should be used for validating the values in the dictionary. If this argument is not provided then values in the mapping will not be validated. +* `allow_empty` - Designates if empty dictionaries are allowed. For example, to create a field that validates a mapping of strings to strings, you would write something like this: @@ -502,8 +502,8 @@ A preconfigured `DictField` that is compatible with Django's postgres `HStoreFie **Signature**: `HStoreField(child=, allow_empty=True)` -- `child` - A field instance that is used for validating the values in the dictionary. The default child field accepts both empty strings and null values. -- `allow_empty` - Designates if empty dictionaries are allowed. +* `child` - A field instance that is used for validating the values in the dictionary. The default child field accepts both empty strings and null values. +* `allow_empty` - Designates if empty dictionaries are allowed. Note that the child field **must** be an instance of `CharField`, as the hstore extension stores values as strings. @@ -513,8 +513,8 @@ A field class that validates that the incoming data structure consists of valid **Signature**: `JSONField(binary, encoder)` -- `binary` - If set to `True` then the field will output and validate a JSON encoded string, rather than a primitive data structure. Defaults to `False`. -- `encoder` - Use this JSON encoder to serialize input object. Defaults to `None`. +* `binary` - If set to `True` then the field will output and validate a JSON encoded string, rather than a primitive data structure. Defaults to `False`. +* `encoder` - Use this JSON encoder to serialize input object. Defaults to `None`. --- @@ -565,7 +565,7 @@ This is a read-only field. It gets its value by calling a method on the serializ **Signature**: `SerializerMethodField(method_name=None)` -- `method_name` - The name of the method on the serializer to be called. If not included this defaults to `get_`. +* `method_name` - The name of the method on the serializer to be called. If not included this defaults to `get_`. The serializer method referred to by the `method_name` argument should accept a single argument (in addition to `self`), which is the object being serialized. It should return whatever you want to be included in the serialized representation of the object. For example: diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index 9c8295b853..a828a88acb 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -33,7 +33,7 @@ For example, the following serializer would lead to a database hit each time eva class Meta: model = Album fields = ['album_name', 'artist', 'tracks'] - + # For each album object, tracks should be fetched from database qs = Album.objects.all() print(AlbumSerializer(qs, many=True).data) @@ -278,7 +278,7 @@ This field is always read-only. As opposed to previously discussed _references_ to another entity, the referred entity can instead also be embedded or _nested_ in the representation of the object that refers to it. -Such nested relationships can be expressed by using serializers as fields. +Such nested relationships can be expressed by using serializers as fields. If the field is used to represent a to-many relationship, you should add the `many=True` flag to the serializer field. @@ -494,8 +494,8 @@ This behavior is intended to prevent a template from being unable to render in a There are two keyword arguments you can use to control this behavior: -- `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Set to `None` to disable any limiting. Defaults to `1000`. -- `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"` +* `html_cutoff` - If set this will be the maximum number of choices that will be displayed by a HTML select drop down. Set to `None` to disable any limiting. Defaults to `1000`. +* `html_cutoff_text` - If set this will display a textual indicator if the maximum number of items have been cutoff in an HTML select drop down. Defaults to `"More than {count} items…"` You can also control these globally using the settings `HTML_SELECT_CUTOFF` and `HTML_SELECT_CUTOFF_TEXT`. From 9407833a8316c9b45af07c77ec51de6ccbe93195 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 14 Oct 2022 18:30:26 +0300 Subject: [PATCH 015/178] Avoid importing `django.test` package when not testing (#8699) Importing anything `rest_framework` causes `django.test` to be imported. This is because DRF registers a receiver on the `django.test_signals.setting_changed` signal. This is not really a problem, but it is good to avoid this because it bloats the memory with unnecessary modules (e.g. `django.test`, `django.core.servers.basehttp`, `socketserver`) and increases the startup time. It also doesn't feel right to import test code into non-test code. Try to import the signal from a core module if possible. Note that there's another `django.test` import in `MultiPartRenderer`, however this import is done lazily only if the functionality is used so can be easily avoided. --- rest_framework/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 9eb4c5653b..96b6645742 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -19,7 +19,9 @@ back to the defaults. """ from django.conf import settings -from django.test.signals import setting_changed +# Import from `django.core.signals` instead of the official location +# `django.test.signals` to avoid importing the test module unnecessarily. +from django.core.signals import setting_changed from django.utils.module_loading import import_string from rest_framework import ISO_8601 From b221aa2cf6e13907e63311cb0c6df36dede720ea Mon Sep 17 00:00:00 2001 From: mka142 <69858959+mka142@users.noreply.github.com> Date: Mon, 17 Oct 2022 10:54:48 +0200 Subject: [PATCH 016/178] Update schemas.md (#8707) --- docs/api-guide/schemas.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 004e2d3ce9..ff27fdffe8 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -122,6 +122,7 @@ The `get_schema_view()` helper takes the following keyword arguments: url='https://www.example.org/api/', patterns=schema_url_patterns, ) +* `public`: May be used to specify if schema should bypass views permissions. Default to False * `generator_class`: May be used to specify a `SchemaGenerator` subclass to be passed to the `SchemaView`. From 0cb693700f39f096e6337ccceab4f4b82b293557 Mon Sep 17 00:00:00 2001 From: Jan Lis <114775140+janlis-ff@users.noreply.github.com> Date: Mon, 17 Oct 2022 11:20:59 +0200 Subject: [PATCH 017/178] add `__eq__` method for `OperandHolder` class (#8710) --- rest_framework/permissions.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 16459b251f..f3ebbcffa7 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -46,6 +46,14 @@ def __call__(self, *args, **kwargs): op2 = self.op2_class(*args, **kwargs) return self.operator_class(op1, op2) + def __eq__(self, other): + return ( + isinstance(other, OperandHolder) and + self.operator_class == other.operator_class and + self.op1_class == other.op1_class and + self.op2_class == other.op2_class + ) + class AND: def __init__(self, op1, op2): From 35c5be6ec23af6e68914812599c905fe0fa2c0cc Mon Sep 17 00:00:00 2001 From: Den Date: Mon, 17 Oct 2022 13:47:45 +0400 Subject: [PATCH 018/178] Add a method for getting serializer field name (OpenAPI) (#7493) * Add a method for getting serializer field name * Add docs and test Co-authored-by: Tom Christie --- rest_framework/schemas/openapi.py | 11 +++++++++-- tests/schemas/test_openapi.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index ee614fdf6e..2f9fb9f28f 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -523,7 +523,7 @@ def map_serializer(self, serializer): continue if field.required: - required.append(field.field_name) + required.append(self.get_field_name(field)) schema = self.map_field(field) if field.read_only: @@ -538,7 +538,7 @@ def map_serializer(self, serializer): schema['description'] = str(field.help_text) self.map_field_validators(field, schema) - properties[field.field_name] = schema + properties[self.get_field_name(field)] = schema result = { 'type': 'object', @@ -589,6 +589,13 @@ def map_field_validators(self, field, schema): schema['maximum'] = int(digits * '9') + 1 schema['minimum'] = -schema['maximum'] + def get_field_name(self, field): + """ + Override this method if you want to change schema field name. + For example, convert snake_case field name to camelCase. + """ + return field.field_name + def get_paginator(self): pagination_class = getattr(self.view, 'pagination_class', None) if pagination_class: diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index daa035a3f3..7542bb6152 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -111,6 +111,20 @@ class Serializer(serializers.Serializer): assert data['properties']['default_false']['default'] is False, "default must be false" assert 'default' not in data['properties']['without_default'], "default must not be defined" + def test_custom_field_name(self): + class CustomSchema(AutoSchema): + def get_field_name(self, field): + return 'custom_' + field.field_name + + class Serializer(serializers.Serializer): + text_field = serializers.CharField() + + inspector = CustomSchema() + + data = inspector.map_serializer(Serializer()) + assert 'custom_text_field' in data['properties'] + assert 'text_field' not in data['properties'] + def test_nullable_fields(self): class Model(models.Model): rw_field = models.CharField(null=True) From e354331743dd6e8999371b0055d61967c78da859 Mon Sep 17 00:00:00 2001 From: Elton Maiyo Date: Wed, 19 Oct 2022 13:11:09 +0300 Subject: [PATCH 019/178] Fixes typo (#8719) --- docs/topics/rest-hypermedia-hateoas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/rest-hypermedia-hateoas.md b/docs/topics/rest-hypermedia-hateoas.md index a11bd6cc72..3498bddd1c 100644 --- a/docs/topics/rest-hypermedia-hateoas.md +++ b/docs/topics/rest-hypermedia-hateoas.md @@ -4,7 +4,7 @@ > > — Mike Amundsen, [REST fest 2012 keynote][cite]. -First off, the disclaimer. The name "Django REST framework" was decided back in early 2011 and was chosen simply to sure the project would be easily found by developers. Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs". +First off, the disclaimer. The name "Django REST framework" was decided back in early 2011 and was chosen simply to ensure the project would be easily found by developers. Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs". If you are serious about designing a Hypermedia API, you should look to resources outside of this documentation to help inform your design choices. From 041b88f8bbb48d9688ebd5add294eee2dfc93d1c Mon Sep 17 00:00:00 2001 From: Francisco Couzo Date: Wed, 19 Oct 2022 07:20:11 -0300 Subject: [PATCH 020/178] Improve style, fix some typos (#8405) * Improve style, fix some typos * Update docs/api-guide/fields.md Co-authored-by: Tom Christie Co-authored-by: Tom Christie --- docs/api-guide/authentication.md | 14 ++++++------- docs/api-guide/fields.md | 10 +++++----- docs/api-guide/format-suffixes.md | 4 ++-- docs/api-guide/generic-views.md | 2 +- docs/api-guide/pagination.md | 2 +- docs/api-guide/parsers.md | 2 +- docs/api-guide/permissions.md | 2 +- docs/api-guide/relations.md | 2 +- docs/api-guide/renderers.md | 6 +++--- docs/api-guide/requests.md | 2 +- docs/api-guide/reverse.md | 2 +- docs/api-guide/schemas.md | 4 ++-- docs/api-guide/serializers.md | 24 +++++++++++------------ docs/api-guide/validators.md | 4 ++-- docs/api-guide/views.md | 2 +- docs/api-guide/viewsets.md | 4 ++-- docs/community/3.0-announcement.md | 4 ++-- docs/community/3.2-announcement.md | 2 +- docs/community/3.3-announcement.md | 2 +- docs/community/3.4-announcement.md | 2 +- docs/community/3.8-announcement.md | 2 +- docs/community/funding.md | 6 +++--- docs/community/third-party-packages.md | 2 +- docs/coreapi/from-documenting-your-api.md | 2 +- docs/coreapi/schemas.md | 12 ++++++------ docs/index.md | 2 +- docs/topics/ajax-csrf-cors.md | 2 +- docs/topics/api-clients.md | 2 +- docs/topics/html-and-forms.md | 20 +++++++++---------- docs/tutorial/1-serialization.md | 4 ++-- docs/tutorial/2-requests-and-responses.md | 2 +- docs/tutorial/3-class-based-views.md | 4 ++-- 32 files changed, 78 insertions(+), 78 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 5fa87c0312..2eb3a2921e 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -201,7 +201,7 @@ If you've already created some users, you can generate tokens for all existing u #### By exposing an api endpoint -When using `TokenAuthentication`, you may want to provide a mechanism for clients to obtain a token given the username and password. REST framework provides a built-in view to provide this behaviour. To use it, add the `obtain_auth_token` view to your URLconf: +When using `TokenAuthentication`, you may want to provide a mechanism for clients to obtain a token given the username and password. REST framework provides a built-in view to provide this behavior. To use it, add the `obtain_auth_token` view to your URLconf: from rest_framework.authtoken import views urlpatterns += [ @@ -216,7 +216,7 @@ The `obtain_auth_token` view will return a JSON response when valid `username` a Note that the default `obtain_auth_token` view explicitly uses JSON requests and responses, rather than using default renderer and parser classes in your settings. -By default, there are no permissions or throttling applied to the `obtain_auth_token` view. If you do wish to apply to throttle you'll need to override the view class, +By default, there are no permissions or throttling applied to the `obtain_auth_token` view. If you do wish to apply throttling you'll need to override the view class, and include them using the `throttle_classes` attribute. If you need a customized version of the `obtain_auth_token` view, you can do so by subclassing the `ObtainAuthToken` view class, and using that in your url conf instead. @@ -250,7 +250,7 @@ And in your `urls.py`: #### With Django admin -It is also possible to create Tokens manually through the admin interface. In case you are using a large user base, we recommend that you monkey patch the `TokenAdmin` class customize it to your needs, more specifically by declaring the `user` field as `raw_field`. +It is also possible to create Tokens manually through the admin interface. In case you are using a large user base, we recommend that you monkey patch the `TokenAdmin` class to customize it to your needs, more specifically by declaring the `user` field as `raw_field`. `your_app/admin.py`: @@ -289,7 +289,7 @@ If you're using an AJAX-style API with SessionAuthentication, you'll need to mak **Warning**: Always use Django's standard login view when creating login pages. This will ensure your login views are properly protected. -CSRF validation in REST framework works slightly differently from standard Django due to the need to support both session and non-session based authentication to the same views. This means that only authenticated requests require CSRF tokens, and anonymous requests may be sent without CSRF tokens. This behaviour is not suitable for login views, which should always have CSRF validation applied. +CSRF validation in REST framework works slightly differently from standard Django due to the need to support both session and non-session based authentication to the same views. This means that only authenticated requests require CSRF tokens, and anonymous requests may be sent without CSRF tokens. This behavior is not suitable for login views, which should always have CSRF validation applied. ## RemoteUserAuthentication @@ -299,7 +299,7 @@ environment variable. To use it, you must have `django.contrib.auth.backends.RemoteUserBackend` (or a subclass) in your `AUTHENTICATION_BACKENDS` setting. By default, `RemoteUserBackend` creates `User` objects for usernames that don't -already exist. To change this and other behaviour, consult the +already exist. To change this and other behavior, consult the [Django documentation](https://docs.djangoproject.com/en/stable/howto/auth-remote-user/). If successfully authenticated, `RemoteUserAuthentication` provides the following credentials: @@ -307,7 +307,7 @@ If successfully authenticated, `RemoteUserAuthentication` provides the following * `request.user` will be a Django `User` instance. * `request.auth` will be `None`. -Consult your web server's documentation for information about configuring an authentication method, e.g.: +Consult your web server's documentation for information about configuring an authentication method, for example: * [Apache Authentication How-To](https://httpd.apache.org/docs/2.4/howto/auth.html) * [NGINX (Restricting Access)](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/) @@ -418,7 +418,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a ## Djoser -[Djoser][djoser] library provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. The package works with a custom user model and uses token-based authentication. This is ready to use REST implementation of the Django authentication system. +[Djoser][djoser] library provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. The package works with a custom user model and uses token-based authentication. This is a ready to use REST implementation of the Django authentication system. ## django-rest-auth / dj-rest-auth diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index e5b200bf6a..0a96d0e0c7 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -46,7 +46,7 @@ Defaults to `True`. If you're using [Model Serializer](https://www.django-rest-f ### `default` -If set, this gives the default value that will be used for the field if no input value is supplied. If not set the default behaviour is to not populate the attribute at all. +If set, this gives the default value that will be used for the field if no input value is supplied. If not set the default behavior is to not populate the attribute at all. The `default` is not applied during partial update operations. In the partial update case only fields that are provided in the incoming data will have a validated value returned. @@ -85,7 +85,7 @@ When serializing fields with dotted notation, it may be necessary to provide a ` class CommentSerializer(serializers.Serializer): email = serializers.EmailField(source="user.email") -would require user object to be fetched from database when it is not prefetched. If that is not wanted, be sure to be using `prefetch_related` and `select_related` methods appropriately. For more information about the methods refer to [django documentation][django-docs-select-related]. +This case would require user object to be fetched from database when it is not prefetched. If that is not wanted, be sure to be using `prefetch_related` and `select_related` methods appropriately. For more information about the methods refer to [django documentation][django-docs-select-related]. The value `source='*'` has a special meaning, and is used to indicate that the entire object should be passed through to the field. This can be useful for creating nested representations, or for fields which require access to the complete object in order to determine the output representation. @@ -151,7 +151,7 @@ Prior to Django 2.1 `models.BooleanField` fields were always `blank=True`. Thus since Django 2.1 default `serializers.BooleanField` instances will be generated without the `required` kwarg (i.e. equivalent to `required=True`) whereas with previous versions of Django, default `BooleanField` instances will be generated -with a `required=False` option. If you want to control this behaviour manually, +with a `required=False` option. If you want to control this behavior manually, explicitly declare the `BooleanField` on the serializer class, or use the `extra_kwargs` option to set the `required` flag. @@ -771,7 +771,7 @@ Here the mapping between the target and source attribute pairs (`x` and `x_coordinate`, `y` and `y_coordinate`) is handled in the `IntegerField` declarations. It's our `NestedCoordinateSerializer` that takes `source='*'`. -Our new `DataPointSerializer` exhibits the same behaviour as the custom field +Our new `DataPointSerializer` exhibits the same behavior as the custom field approach. Serializing: @@ -831,7 +831,7 @@ the [djangorestframework-recursive][djangorestframework-recursive] package provi ## django-rest-framework-gis -The [django-rest-framework-gis][django-rest-framework-gis] package provides geographic addons for django rest framework like a `GeometryField` field and a GeoJSON serializer. +The [django-rest-framework-gis][django-rest-framework-gis] package provides geographic addons for django rest framework like a `GeometryField` field and a GeoJSON serializer. ## django-rest-framework-hstore diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md index da4a1af78b..9b4e5b9da4 100644 --- a/docs/api-guide/format-suffixes.md +++ b/docs/api-guide/format-suffixes.md @@ -23,8 +23,8 @@ Returns a URL pattern list which includes format suffix patterns appended to eac Arguments: * **urlpatterns**: Required. A URL pattern list. -* **suffix_required**: Optional. A boolean indicating if suffixes in the URLs should be optional or mandatory. Defaults to `False`, meaning that suffixes are optional by default. -* **allowed**: Optional. A list or tuple of valid format suffixes. If not provided, a wildcard format suffix pattern will be used. +* **suffix_required**: Optional. A boolean indicating if suffixes in the URLs should be optional or mandatory. Defaults to `False`, meaning that suffixes are optional by default. +* **allowed**: Optional. A list or tuple of valid format suffixes. If not provided, a wildcard format suffix pattern will be used. Example: diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 9467eb6b48..410e3518df 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -98,7 +98,7 @@ For example: --- -**Note:** If the serializer_class used in the generic view spans orm relations, leading to an n+1 problem, you could optimize your queryset in this method using `select_related` and `prefetch_related`. To get more information about n+1 problem and use cases of the mentioned methods refer to related section in [django documentation][django-docs-select-related]. +**Note:** If the `serializer_class` used in the generic view spans orm relations, leading to an n+1 problem, you could optimize your queryset in this method using `select_related` and `prefetch_related`. To get more information about n+1 problem and use cases of the mentioned methods refer to related section in [django documentation][django-docs-select-related]. --- diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index aadc1bbc7f..3081e2bc36 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -227,7 +227,7 @@ Note that the `paginate_queryset` method may set state on the pagination instanc ## Example -Suppose we want to replace the default pagination output style with a modified format that includes the next and previous links under in a nested 'links' key. We could specify a custom pagination class like so: +Suppose we want to replace the default pagination output style with a modified format that includes the next and previous links under in a nested 'links' key. We could specify a custom pagination class like so: class CustomPagination(pagination.PageNumberPagination): def get_paginated_response(self, data): diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index dde77c3e0e..4993c447fd 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -15,7 +15,7 @@ REST framework includes a number of built in Parser classes, that allow you to a ## How the parser is determined -The set of valid parsers for a view is always defined as a list of classes. When `request.data` is accessed, REST framework will examine the `Content-Type` header on the incoming request, and determine which parser to use to parse the request content. +The set of valid parsers for a view is always defined as a list of classes. When `request.data` is accessed, REST framework will examine the `Content-Type` header on the incoming request, and determine which parser to use to parse the request content. --- diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 8d6f47c79b..6e164d220e 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -177,7 +177,7 @@ This permission class ties into Django's standard `django.contrib.auth` [model p * `PUT` and `PATCH` requests require the user to have the `change` permission on the model. * `DELETE` requests require the user to have the `delete` permission on the model. -The default behaviour can also be overridden to support custom model permissions. For example, you might want to include a `view` model permission for `GET` requests. +The default behavior can also be overridden to support custom model permissions. For example, you might want to include a `view` model permission for `GET` requests. To use custom model permissions, override `DjangoModelPermissions` and set the `.perms_map` property. Refer to the source code for details. diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index a828a88acb..56eb61e436 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -246,7 +246,7 @@ When using `SlugRelatedField` as a read-write field, you will normally want to e ## HyperlinkedIdentityField -This field can be applied as an identity relationship, such as the `'url'` field on a HyperlinkedModelSerializer. It can also be used for an attribute on the object. For example, the following serializer: +This field can be applied as an identity relationship, such as the `'url'` field on a HyperlinkedModelSerializer. It can also be used for an attribute on the object. For example, the following serializer: class AlbumSerializer(serializers.HyperlinkedModelSerializer): track_listing = serializers.HyperlinkedIdentityField(view_name='track-list') diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index 685a98f5e0..44f1b60218 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -105,7 +105,7 @@ The TemplateHTMLRenderer will create a `RequestContext`, using the `response.dat --- -**Note:** When used with a view that makes use of a serializer the `Response` sent for rendering may not be a dictionary and will need to be wrapped in a dict before returning to allow the TemplateHTMLRenderer to render it. For example: +**Note:** When used with a view that makes use of a serializer the `Response` sent for rendering may not be a dictionary and will need to be wrapped in a dict before returning to allow the `TemplateHTMLRenderer` to render it. For example: ``` response.data = {'results': response.data} @@ -192,7 +192,7 @@ By default the response content will be rendered with the highest priority rende def get_default_renderer(self, view): return JSONRenderer() -## AdminRenderer +## AdminRenderer Renders data into HTML for an admin-like display: @@ -332,7 +332,7 @@ You can do some pretty flexible things using REST framework's renderers. Some e * Specify multiple types of HTML representation for API clients to use. * Underspecify a renderer's media type, such as using `media_type = 'image/*'`, and use the `Accept` header to vary the encoding of the response. -## Varying behaviour by media type +## Varying behavior by media type In some cases you might want your view to use different serialization styles depending on the accepted media type. If you need to do this you can access `request.accepted_renderer` to determine the negotiated renderer that will be used for the response. diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md index e877c868df..d072ac4367 100644 --- a/docs/api-guide/requests.md +++ b/docs/api-guide/requests.md @@ -49,7 +49,7 @@ If a client sends a request with a content-type that cannot be parsed then a `Un # Content negotiation -The request exposes some properties that allow you to determine the result of the content negotiation stage. This allows you to implement behaviour such as selecting a different serialization schemes for different media types. +The request exposes some properties that allow you to determine the result of the content negotiation stage. This allows you to implement behavior such as selecting a different serialization schemes for different media types. ## .accepted_renderer diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md index ba37a0f667..82dd168818 100644 --- a/docs/api-guide/reverse.md +++ b/docs/api-guide/reverse.md @@ -38,7 +38,7 @@ You should **include the request as a keyword argument** to the function, for ex def get(self, request): year = now().year data = { - ... + ... 'year-summary-url': reverse('year-summary', args=[year], request=request) } return Response(data) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index ff27fdffe8..e402019a4f 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -293,7 +293,7 @@ class CustomView(APIView): This saves you having to create a custom subclass per-view for a commonly used option. -Not all `AutoSchema` methods expose related `__init__()` kwargs, but those for +Not all `AutoSchema` methods expose related `__init__()` kwargs, but those for the more commonly needed options do. ### `AutoSchema` methods @@ -301,7 +301,7 @@ the more commonly needed options do. #### `get_components()` Generates the OpenAPI components that describe request and response bodies, -deriving their properties from the serializer. +deriving their properties from the serializer. Returns a dictionary mapping the component name to the generated representation. By default this has just a single pair but you may override diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index ca120768e1..0d14cac46b 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -594,11 +594,11 @@ The ModelSerializer class also exposes an API that you can override in order to Normally if a `ModelSerializer` does not generate the fields you need by default then you should either add them to the class explicitly, or simply use a regular `Serializer` class instead. However in some cases you may want to create a new base class that defines how the serializer fields are created for any given model. -### `.serializer_field_mapping` +### `serializer_field_mapping` A mapping of Django model fields to REST framework serializer fields. You can override this mapping to alter the default serializer fields that should be used for each model field. -### `.serializer_related_field` +### `serializer_related_field` This property should be the serializer field class, that is used for relational fields by default. @@ -606,13 +606,13 @@ For `ModelSerializer` this defaults to `serializers.PrimaryKeyRelatedField`. For `HyperlinkedModelSerializer` this defaults to `serializers.HyperlinkedRelatedField`. -### `.serializer_url_field` +### `serializer_url_field` The serializer field class that should be used for any `url` field on the serializer. Defaults to `serializers.HyperlinkedIdentityField` -### `.serializer_choice_field` +### `serializer_choice_field` The serializer field class that should be used for any choice fields on the serializer. @@ -622,13 +622,13 @@ Defaults to `serializers.ChoiceField` The following methods are called to determine the class and keyword arguments for each field that should be automatically included on the serializer. Each of these methods should return a two tuple of `(field_class, field_kwargs)`. -### `.build_standard_field(self, field_name, model_field)` +### `build_standard_field(self, field_name, model_field)` Called to generate a serializer field that maps to a standard model field. The default implementation returns a serializer class based on the `serializer_field_mapping` attribute. -### `.build_relational_field(self, field_name, relation_info)` +### `build_relational_field(self, field_name, relation_info)` Called to generate a serializer field that maps to a relational model field. @@ -636,7 +636,7 @@ The default implementation returns a serializer class based on the `serializer_r The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties. -### `.build_nested_field(self, field_name, relation_info, nested_depth)` +### `build_nested_field(self, field_name, relation_info, nested_depth)` Called to generate a serializer field that maps to a relational model field, when the `depth` option has been set. @@ -646,17 +646,17 @@ The `nested_depth` will be the value of the `depth` option, minus one. The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties. -### `.build_property_field(self, field_name, model_class)` +### `build_property_field(self, field_name, model_class)` Called to generate a serializer field that maps to a property or zero-argument method on the model class. The default implementation returns a `ReadOnlyField` class. -### `.build_url_field(self, field_name, model_class)` +### `build_url_field(self, field_name, model_class)` Called to generate a serializer field for the serializer's own `url` field. The default implementation returns a `HyperlinkedIdentityField` class. -### `.build_unknown_field(self, field_name, model_class)` +### `build_unknown_field(self, field_name, model_class)` Called when the field name did not map to any model field or model property. The default implementation raises an error, although subclasses may customize this behavior. @@ -1021,7 +1021,7 @@ Some reasons this might be useful include... The signatures for these methods are as follows: -#### `.to_representation(self, instance)` +#### `to_representation(self, instance)` Takes the object instance that requires serialization, and should return a primitive representation. Typically this means returning a structure of built-in Python datatypes. The exact types that can be handled will depend on the render classes you have configured for your API. @@ -1033,7 +1033,7 @@ May be overridden in order to modify the representation style. For example: ret['username'] = ret['username'].lower() return ret -#### ``.to_internal_value(self, data)`` +#### ``to_internal_value(self, data)`` Takes the unvalidated incoming data as input and should return the validated data that will be made available as `serializer.validated_data`. The return value will also be passed to the `.create()` or `.update()` methods if `.save()` is called on the serializer class. diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index 76dcb0d541..bb8466a2c8 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -20,7 +20,7 @@ Validation in Django REST framework serializers is handled a little differently With `ModelForm` the validation is performed partially on the form, and partially on the model instance. With REST framework the validation is performed entirely on the serializer class. This is advantageous for the following reasons: * It introduces a proper separation of concerns, making your code behavior more obvious. -* It is easy to switch between using shortcut `ModelSerializer` classes and using explicit `Serializer` classes. Any validation behavior being used for `ModelSerializer` is simple to replicate. +* It is easy to switch between using shortcut `ModelSerializer` classes and using explicit `Serializer` classes. Any validation behavior being used for `ModelSerializer` is simple to replicate. * Printing the `repr` of a serializer instance will show you exactly what validation rules it applies. There's no extra hidden validation behavior being called on the model instance. When you're using `ModelSerializer` all of this is handled automatically for you. If you want to drop down to using `Serializer` classes instead, then you need to define the validation rules explicitly. @@ -208,7 +208,7 @@ by specifying an empty list for the serializer `Meta.validators` attribute. By default "unique together" validation enforces that all fields be `required=True`. In some cases, you might want to explicit apply -`required=False` to one of the fields, in which case the desired behaviour +`required=False` to one of the fields, in which case the desired behavior of the validation is ambiguous. In this case you will typically need to exclude the validator from the diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index 878a291b22..b293de75ab 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -153,7 +153,7 @@ The core of this functionality is the `api_view` decorator, which takes a list o This view will use the default renderers, parsers, authentication classes etc specified in the [settings]. -By default only `GET` methods will be accepted. Other methods will respond with "405 Method Not Allowed". To alter this behaviour, specify which methods the view allows, like so: +By default only `GET` methods will be accepted. Other methods will respond with "405 Method Not Allowed". To alter this behavior, specify which methods the view allows, like so: @api_view(['GET', 'POST']) def hello_world(request): diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 4179725078..5d5491a83f 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -116,7 +116,7 @@ During dispatch, the following attributes are available on the `ViewSet`. * `name` - the display name for the viewset. This argument is mutually exclusive to `suffix`. * `description` - the display description for the individual view of a viewset. -You may inspect these attributes to adjust behaviour based on the current action. For example, you could restrict permissions to everything except the `list` action similar to this: +You may inspect these attributes to adjust behavior based on the current action. For example, you could restrict permissions to everything except the `list` action similar to this: def get_permissions(self): """ @@ -247,7 +247,7 @@ In order to use a `GenericViewSet` class you'll override the class and either mi The `ModelViewSet` class inherits from `GenericAPIView` and includes implementations for various actions, by mixing in the behavior of the various mixin classes. -The actions provided by the `ModelViewSet` class are `.list()`, `.retrieve()`, `.create()`, `.update()`, `.partial_update()`, and `.destroy()`. +The actions provided by the `ModelViewSet` class are `.list()`, `.retrieve()`, `.create()`, `.update()`, `.partial_update()`, and `.destroy()`. #### Example diff --git a/docs/community/3.0-announcement.md b/docs/community/3.0-announcement.md index b9461defe9..0cb79fc2e2 100644 --- a/docs/community/3.0-announcement.md +++ b/docs/community/3.0-announcement.md @@ -24,7 +24,7 @@ Notable features of this new release include: * Support for overriding how validation errors are handled by your API. * A metadata API that allows you to customize how `OPTIONS` requests are handled by your API. * A more compact JSON output with unicode style encoding turned on by default. -* Templated based HTML form rendering for serializers. This will be finalized as public API in the upcoming 3.1 release. +* Templated based HTML form rendering for serializers. This will be finalized as public API in the upcoming 3.1 release. Significant new functionality continues to be planned for the 3.1 and 3.2 releases. These releases will correspond to the two [Kickstarter stretch goals](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) - "Feature improvements" and "Admin interface". Further 3.x releases will present simple upgrades, without the same level of fundamental API changes necessary for the 3.0 release. @@ -632,7 +632,7 @@ The `MultipleChoiceField` class has been added. This field acts like `ChoiceFiel The `from_native(self, value)` and `to_native(self, data)` method names have been replaced with the more obviously named `to_internal_value(self, data)` and `to_representation(self, value)`. -The `field_from_native()` and `field_to_native()` methods are removed. Previously you could use these methods if you wanted to customise the behaviour in a way that did not simply lookup the field value from the object. For example... +The `field_from_native()` and `field_to_native()` methods are removed. Previously you could use these methods if you wanted to customise the behavior in a way that did not simply lookup the field value from the object. For example... def field_to_native(self, obj, field_name): """A custom read-only field that returns the class name.""" diff --git a/docs/community/3.2-announcement.md b/docs/community/3.2-announcement.md index a66ad5d292..eda4071b2e 100644 --- a/docs/community/3.2-announcement.md +++ b/docs/community/3.2-announcement.md @@ -64,7 +64,7 @@ These are a little subtle and probably won't affect most users, but are worth un ### ManyToMany fields and blank=True -We've now added an `allow_empty` argument, which can be used with `ListSerializer`, or with `many=True` relationships. This is `True` by default, but can be set to `False` if you want to disallow empty lists as valid input. +We've now added an `allow_empty` argument, which can be used with `ListSerializer`, or with `many=True` relationships. This is `True` by default, but can be set to `False` if you want to disallow empty lists as valid input. As a follow-up to this we are now able to properly mirror the behavior of Django's `ModelForm` with respect to how many-to-many fields are validated. diff --git a/docs/community/3.3-announcement.md b/docs/community/3.3-announcement.md index 5dcbe3b3b5..24f493dcdc 100644 --- a/docs/community/3.3-announcement.md +++ b/docs/community/3.3-announcement.md @@ -38,7 +38,7 @@ The AJAX based support for the browsable API means that there are a number of in * To support form based `PUT` and `DELETE`, or to support form content types such as JSON, you should now use the [AJAX forms][ajax-form] javascript library. This replaces the previous 'method and content type overloading' that required significant internal complexity to the request class. * The `accept` query parameter is no longer supported by the default content negotiation class. If you require it then you'll need to [use a custom content negotiation class][accept-headers]. -* The custom `HTTP_X_HTTP_METHOD_OVERRIDE` header is no longer supported by default. If you require it then you'll need to [use custom middleware][method-override]. +* The custom `HTTP_X_HTTP_METHOD_OVERRIDE` header is no longer supported by default. If you require it then you'll need to [use custom middleware][method-override]. The following pagination view attributes and settings have been moved into attributes on the pagination class since 3.1. Their usage was formerly deprecated, and has now been removed entirely, in line with the deprecation policy. diff --git a/docs/community/3.4-announcement.md b/docs/community/3.4-announcement.md index 67192ecbb2..6d5f0399c5 100644 --- a/docs/community/3.4-announcement.md +++ b/docs/community/3.4-announcement.md @@ -89,7 +89,7 @@ Name | Support | PyPI pa ---------------------------------|-------------------------------------|-------------------------------- [Core JSON][core-json] | Schema generation & client support. | Built-in support in `coreapi`. [Swagger / OpenAPI][swagger] | Schema generation & client support. | The `openapi-codec` package. -[JSON Hyper-Schema][hyperschema] | Currently client support only. | The `hyperschema-codec` package. +[JSON Hyper-Schema][hyperschema] | Currently client support only. | The `hyperschema-codec` package. [API Blueprint][api-blueprint] | Not yet available. | Not yet available. --- diff --git a/docs/community/3.8-announcement.md b/docs/community/3.8-announcement.md index 507299782d..f33b220fd7 100644 --- a/docs/community/3.8-announcement.md +++ b/docs/community/3.8-announcement.md @@ -89,7 +89,7 @@ for a complete listing. We're currently working towards moving to using [OpenAPI][openapi] as our default schema output. We'll also be revisiting our API documentation generation and client libraries. -We're doing some consolidation in order to make this happen. It's planned that 3.9 will drop the `coreapi` and `coreschema` libraries, and instead use `apistar` for the API documentation generation, schema generation, and API client libraries. +We're doing some consolidation in order to make this happen. It's planned that 3.9 will drop the `coreapi` and `coreschema` libraries, and instead use `apistar` for the API documentation generation, schema generation, and API client libraries. [funding]: funding.md [gh5886]: https://github.com/encode/django-rest-framework/issues/5886 diff --git a/docs/community/funding.md b/docs/community/funding.md index 2158cd38f3..951833682e 100644 --- a/docs/community/funding.md +++ b/docs/community/funding.md @@ -124,7 +124,7 @@ REST framework continues to be open-source and permissively licensed, but we fir ## What funding has enabled so far * The [3.4](https://www.django-rest-framework.org/community/3.4-announcement/) and [3.5](https://www.django-rest-framework.org/community/3.5-announcement/) releases, including schema generation for both Swagger and RAML, a Python client library, a Command Line client, and addressing of a large number of outstanding issues. -* The [3.6](https://www.django-rest-framework.org/community/3.6-announcement/) release, including JavaScript client library, and API documentation, complete with auto-generated code samples. +* The [3.6](https://www.django-rest-framework.org/community/3.6-announcement/) release, including JavaScript client library, and API documentation, complete with auto-generated code samples. * The [3.7 release](https://www.django-rest-framework.org/community/3.7-announcement/), made possible due to our collaborative funding model, focuses on improvements to schema generation and the interactive API documentation. * The recent [3.8 release](https://www.django-rest-framework.org/community/3.8-announcement/). * Tom Christie, the creator of Django REST framework, working on the project full-time. @@ -154,13 +154,13 @@ Sign up for a paid plan today, and help ensure that REST framework becomes a sus   -> The number one feature of the Python programming language is its community. Such a community is only possible because of the Open Source nature of the language and all the culture that comes from it. Building great Open Source projects require great minds. Given that, we at Vinta are not only proud to sponsor the team behind DRF but we also recognize the ROI that comes from it. +> The number one feature of the Python programming language is its community. Such a community is only possible because of the Open Source nature of the language and all the culture that comes from it. Building great Open Source projects require great minds. Given that, we at Vinta are not only proud to sponsor the team behind DRF but we also recognize the ROI that comes from it. > > — Filipe Ximenes, Vinta Software   -> It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality. +> It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality. DRF is one of the core reasons why Django is top choice among web frameworks today. In my opinion, it sets the standard for rest frameworks for the development community at large. > > — Andrew Conti, Django REST framework user diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 9513b13d1a..1a40539da9 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -205,7 +205,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [dry-rest-permissions]: https://github.com/FJNR-inc/dry-rest-permissions [django-url-filter]: https://github.com/miki725/django-url-filter [drf-url-filter]: https://github.com/manjitkumar/drf-url-filters -[cookiecutter-django-rest]: https://github.com/agconti/cookiecutter-django-rest +[cookiecutter-django-rest]: https://github.com/agconti/cookiecutter-django-rest [drf-haystack]: https://drf-haystack.readthedocs.io/en/latest/ [django-rest-framework-version-transforms]: https://github.com/mrhwick/django-rest-framework-version-transforms [djangorestframework-jsonapi]: https://github.com/django-json-api/django-rest-framework-json-api diff --git a/docs/coreapi/from-documenting-your-api.md b/docs/coreapi/from-documenting-your-api.md index 65ad71c7a7..f8ddff019e 100644 --- a/docs/coreapi/from-documenting-your-api.md +++ b/docs/coreapi/from-documenting-your-api.md @@ -43,7 +43,7 @@ This will include two different views: **Note**: By default `include_docs_urls` configures the underlying `SchemaView` to generate _public_ schemas. This means that views will not be instantiated with a `request` instance. i.e. Inside the view `self.request` will be `None`. -To be compatible with this behaviour, methods (such as `get_serializer` or `get_serializer_class` etc.) which inspect `self.request` or, particularly, `self.request.user` may need to be adjusted to handle this case. +To be compatible with this behavior, methods (such as `get_serializer` or `get_serializer_class` etc.) which inspect `self.request` or, particularly, `self.request.user` may need to be adjusted to handle this case. You may ensure views are given a `request` instance by calling `include_docs_urls` with `public=False`: diff --git a/docs/coreapi/schemas.md b/docs/coreapi/schemas.md index 9f1482d2d8..73fc7b67d1 100644 --- a/docs/coreapi/schemas.md +++ b/docs/coreapi/schemas.md @@ -398,7 +398,7 @@ then you can use the `SchemaGenerator` class directly to auto-generate the `Document` instance, and to return that from a view. This option gives you the flexibility of setting up the schema endpoint -with whatever behaviour you want. For example, you can apply different +with whatever behavior you want. For example, you can apply different permission, throttling, or authentication policies to the schema endpoint. Here's an example of using `SchemaGenerator` together with a view to @@ -572,7 +572,7 @@ A class that deals with introspection of individual views for schema generation. `AutoSchema` is attached to `APIView` via the `schema` attribute. -The `AutoSchema` constructor takes a single keyword argument `manual_fields`. +The `AutoSchema` constructor takes a single keyword argument `manual_fields`. **`manual_fields`**: a `list` of `coreapi.Field` instances that will be added to the generated fields. Generated fields with a matching `name` will be overwritten. @@ -649,10 +649,10 @@ def get_manual_fields(self, path, method): """Example adding per-method fields.""" extra_fields = [] - if method=='GET': - extra_fields = # ... list of extra fields for GET ... - if method=='POST': - extra_fields = # ... list of extra fields for POST ... + if method == 'GET': + extra_fields = ... # list of extra fields for GET + if method == 'POST': + extra_fields = ... # list of extra fields for POST manual_fields = super().get_manual_fields(path, method) return manual_fields + extra_fields diff --git a/docs/index.md b/docs/index.md index 2f44fae9a0..0c428c8ec3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -188,7 +188,7 @@ Framework. ## Support -For support please see the [REST framework discussion group][group], try the `#restframework` channel on `irc.libera.chat`, or raise a question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag. +For support please see the [REST framework discussion group][group], try the `#restframework` channel on `irc.libera.chat`, or raise a question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag. For priority support please sign up for a [professional or premium sponsorship plan](https://fund.django-rest-framework.org/topics/funding/). diff --git a/docs/topics/ajax-csrf-cors.md b/docs/topics/ajax-csrf-cors.md index a65e3fdf8d..094ecc4a41 100644 --- a/docs/topics/ajax-csrf-cors.md +++ b/docs/topics/ajax-csrf-cors.md @@ -2,7 +2,7 @@ > "Take a close look at possible CSRF / XSRF vulnerabilities on your own websites. They're the worst kind of vulnerability — very easy to exploit by attackers, yet not so intuitively easy to understand for software developers, at least until you've been bitten by one." > -> — [Jeff Atwood][cite] +> — [Jeff Atwood][cite] ## Javascript clients diff --git a/docs/topics/api-clients.md b/docs/topics/api-clients.md index b9f5e3ecd8..3732d3f93d 100644 --- a/docs/topics/api-clients.md +++ b/docs/topics/api-clients.md @@ -230,7 +230,7 @@ started. In order to start working with an API, we first need a `Client` instance. The client holds any configuration around which codecs and transports are supported when interacting with an API, which allows you to provide for more advanced -kinds of behaviour. +kinds of behavior. import coreapi client = coreapi.Client() diff --git a/docs/topics/html-and-forms.md b/docs/topics/html-and-forms.md index 18774926b5..17c9e3314c 100644 --- a/docs/topics/html-and-forms.md +++ b/docs/topics/html-and-forms.md @@ -207,14 +207,14 @@ Field templates can also use additional style properties, depending on their typ The complete list of `base_template` options and their associated style options is listed below. -base_template | Valid field types | Additional style options -----|----|---- -input.html | Any string, numeric or date/time field | input_type, placeholder, hide_label, autofocus -textarea.html | `CharField` | rows, placeholder, hide_label -select.html | `ChoiceField` or relational field types | hide_label -radio.html | `ChoiceField` or relational field types | inline, hide_label -select_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | hide_label +base_template | Valid field types | Additional style options +-----------------------|-------------------------------------------------------------|----------------------------------------------- +input.html | Any string, numeric or date/time field | input_type, placeholder, hide_label, autofocus +textarea.html | `CharField` | rows, placeholder, hide_label +select.html | `ChoiceField` or relational field types | hide_label +radio.html | `ChoiceField` or relational field types | inline, hide_label +select_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | hide_label checkbox_multiple.html | `MultipleChoiceField` or relational fields with `many=True` | inline, hide_label -checkbox.html | `BooleanField` | hide_label -fieldset.html | Nested serializer | hide_label -list_fieldset.html | `ListField` or nested serializer with `many=True` | hide_label +checkbox.html | `BooleanField` | hide_label +fieldset.html | Nested serializer | hide_label +list_fieldset.html | `ListField` or nested serializer with `many=True` | hide_label diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 67746c517e..6db3ea282d 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -179,7 +179,7 @@ We can also serialize querysets instead of model instances. To do so we simply ## Using ModelSerializers -Our `SnippetSerializer` class is replicating a lot of information that's also contained in the `Snippet` model. It would be nice if we could keep our code a bit more concise. +Our `SnippetSerializer` class is replicating a lot of information that's also contained in the `Snippet` model. It would be nice if we could keep our code a bit more concise. In the same way that Django provides both `Form` classes and `ModelForm` classes, REST framework includes both `Serializer` classes, and `ModelSerializer` classes. @@ -307,7 +307,7 @@ Quit out of the shell... Validating models... 0 errors found - Django version 4.0,1 using settings 'tutorial.settings' + Django version 4.0, using settings 'tutorial.settings' Starting Development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index 1d272f3040..47c7facfc6 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -29,7 +29,7 @@ REST framework provides two wrappers you can use to write API views. These wrappers provide a few bits of functionality such as making sure you receive `Request` instances in your view, and adding context to `Response` objects so that content negotiation can be performed. -The wrappers also provide behaviour such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exceptions that occur when accessing `request.data` with malformed input. +The wrappers also provide behavior such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exceptions that occur when accessing `request.data` with malformed input. ## Pulling it all together diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index e02feaa5ea..ccfcd095da 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -79,9 +79,9 @@ Okay, we're done. If you run the development server everything should be workin ## Using mixins -One of the big wins of using class-based views is that it allows us to easily compose reusable bits of behaviour. +One of the big wins of using class-based views is that it allows us to easily compose reusable bits of behavior. -The create/retrieve/update/delete operations that we've been using so far are going to be pretty similar for any model-backed API views we create. Those bits of common behaviour are implemented in REST framework's mixin classes. +The create/retrieve/update/delete operations that we've been using so far are going to be pretty similar for any model-backed API views we create. Those bits of common behavior are implemented in REST framework's mixin classes. Let's take a look at how we can compose the views by using the mixin classes. Here's our `views.py` module again. From 2510456817d9d2840a4080e69a0efb8a4da423ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Thu, 20 Oct 2022 16:32:25 +0200 Subject: [PATCH 021/178] Update quickstart.md (#8575) * Update quickstart.md * Use PEP 8 compliant import * Remove unauthorized password by Django (too common) --- docs/tutorial/quickstart.md | 54 ++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 4fa2fbbe52..754b46f9ab 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -30,22 +30,24 @@ The project layout should look like: /tutorial $ find . . - ./manage.py ./tutorial + ./tutorial/asgi.py ./tutorial/__init__.py ./tutorial/quickstart - ./tutorial/quickstart/__init__.py - ./tutorial/quickstart/admin.py - ./tutorial/quickstart/apps.py ./tutorial/quickstart/migrations ./tutorial/quickstart/migrations/__init__.py ./tutorial/quickstart/models.py + ./tutorial/quickstart/__init__.py + ./tutorial/quickstart/apps.py + ./tutorial/quickstart/admin.py ./tutorial/quickstart/tests.py ./tutorial/quickstart/views.py - ./tutorial/asgi.py ./tutorial/settings.py ./tutorial/urls.py ./tutorial/wsgi.py + ./env + ./env/... + ./manage.py It may look unusual that the application has been created within the project directory. Using the project's namespace avoids name clashes with external modules (a topic that goes outside the scope of the quickstart). @@ -53,9 +55,9 @@ Now sync your database for the first time: python manage.py migrate -We'll also create an initial user named `admin` with a password of `password123`. We'll authenticate as that user later in our example. +We'll also create an initial user named `admin` with a password. We'll authenticate as that user later in our example. - python manage.py createsuperuser --email admin@example.com --username admin + python manage.py createsuperuser --username admin --email admin@example.com Once you've set up a database and the initial user is created and ready to go, open up the app's directory and we'll get coding... @@ -63,7 +65,7 @@ Once you've set up a database and the initial user is created and ready to go, o First up we're going to define some serializers. Let's create a new module named `tutorial/quickstart/serializers.py` that we'll use for our data representations. - from django.contrib.auth.models import User, Group + from django.contrib.auth.models import Group, User from rest_framework import serializers @@ -84,10 +86,10 @@ Notice that we're using hyperlinked relations in this case with `HyperlinkedMode Right, we'd better write some views then. Open `tutorial/quickstart/views.py` and get typing. - from django.contrib.auth.models import User, Group - from rest_framework import viewsets - from rest_framework import permissions - from tutorial.quickstart.serializers import UserSerializer, GroupSerializer + from django.contrib.auth.models import Group, User + from rest_framework import permissions, viewsets + + from tutorial.quickstart.serializers import GroupSerializer, UserSerializer class UserViewSet(viewsets.ModelViewSet): @@ -117,6 +119,7 @@ Okay, now let's wire up the API URLs. On to `tutorial/urls.py`... from django.urls import include, path from rest_framework import routers + from tutorial.quickstart import views router = routers.DefaultRouter() @@ -165,38 +168,39 @@ We're now ready to test the API we've built. Let's fire up the server from the We can now access our API, both from the command-line, using tools like `curl`... - bash: curl -H 'Accept: application/json; indent=4' -u admin:password123 http://127.0.0.1:8000/users/ + bash: curl -u admin -H 'Accept: application/json; indent=4' http://127.0.0.1:8000/users/ + Enter host password for user 'admin': { - "count": 2, + "count": 1, "next": null, "previous": null, "results": [ { - "email": "admin@example.com", - "groups": [], "url": "http://127.0.0.1:8000/users/1/", - "username": "admin" - }, + "username": "admin", + "email": "admin@example.com", + "groups": [] + } ] } Or using the [httpie][httpie], command line tool... - bash: http -a admin:password123 http://127.0.0.1:8000/users/ - - HTTP/1.1 200 OK + bash: http -a admin http://127.0.0.1:8000/users/ + http: password for admin@127.0.0.1:8000:: + $HTTP/1.1 200 OK ... { - "count": 2, + "count": 1, "next": null, "previous": null, "results": [ { "email": "admin@example.com", "groups": [], - "url": "http://localhost:8000/users/1/", - "username": "paul" - }, + "url": "http://127.0.0.1:8000/users/1/", + "username": "admin" + } ] } From 1142ee5fc11991b39ecbc815d85d60e932b24895 Mon Sep 17 00:00:00 2001 From: Thomas <17140634+tompec@users.noreply.github.com> Date: Wed, 2 Nov 2022 19:10:45 +0800 Subject: [PATCH 022/178] Update jobs.md (#8737) Update remoteok.io to remoteok.com Add pyjobs.com --- docs/community/jobs.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/community/jobs.md b/docs/community/jobs.md index ce85b75707..aa1c5d4b4c 100644 --- a/docs/community/jobs.md +++ b/docs/community/jobs.md @@ -14,8 +14,9 @@ Looking for a new Django REST Framework related role? On this site we provide a * [https://stackoverflow.com/jobs/companies?tl=django][stackoverflow-com] * [https://www.upwork.com/o/jobs/browse/skill/django-framework/][upwork-com] * [https://www.technojobs.co.uk/django-jobs][technobjobs-co-uk] -* [https://remoteok.io/remote-django-jobs][remoteok-io] +* [https://remoteok.com/remote-django-jobs][remoteok-com] * [https://www.remotepython.com/jobs/][remotepython-com] +* [https://www.pyjobs.com/][pyjobs-com] Know of any other great resources for Django REST Framework jobs that are missing in our list? Please [submit a pull request][submit-pr] or [email us][anna-email]. @@ -32,8 +33,9 @@ Wonder how else you can help? One of the best ways you can help Django REST Fram [stackoverflow-com]: https://stackoverflow.com/jobs/companies?tl=django [upwork-com]: https://www.upwork.com/o/jobs/browse/skill/django-framework/ [technobjobs-co-uk]: https://www.technojobs.co.uk/django-jobs -[remoteok-io]: https://remoteok.io/remote-django-jobs +[remoteok-com]: https://remoteok.com/remote-django-jobs [remotepython-com]: https://www.remotepython.com/jobs/ +[pyjobs-com]: https://www.pyjobs.com/ [drf-funding]: https://fund.django-rest-framework.org/topics/funding/ [submit-pr]: https://github.com/encode/django-rest-framework [anna-email]: mailto:anna@django-rest-framework.org From ae7a2b0dfadc8f2ac5b648ad4391101bab7baf51 Mon Sep 17 00:00:00 2001 From: "Lewis M. Kabui" <13940255+lewisemm@users.noreply.github.com> Date: Thu, 10 Nov 2022 13:31:06 +0300 Subject: [PATCH 023/178] Remove extraneous word "Both" (#8740) * Remove extraneous word "Both" * Update Multiparser docs Co-authored-by: Lewis Kabui --- docs/api-guide/parsers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 4993c447fd..0a85d474f6 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -87,7 +87,7 @@ You will typically want to use both `FormParser` and `MultiPartParser` together ## MultiPartParser -Parses multipart HTML form content, which supports file uploads. Both `request.data` will be populated with a `QueryDict`. +Parses multipart HTML form content, which supports file uploads. `request.data` and `request.FILES` will be populated with a `QueryDict` and `MultiValueDict` respectively. You will typically want to use both `FormParser` and `MultiPartParser` together in order to fully support HTML form data. From 78cdae69997c9fd95211ec15fb4e21f4cd45e30a Mon Sep 17 00:00:00 2001 From: Stanislav Levin Date: Tue, 15 Nov 2022 15:29:15 +0300 Subject: [PATCH 024/178] Fix Pytest's deprecation warnings about nose usage (#8758) Pytest 7.2.0 deprecated plain `setup` and `teardown` functions and methods as nose idioms: https://docs.pytest.org/en/latest/changelog.html#pytest-7-2-0-2022-10-23 `setup` can be safely replaced with `setup_method`: https://docs.pytest.org/en/stable/deprecations.html#setup-teardown Fixes: https://github.com/encode/django-rest-framework/issues/8757 Signed-off-by: Stanislav Levin Signed-off-by: Stanislav Levin --- tests/test_fields.py | 14 +++++++------- tests/test_pagination.py | 12 ++++++------ tests/test_relations.py | 2 +- tests/test_serializer.py | 14 +++++++------- tests/test_serializer_lists.py | 12 ++++++------ tests/test_serializer_nested.py | 10 +++++----- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 11e293107d..72ef23dca5 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -239,7 +239,7 @@ class BuiltinSerializer(serializers.Serializer): class TestReadOnly: - def setup(self): + def setup_method(self): class TestSerializer(serializers.Serializer): read_only = serializers.ReadOnlyField(default="789") writable = serializers.IntegerField() @@ -271,7 +271,7 @@ def test_serialize_read_only(self): class TestWriteOnly: - def setup(self): + def setup_method(self): class TestSerializer(serializers.Serializer): write_only = serializers.IntegerField(write_only=True) readable = serializers.IntegerField() @@ -296,7 +296,7 @@ def test_serialize_write_only(self): class TestInitial: - def setup(self): + def setup_method(self): class TestSerializer(serializers.Serializer): initial_field = serializers.IntegerField(initial=123) blank_field = serializers.IntegerField() @@ -313,7 +313,7 @@ def test_initial(self): class TestInitialWithCallable: - def setup(self): + def setup_method(self): def initial_value(): return 123 @@ -331,7 +331,7 @@ def test_initial_should_accept_callable(self): class TestLabel: - def setup(self): + def setup_method(self): class TestSerializer(serializers.Serializer): labeled = serializers.IntegerField(label='My label') self.serializer = TestSerializer() @@ -345,7 +345,7 @@ def test_label(self): class TestInvalidErrorKey: - def setup(self): + def setup_method(self): class ExampleField(serializers.Field): def to_native(self, data): self.fail('incorrect') @@ -539,7 +539,7 @@ class TestSerializer(serializers.Serializer): class TestCreateOnlyDefault: - def setup(self): + def setup_method(self): default = serializers.CreateOnlyDefault('2001-01-01') class TestSerializer(serializers.Serializer): diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 5d7864700d..74a65bf506 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -18,7 +18,7 @@ class TestPaginationIntegration: Integration tests. """ - def setup(self): + def setup_method(self): class PassThroughSerializer(serializers.BaseSerializer): def to_representation(self, item): return item @@ -140,7 +140,7 @@ class TestPaginationDisabledIntegration: Integration tests for disabled pagination. """ - def setup(self): + def setup_method(self): class PassThroughSerializer(serializers.BaseSerializer): def to_representation(self, item): return item @@ -163,7 +163,7 @@ class TestPageNumberPagination: Unit tests for `pagination.PageNumberPagination`. """ - def setup(self): + def setup_method(self): class ExamplePagination(pagination.PageNumberPagination): page_size = 5 @@ -302,7 +302,7 @@ class TestPageNumberPaginationOverride: the Django Paginator Class is overridden. """ - def setup(self): + def setup_method(self): class OverriddenDjangoPaginator(DjangoPaginator): # override the count in our overridden Django Paginator # we will only return one page, with one item @@ -358,7 +358,7 @@ class TestLimitOffset: Unit tests for `pagination.LimitOffsetPagination`. """ - def setup(self): + def setup_method(self): class ExamplePagination(pagination.LimitOffsetPagination): default_limit = 10 max_limit = 15 @@ -941,7 +941,7 @@ class TestCursorPagination(CursorPaginationTestsMixin): Unit tests for `pagination.CursorPagination`. """ - def setup(self): + def setup_method(self): class MockObject: def __init__(self, idx): self.created = idx diff --git a/tests/test_relations.py b/tests/test_relations.py index bb719a65a9..7a4db1c487 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -374,7 +374,7 @@ def test_get_value_multi_dictionary_partial(self): class TestHyperlink: - def setup(self): + def setup_method(self): self.default_hyperlink = serializers.Hyperlink('http://example.com', 'test') def test_can_be_pickled(self): diff --git a/tests/test_serializer.py b/tests/test_serializer.py index c4c29ba4ad..1d9efaa434 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -61,7 +61,7 @@ def test_relations(self): # ----------------------------- class TestSerializer: - def setup(self): + def setup_method(self): class ExampleSerializer(serializers.Serializer): char = serializers.CharField() integer = serializers.IntegerField() @@ -240,7 +240,7 @@ def validate(self, attrs): class TestBaseSerializer: - def setup(self): + def setup_method(self): class ExampleSerializer(serializers.BaseSerializer): def to_representation(self, obj): return { @@ -337,7 +337,7 @@ class TestStarredSource: 'nested2': {'c': 3, 'd': 4} } - def setup(self): + def setup_method(self): class NestedSerializer1(serializers.Serializer): a = serializers.IntegerField() b = serializers.IntegerField() @@ -463,7 +463,7 @@ def create(self, validated_data): class TestDefaultOutput: - def setup(self): + def setup_method(self): class ExampleSerializer(serializers.Serializer): has_default = serializers.CharField(default='x') has_default_callable = serializers.CharField(default=lambda: 'y') @@ -584,7 +584,7 @@ class ExampleSerializer(serializers.Serializer): class TestDefaultInclusions: - def setup(self): + def setup_method(self): class ExampleSerializer(serializers.Serializer): char = serializers.CharField(default='abc') integer = serializers.IntegerField() @@ -612,7 +612,7 @@ def test_default_should_not_be_included_on_partial_update(self): class TestSerializerValidationWithCompiledRegexField: - def setup(self): + def setup_method(self): class ExampleSerializer(serializers.Serializer): name = serializers.RegexField(re.compile(r'\d'), required=True) self.Serializer = ExampleSerializer @@ -641,7 +641,7 @@ class ParentSerializer(serializers.Serializer): class Test4606Regression: - def setup(self): + def setup_method(self): class ExampleSerializer(serializers.Serializer): name = serializers.CharField(required=True) choices = serializers.CharField(required=True) diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index 551f626662..4b60643a85 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -32,7 +32,7 @@ class TestListSerializer: Note that this is in contrast to using ListSerializer as a field. """ - def setup(self): + def setup_method(self): class IntegerListSerializer(serializers.ListSerializer): child = serializers.IntegerField() self.Serializer = IntegerListSerializer @@ -70,7 +70,7 @@ class TestListSerializerContainingNestedSerializer: Tests for using a ListSerializer containing another serializer. """ - def setup(self): + def setup_method(self): class TestSerializer(serializers.Serializer): integer = serializers.IntegerField() boolean = serializers.BooleanField() @@ -156,7 +156,7 @@ class TestNestedListSerializer: Tests for using a ListSerializer as a field. """ - def setup(self): + def setup_method(self): class TestSerializer(serializers.Serializer): integers = serializers.ListSerializer(child=serializers.IntegerField()) booleans = serializers.ListSerializer(child=serializers.BooleanField()) @@ -278,7 +278,7 @@ class ParentSerializer(serializers.Serializer): class TestNestedListOfListsSerializer: - def setup(self): + def setup_method(self): class TestSerializer(serializers.Serializer): integers = serializers.ListSerializer( child=serializers.ListSerializer( @@ -594,7 +594,7 @@ class TestEmptyListSerializer: Tests the behaviour of ListSerializers when there is no data passed to it """ - def setup(self): + def setup_method(self): class ExampleListSerializer(serializers.ListSerializer): child = serializers.IntegerField() @@ -623,7 +623,7 @@ class TestMaxMinLengthListSerializer: Tests the behaviour of ListSerializers when max_length and min_length are used """ - def setup(self): + def setup_method(self): class IntegerSerializer(serializers.Serializer): some_int = serializers.IntegerField() diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index 09ac37f246..986972a65a 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -9,7 +9,7 @@ class TestNestedSerializer: - def setup(self): + def setup_method(self): class NestedSerializer(serializers.Serializer): one = serializers.IntegerField(max_value=10) two = serializers.IntegerField(max_value=10) @@ -54,7 +54,7 @@ def test_nested_serialize_no_data(self): class TestNotRequiredNestedSerializer: - def setup(self): + def setup_method(self): class NestedSerializer(serializers.Serializer): one = serializers.IntegerField(max_value=10) @@ -83,7 +83,7 @@ def test_multipart_validate(self): class TestNestedSerializerWithMany: - def setup(self): + def setup_method(self): class NestedSerializer(serializers.Serializer): example = serializers.IntegerField(max_value=10) @@ -181,7 +181,7 @@ def test_empty_not_allowed_if_allow_empty_is_set_to_false(self): class TestNestedSerializerWithList: - def setup(self): + def setup_method(self): class NestedSerializer(serializers.Serializer): example = serializers.MultipleChoiceField(choices=[1, 2, 3]) @@ -210,7 +210,7 @@ def test_nested_serializer_with_list_multipart(self): class TestNotRequiredNestedSerializerWithMany: - def setup(self): + def setup_method(self): class NestedSerializer(serializers.Serializer): one = serializers.IntegerField(max_value=10) From d5f228dd0012fe514e552224c1501782237eecb0 Mon Sep 17 00:00:00 2001 From: Henrik Wahlgren Date: Wed, 16 Nov 2022 15:31:50 +0100 Subject: [PATCH 025/178] Possibility to remove trailing zeros on DecimalFields representation (#6514) * Added normalize parameter to DecimalField to be able to strip trailing zeros. Fixes #6151. * Updated docs to include normalize option on DecimalField * Fixed linting error in test_fields * Removed comment and renamed normalize to normalize_output as suggested in code review Co-authored-by: Tom Christie --- docs/api-guide/fields.md | 1 + rest_framework/fields.py | 6 +++++- tests/test_fields.py | 21 +++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 0a96d0e0c7..4b4ea83cc1 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -295,6 +295,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 quantising 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`. #### Example usage diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 7f4c83b5df..88d606b282 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -963,10 +963,11 @@ class DecimalField(Field): MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None, - localize=False, rounding=None, **kwargs): + localize=False, rounding=None, normalize_output=False, **kwargs): self.max_digits = max_digits self.decimal_places = decimal_places self.localize = localize + self.normalize_output = normalize_output if coerce_to_string is not None: self.coerce_to_string = coerce_to_string if self.localize: @@ -1079,6 +1080,9 @@ def to_representation(self, value): quantized = self.quantize(value) + if self.normalize_output: + quantized = quantized.normalize() + if not coerce_to_string: return quantized if self.localize: diff --git a/tests/test_fields.py b/tests/test_fields.py index 72ef23dca5..3112fc2cce 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1251,6 +1251,27 @@ def test_part_precision_string_quantized_value_for_decimal(self): assert value == expected_digit_tuple +class TestNormalizedOutputValueDecimalField(TestCase): + """ + Test that we get the expected behavior of on DecimalField when normalize=True + """ + + def test_normalize_output(self): + field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize_output=True) + output = field.to_representation(Decimal('1.000')) + assert output == '1' + + def test_non_normalize_output(self): + field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize_output=False) + output = field.to_representation(Decimal('1.000')) + assert output == '1.000' + + def test_normalize_coeherce_to_string(self): + field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize_output=True, coerce_to_string=False) + output = field.to_representation(Decimal('1.000')) + assert output == Decimal('1') + + class TestNoDecimalPlaces(FieldValues): valid_inputs = { '0.12345': Decimal('0.12345'), From 759fc6f42e834b204501b17049daa473bdd2ce2c Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Thu, 17 Nov 2022 13:47:47 +0100 Subject: [PATCH 026/178] Make request consistently available in pagination classes (#8764) * Store request in CursorPagination field * Define request at start of pagination entrypoint --- rest_framework/pagination.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 8c434ea4f2..50e5aaacbe 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -195,6 +195,7 @@ def paginate_queryset(self, queryset, request, view=None): Paginate a queryset if required, either returning a page object, or `None` if pagination is not configured for this view. """ + self.request = request page_size = self.get_page_size(request) if not page_size: return None @@ -214,7 +215,6 @@ def paginate_queryset(self, queryset, request, view=None): # The browsable API should display pagination controls. self.display_page_controls = True - self.request = request return list(self.page) def get_page_number(self, request, paginator): @@ -379,13 +379,13 @@ class LimitOffsetPagination(BasePagination): template = 'rest_framework/pagination/numbers.html' def paginate_queryset(self, queryset, request, view=None): + self.request = request self.limit = self.get_limit(request) if self.limit is None: return None self.count = self.get_count(queryset) self.offset = self.get_offset(request) - self.request = request if self.count > self.limit and self.template is not None: self.display_page_controls = True @@ -599,6 +599,7 @@ class CursorPagination(BasePagination): offset_cutoff = 1000 def paginate_queryset(self, queryset, request, view=None): + self.request = request self.page_size = self.get_page_size(request) if not self.page_size: return None From 3e052376ace520135d42dcf52f35a51b7dcc5ac2 Mon Sep 17 00:00:00 2001 From: Ilaletdinov Almaz <45946541+blablatdinov@users.noreply.github.com> Date: Thu, 17 Nov 2022 16:13:09 +0300 Subject: [PATCH 027/178] Added http 102, 103, 421, and 425 status codes in documentation (#8763) --- docs/api-guide/status-codes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api-guide/status-codes.md b/docs/api-guide/status-codes.md index a37ba15d45..fb2f9bf138 100644 --- a/docs/api-guide/status-codes.md +++ b/docs/api-guide/status-codes.md @@ -41,6 +41,8 @@ This class of status code indicates a provisional response. There are no 1xx st HTTP_100_CONTINUE HTTP_101_SWITCHING_PROTOCOLS + HTTP_102_PROCESSING + HTTP_103_EARLY_HINTS ## Successful - 2xx @@ -93,9 +95,11 @@ The 4xx class of status code is intended for cases in which the client seems to HTTP_415_UNSUPPORTED_MEDIA_TYPE HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE HTTP_417_EXPECTATION_FAILED + HTTP_421_MISDIRECTED_REQUEST HTTP_422_UNPROCESSABLE_ENTITY HTTP_423_LOCKED HTTP_424_FAILED_DEPENDENCY + HTTP_425_TOO_EARLY HTTP_426_UPGRADE_REQUIRED HTTP_428_PRECONDITION_REQUIRED HTTP_429_TOO_MANY_REQUESTS From df60510db51eaee06f4b4483e31b1d2f7aa5ed7e Mon Sep 17 00:00:00 2001 From: Manos Date: Thu, 17 Nov 2022 14:13:18 +0000 Subject: [PATCH 028/178] Add 3rd party entry for rest-framework-roles (#8755) --- docs/api-guide/permissions.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 6e164d220e..d945e95c3b 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -324,6 +324,10 @@ The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to The [Django Rest Framework Roles][django-rest-framework-roles] package makes it easier to parameterize your API over multiple types of users. +## Rest Framework Roles + +The [Rest Framework Roles][rest-framework-roles] makes it super easy to protect views based on roles. Most importantly allows you to decouple accessibility logic from models and views in a clean human-readable way. + ## Django REST Framework API Key The [Django REST Framework API Key][djangorestframework-api-key] package provides permissions classes, models and helpers to add API key authorization to your API. It can be used to authorize internal or third-party backends and services (i.e. _machines_) which do not have a user account. API keys are stored securely using Django's password hashing infrastructure, and they can be viewed, edited and revoked at anytime in the Django admin. @@ -349,6 +353,7 @@ The [Django Rest Framework PSQ][drf-psq] package is an extension that gives supp [rest-condition]: https://github.com/caxap/rest_condition [dry-rest-permissions]: https://github.com/FJNR-inc/dry-rest-permissions [django-rest-framework-roles]: https://github.com/computer-lab/django-rest-framework-roles +[rest-framework-roles]: https://github.com/Pithikos/rest-framework-roles [djangorestframework-api-key]: https://florimondmanca.github.io/djangorestframework-api-key/ [django-rest-framework-role-filters]: https://github.com/allisson/django-rest-framework-role-filters [django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian From 21fdf066b4dce6ebef4539cded53fa3ab84ce78f Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Fri, 18 Nov 2022 17:09:05 +0600 Subject: [PATCH 029/178] pytest versions update (#8745) * pytest versions update * pytest>=7.0.0,<8.0 * pytest>=7.2.0,<8.0 * pytest>=6.2.0,<8.0 --- requirements/requirements-testing.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt index f6823e2e27..fb6dbf05fd 100644 --- a/requirements/requirements-testing.txt +++ b/requirements/requirements-testing.txt @@ -1,5 +1,5 @@ # Pytest for running the tests. -pytest>=6.1,<7.0 -pytest-cov>=2.10.1,<3.0 -pytest-django>=4.1.0,<5.0 +pytest>=6.2.0,<8.0 +pytest-cov>=4.0.0,<5.0 +pytest-django>=4.5.2,<5.0 importlib-metadata<5.0 From cac89ae65defa1a89cb931106775314f0671bb46 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Fri, 18 Nov 2022 21:32:10 +0600 Subject: [PATCH 030/178] update minimum version to psycopg2-binary>=2.9.5 (#8767) --- requirements/requirements-optionals.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt index f3bb9b709d..3de07d1c7b 100644 --- a/requirements/requirements-optionals.txt +++ b/requirements/requirements-optionals.txt @@ -4,6 +4,6 @@ coreschema==0.0.4 django-filter>=2.4.0,<3.0 django-guardian>=2.4.0,<2.5 markdown==3.3 -psycopg2-binary>=2.8.5,<2.9 +psycopg2-binary>=2.9.5,<2.10 pygments==2.12 pyyaml>=5.3.1,<5.4 From 2a2b092864a2b33b41336b83f8b2ebf6c5ef6076 Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Mon, 21 Nov 2022 11:47:21 +0100 Subject: [PATCH 031/178] Fix #8751 - Add support to Python 3.11 (#8752) --- .github/workflows/main.yml | 1 + docs/index.md | 2 +- setup.py | 1 + tox.ini | 6 +++++- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c88dc55cd9..b635573f26 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,6 +19,7 @@ jobs: - '3.8' - '3.9' - '3.10' + - '3.11' steps: - uses: actions/checkout@v3 diff --git a/docs/index.md b/docs/index.md index 0c428c8ec3..cab5511ac1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -85,7 +85,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) +* 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) We **highly recommend** and only officially support the latest patch release of diff --git a/setup.py b/setup.py index 1817401654..d004702680 100755 --- a/setup.py +++ b/setup.py @@ -104,6 +104,7 @@ def get_version(package): 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3 :: Only', 'Topic :: Internet :: WWW/HTTP', ], diff --git a/tox.ini b/tox.ini index 957d8a2e77..2b421f6c27 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist = {py36,py37,py38,py39}-django31, {py36,py37,py38,py39,py310}-django32, {py38,py39,py310}-{django40,django41,djangomain}, + {py311}-{django41,djangomain}, base,dist,docs, [travis:env] @@ -26,7 +27,7 @@ deps = django31: Django>=3.1,<3.2 django32: Django>=3.2,<4.0 django40: Django>=4.0,<4.1 - django41: Django>=4.1a1,<4.2 + django41: Django>=4.1,<4.2 djangomain: https://github.com/django/django/archive/main.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt @@ -59,3 +60,6 @@ ignore_outcome = true [testenv:py310-djangomain] ignore_outcome = true + +[testenv:py311-djangomain] +ignore_outcome = true From 751808c59b973b329d8da89c7ae78048e499cc40 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Mon, 21 Nov 2022 17:32:07 +0600 Subject: [PATCH 032/178] converted assertion to pytest style in test status (#8769) --- tests/test_status.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/test_status.py b/tests/test_status.py index b10f7df994..4d7aeb90a6 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -7,27 +7,27 @@ class TestStatus(TestCase): def test_status_categories(self): - self.assertFalse(is_informational(99)) - self.assertTrue(is_informational(100)) - self.assertTrue(is_informational(199)) - self.assertFalse(is_informational(200)) + assert not is_informational(99) + assert is_informational(100) + assert is_informational(199) + assert not is_informational(200) - self.assertFalse(is_success(199)) - self.assertTrue(is_success(200)) - self.assertTrue(is_success(299)) - self.assertFalse(is_success(300)) + assert not is_success(199) + assert is_success(200) + assert is_success(299) + assert not is_success(300) - self.assertFalse(is_redirect(299)) - self.assertTrue(is_redirect(300)) - self.assertTrue(is_redirect(399)) - self.assertFalse(is_redirect(400)) + assert not is_redirect(299) + assert is_redirect(300) + assert is_redirect(399) + assert not is_redirect(400) - self.assertFalse(is_client_error(399)) - self.assertTrue(is_client_error(400)) - self.assertTrue(is_client_error(499)) - self.assertFalse(is_client_error(500)) + assert not is_client_error(399) + assert is_client_error(400) + assert is_client_error(499) + assert not is_client_error(500) - self.assertFalse(is_server_error(499)) - self.assertTrue(is_server_error(500)) - self.assertTrue(is_server_error(599)) - self.assertFalse(is_server_error(600)) + assert not is_server_error(499) + assert is_server_error(500) + assert is_server_error(599) + assert not is_server_error(600) From dc300aa4e01c4cc53c5d74057dc307e31d8ef56d Mon Sep 17 00:00:00 2001 From: smt-insens <102955499+smt-insens@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:33:19 +0100 Subject: [PATCH 033/178] [FIX] add missing DurationField to SimpleMetada label_lookup (#8702) --- rest_framework/metadata.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py index 7708fe0a21..015587897d 100644 --- a/rest_framework/metadata.py +++ b/rest_framework/metadata.py @@ -48,6 +48,7 @@ class SimpleMetadata(BaseMetadata): serializers.DateField: 'date', serializers.DateTimeField: 'datetime', serializers.TimeField: 'time', + serializers.DurationField: 'duration', serializers.ChoiceField: 'choice', serializers.MultipleChoiceField: 'multiple choice', serializers.FileField: 'file upload', From 9e56f54efb422459dc8765caa1fd1a21ce583039 Mon Sep 17 00:00:00 2001 From: Jason Joy Atsu Winmorre <78548837+winmorre@users.noreply.github.com> Date: Tue, 22 Nov 2022 05:15:25 +0000 Subject: [PATCH 034/178] FloatField will crash if the input is a number that is too big (#8725) * FloatField will crash if the input is a number that is too big * Added Unit test for float field overflow error catch * Removed random import * Removed additional imported ValidationError * Update rest_framework/fields.py * Update tests/test_fields.py Co-authored-by: Asif Saif Uddin --- rest_framework/fields.py | 5 ++++- tests/test_fields.py | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 88d606b282..4bea5c419b 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -919,7 +919,8 @@ class FloatField(Field): 'invalid': _('A valid number is required.'), 'max_value': _('Ensure this value is less than or equal to {max_value}.'), 'min_value': _('Ensure this value is greater than or equal to {min_value}.'), - 'max_string_length': _('String value too large.') + 'max_string_length': _('String value too large.'), + 'overflow': _('Integer value too large to convert to float') } MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. @@ -945,6 +946,8 @@ def to_internal_value(self, data): return float(data) except (TypeError, ValueError): self.fail('invalid') + except OverflowError: + self.fail('overflow') def to_representation(self, value): return float(value) diff --git a/tests/test_fields.py b/tests/test_fields.py index 3112fc2cce..bcdcf15d86 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1,4 +1,5 @@ import datetime +import math import os import re import uuid @@ -1072,6 +1073,14 @@ class TestMinMaxFloatField(FieldValues): field = serializers.FloatField(min_value=1, max_value=3) +class TestFloatFieldOverFlowError(TestCase): + def test_overflow_error_float_field(self): + field = serializers.FloatField() + with pytest.raises(serializers.ValidationError) as exec_info: + field.to_internal_value(data=math.factorial(171)) + assert "Integer value too large to convert to float" in str(exec_info.value.detail) + + class TestDecimalField(FieldValues): """ Valid and invalid values for `DecimalField`. From 03c2ef1787c416a0e333d82089659a00bd0cbf80 Mon Sep 17 00:00:00 2001 From: 954 <510485871@qq.com> Date: Tue, 22 Nov 2022 14:01:01 +0800 Subject: [PATCH 035/178] Fixes instance check in ListSerializer.to_representation (#8726) (#8727) * Fixes 'RelatedManager' object is not iterable in ListSerializer.to_representation.(#8726) * Change to only BaseManager * Commit unit test * Update tests/test_serializer_lists.py * Update tests/test_serializer_lists.py * Update tests/test_serializer_lists.py * Update tests/test_serializer_lists.py * Update tests/test_serializer_lists.py * Update tests/test_serializer_lists.py * Format import * Format import Co-authored-by: Asif Saif Uddin --- rest_framework/serializers.py | 2 +- tests/models.py | 26 ++++++++++++++++++++ tests/test_serializer_lists.py | 43 ++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 7037c07cdc..eae6a0b2e1 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -683,7 +683,7 @@ def to_representation(self, data): """ # Dealing with nested relationships, data can be a Manager, # so, first get a queryset from the Manager if needed - iterable = data.all() if isinstance(data, models.Manager) else data + iterable = data.all() if isinstance(data, models.manager.BaseManager) else data return [ self.child.to_representation(item) for item in iterable diff --git a/tests/models.py b/tests/models.py index 666e9f0031..88e3d8dcaf 100644 --- a/tests/models.py +++ b/tests/models.py @@ -2,6 +2,8 @@ from django.contrib.auth.models import User from django.db import models +from django.db.models import QuerySet +from django.db.models.manager import BaseManager from django.utils.translation import gettext_lazy as _ @@ -124,3 +126,27 @@ class OneToOnePKSource(RESTFrameworkModel): target = models.OneToOneField( OneToOneTarget, primary_key=True, related_name='required_source', on_delete=models.CASCADE) + + +class CustomManagerModel(RESTFrameworkModel): + class CustomManager: + def __new__(cls, *args, **kwargs): + cls = BaseManager.from_queryset( + QuerySet + ) + return cls + + objects = CustomManager()() + # `CustomManager()` will return a `BaseManager` class. + # We need to instantiation it, so we write `CustomManager()()` here. + + text = models.CharField( + max_length=100, + verbose_name=_("Text comes here"), + help_text=_("Text description.") + ) + + o2o_target = models.ForeignKey(OneToOneTarget, + help_text='OneToOneTarget', + verbose_name='OneToOneTarget', + on_delete=models.CASCADE) diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index 4b60643a85..10463d29ab 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -6,6 +6,9 @@ from rest_framework import serializers from rest_framework.exceptions import ErrorDetail +from tests.models import ( + CustomManagerModel, NullableOneToOneSource, OneToOneTarget +) class BasicObject: @@ -683,3 +686,43 @@ def test_min_max_length_six_items(self): assert min_serializer.validated_data == input_data assert not max_min_serializer.is_valid() + + +@pytest.mark.django_db() +class TestToRepresentationManagerCheck: + """ + https://github.com/encode/django-rest-framework/issues/8726 + """ + + def setup_method(self): + class CustomManagerModelSerializer(serializers.ModelSerializer): + class Meta: + model = CustomManagerModel + fields = '__all__' + + class OneToOneTargetSerializer(serializers.ModelSerializer): + my_model = CustomManagerModelSerializer(many=True, source="custommanagermodel_set") + + class Meta: + model = OneToOneTarget + fields = '__all__' + depth = 3 + + class NullableOneToOneSourceSerializer(serializers.ModelSerializer): + target = OneToOneTargetSerializer() + + class Meta: + model = NullableOneToOneSource + fields = '__all__' + + self.serializer = NullableOneToOneSourceSerializer + + def test(self): + o2o_target = OneToOneTarget.objects.create(name='OneToOneTarget') + NullableOneToOneSource.objects.create( + name='NullableOneToOneSource', + target=o2o_target + ) + queryset = NullableOneToOneSource.objects.all() + serializer = self.serializer(queryset, many=True) + assert serializer.data From 0ae3323bd237b70d62873fb052cf5617fc446229 Mon Sep 17 00:00:00 2001 From: Shi Pengtao Date: Tue, 22 Nov 2022 14:37:31 +0800 Subject: [PATCH 036/178] fix 404 when page query parameter is empty string (#8578) * fix 404 when page query parameter is empty string * Update pagination.py * Update pagination.py Co-authored-by: Tom Christie --- rest_framework/pagination.py | 2 +- tests/test_pagination.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 50e5aaacbe..f5c5b913b5 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -218,7 +218,7 @@ def paginate_queryset(self, queryset, request, view=None): return list(self.page) def get_page_number(self, request, paginator): - page_number = request.query_params.get(self.page_query_param, 1) + page_number = request.query_params.get(self.page_query_param) or 1 if page_number in self.last_page_strings: page_number = paginator.num_pages return page_number diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 74a65bf506..2812c4489e 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -180,8 +180,9 @@ def get_paginated_content(self, queryset): def get_html_context(self): return self.pagination.get_html_context() - def test_no_page_number(self): - request = Request(factory.get('/')) + @pytest.mark.parametrize('url', ['/', '/?page=']) + def test_no_page_number(self, url): + request = Request(factory.get(url)) queryset = self.paginate_queryset(request) content = self.get_paginated_content(queryset) context = self.get_html_context() From 8175f05c8990abf02e8b44162a729a3560006c33 Mon Sep 17 00:00:00 2001 From: Subin Kim <32478597+sudosubin@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:18:47 +0900 Subject: [PATCH 037/178] Added pagination settings test case (#8362) --- tests/test_settings.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_settings.py b/tests/test_settings.py index b78125ff95..15624c0599 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -43,6 +43,31 @@ def test_compatibility_with_override_settings(self): assert api_settings.PAGE_SIZE is None, "Setting should have been restored" + def test_pagination_settings(self): + """ + Integration tests for pagination system check. + """ + from rest_framework.checks import pagination_system_check + + def get_pagination_error(error_id: str): + errors = pagination_system_check(app_configs=None) + return next((error for error in errors if error.id == error_id), None) + + self.assertIsNone(api_settings.PAGE_SIZE) + self.assertIsNone(api_settings.DEFAULT_PAGINATION_CLASS) + + pagination_error = get_pagination_error('rest_framework.W001') + self.assertIsNone(pagination_error) + + with override_settings(REST_FRAMEWORK={'PAGE_SIZE': 10}): + pagination_error = get_pagination_error('rest_framework.W001') + self.assertIsNotNone(pagination_error) + + default_pagination_class = 'rest_framework.pagination.PageNumberPagination' + with override_settings(REST_FRAMEWORK={'PAGE_SIZE': 10, 'DEFAULT_PAGINATION_CLASS': default_pagination_class}): + pagination_error = get_pagination_error('rest_framework.W001') + self.assertIsNone(pagination_error) + class TestSettingTypes(TestCase): def test_settings_consistently_coerced_to_list(self): From fd7d3a7b9275b52fbb8c666895b369af7ab016a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Tue, 22 Nov 2022 09:39:26 +0100 Subject: [PATCH 038/178] Update 6-viewsets-and-routers.md (#8590) * Update 6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md * Update docs/tutorial/6-viewsets-and-routers.md Co-authored-by: Asif Saif Uddin --- docs/tutorial/6-viewsets-and-routers.md | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index b7a7696a72..74789e3372 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -10,10 +10,11 @@ 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` views into a single `UserViewSet`. We can remove the two views, and replace them with a single class: +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: from rest_framework import viewsets + class UserViewSet(viewsets.ReadOnlyModelViewSet): """ This viewset automatically provides `list` and `retrieve` actions. @@ -25,13 +26,14 @@ Here we've used the `ReadOnlyModelViewSet` class to automatically provide the de Next we're going to replace the `SnippetList`, `SnippetDetail` and `SnippetHighlight` view classes. We can remove the three views, and again replace them with a single class. + from rest_framework import permissions from rest_framework.decorators import action from rest_framework.response import Response - from rest_framework import permissions + class SnippetViewSet(viewsets.ModelViewSet): """ - This viewset automatically provides `list`, `create`, `retrieve`, + This ViewSet automatically provides `list`, `create`, `retrieve`, `update` and `destroy` actions. Additionally we also provide an extra `highlight` action. @@ -64,9 +66,10 @@ To see what's going on under the hood let's first explicitly create a set of vie In the `snippets/urls.py` file we bind our `ViewSet` classes into a set of concrete views. - from snippets.views import SnippetViewSet, UserViewSet, api_root from rest_framework import renderers + from snippets.views import api_root, SnippetViewSet, UserViewSet + snippet_list = SnippetViewSet.as_view({ 'get': 'list', 'post': 'create' @@ -87,7 +90,7 @@ In the `snippets/urls.py` file we bind our `ViewSet` classes into a set of concr 'get': 'retrieve' }) -Notice how we're creating multiple views from each `ViewSet` class, by binding the http methods to the required action for each view. +Notice how we're creating multiple views from each `ViewSet` class, by binding the HTTP methods to the required action for each view. Now that we've bound our resources into concrete views, we can register the views with the URL conf as usual. @@ -108,9 +111,10 @@ Here's our re-wired `snippets/urls.py` file. from django.urls import path, include from rest_framework.routers import DefaultRouter + from snippets import views - # Create a router and register our viewsets with it. + # Create a router and register our ViewSets with it. router = DefaultRouter() router.register(r'snippets', views.SnippetViewSet, basename='snippet') router.register(r'users', views.UserViewSet, basename='user') @@ -120,12 +124,12 @@ Here's our re-wired `snippets/urls.py` file. path('', include(router.urls)), ] -Registering the viewsets with the router is similar to providing a urlpattern. We include two arguments - the URL prefix for the views, and the viewset itself. +Registering the ViewSets with the router is similar to providing a urlpattern. We include two arguments - the URL prefix for the views, and the view set itself. -The `DefaultRouter` class we're using also automatically creates the API root view for us, so we can now delete the `api_root` method from our `views` module. +The `DefaultRouter` class we're using also automatically creates the API root view for us, so we can now delete the `api_root` function from our `views` module. -## Trade-offs between views vs viewsets +## Trade-offs between views vs ViewSets -Using viewsets can be a really useful abstraction. It helps ensure that URL conventions will be consistent across your API, minimizes the amount of code you need to write, and allows you to concentrate on the interactions and representations your API provides rather than the specifics of the URL conf. +Using ViewSets can be a really useful abstraction. It helps ensure that URL conventions will be consistent across your API, minimizes the amount of code you need to write, and allows you to concentrate on the interactions and representations your API provides rather than the specifics of the URL conf. -That doesn't mean it's always the right approach to take. There's a similar set of trade-offs to consider as when using class-based views instead of function based views. Using viewsets is less explicit than building your views individually. +That doesn't mean it's always the right approach to take. There's a similar set of trade-offs to consider as when using class-based views instead of function-based views. Using ViewSets is less explicit than building your API views individually. From b87699c034816123c0ca0371078ccfdf702833c2 Mon Sep 17 00:00:00 2001 From: "T. Franzel" Date: Tue, 22 Nov 2022 14:53:54 +0100 Subject: [PATCH 039/178] Update schema generation doc & add deprecation notice #8453 (#8773) * Update schema generation doc & add deprecation notice #8453 * Update docs/topics/documenting-your-api.md Co-authored-by: Tom Christie * Update docs/topics/documenting-your-api.md Co-authored-by: Tom Christie * Update docs/topics/documenting-your-api.md Co-authored-by: Tom Christie Co-authored-by: T. Franzel <13507857+tfranzel@users.noreply.github.com> Co-authored-by: Asif Saif Uddin Co-authored-by: Tom Christie --- docs/api-guide/schemas.md | 18 ++++++++ docs/topics/documenting-your-api.md | 67 +++++++++++++++-------------- 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index e402019a4f..3f0b155306 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -9,6 +9,23 @@ source: > > — Heroku, [JSON Schema for the Heroku Platform API][cite] +--- + +**Deprecation notice:** + +REST framework's built-in support for generating OpenAPI schemas is +**deprecated** in favor of 3rd party packages that can provide this +functionality instead. The built-in support will be moved into a separate +package and then subsequently retired over the next releases. + +As a full-fledged replacement, we recommend the [drf-spectacular] package. +It has extensive support for generating OpenAPI 3 schemas from +REST framework APIs, with both automatic and customisable options available. +For further information please refer to +[Documenting your API](../topics/documenting-your-api.md#drf-spectacular). + +--- + API schemas are a useful tool that allow for a range of use cases, including generating reference documentation, or driving dynamic client libraries that can interact with your API. @@ -438,3 +455,4 @@ create a base `AutoSchema` subclass for your project that takes additional [openapi-generator]: https://github.com/OpenAPITools/openapi-generator [swagger-codegen]: https://github.com/swagger-api/swagger-codegen [info-object]: https://swagger.io/specification/#infoObject +[drf-spectacular]: https://drf-spectacular.readthedocs.io/en/latest/readme.html diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md index 5eabeee7bb..b85d6310ad 100644 --- a/docs/topics/documenting-your-api.md +++ b/docs/topics/documenting-your-api.md @@ -4,12 +4,42 @@ > > — Roy Fielding, [REST APIs must be hypertext driven][cite] -REST framework provides built-in support for generating OpenAPI schemas, which -can be used with tools that allow you to build API documentation. +REST framework provides a range of different choices for documenting your API. The following +is a non-exhaustive list of the most popular ones. -There are also a number of great third-party documentation packages available. +## Third party packages for OpenAPI support -## Generating documentation from OpenAPI schemas +### drf-spectacular + +[drf-spectacular][drf-spectacular] is an [OpenAPI 3][open-api] schema generation library with explicit +focus on extensibility, customizability and client generation. It is the recommended way for +generating and presenting OpenAPI schemas. + +The library aims to extract as much schema information as possible, while providing decorators and extensions for easy +customization. There is explicit support for [swagger-codegen][swagger], [SwaggerUI][swagger-ui] and [Redoc][redoc], +i18n, versioning, authentication, polymorphism (dynamic requests and responses), query/path/header parameters, +documentation and more. Several popular plugins for DRF are supported out-of-the-box as well. + +### drf-yasg + +[drf-yasg][drf-yasg] is a [Swagger / OpenAPI 2][swagger] generation tool implemented without using the schema generation provided +by Django Rest Framework. + +It aims to implement as much of the [OpenAPI 2][open-api] specification as possible - nested schemas, named models, +response bodies, enum/pattern/min/max validators, form parameters, etc. - and to generate documents usable with code +generation tools like `swagger-codegen`. + +This also translates into a very useful interactive documentation viewer in the form of `swagger-ui`: + +![Screenshot - drf-yasg][image-drf-yasg] + +--- + +## Built-in OpenAPI schema generation (deprecated) + +**Deprecation notice: REST framework's built-in support for generating OpenAPI schemas is +deprecated in favor of 3rd party packages that can provide this functionality instead. +As replacement, we recommend using the [drf-spectacular](#drf-spectacular) package.** There are a number of packages available that allow you to generate HTML documentation pages from OpenAPI schemas. @@ -124,35 +154,6 @@ urlpatterns = [ See the [ReDoc documentation][redoc] for advanced usage. -## Third party packages - -There are a number of mature third-party packages for providing API documentation. - -#### drf-yasg - Yet Another Swagger Generator - -[drf-yasg][drf-yasg] is a [Swagger][swagger] generation tool implemented without using the schema generation provided -by Django Rest Framework. - -It aims to implement as much of the [OpenAPI][open-api] specification as possible - nested schemas, named models, -response bodies, enum/pattern/min/max validators, form parameters, etc. - and to generate documents usable with code -generation tools like `swagger-codegen`. - -This also translates into a very useful interactive documentation viewer in the form of `swagger-ui`: - - -![Screenshot - drf-yasg][image-drf-yasg] - -#### drf-spectacular - Sane and flexible OpenAPI 3.0 schema generation for Django REST framework - -[drf-spectacular][drf-spectacular] is a [OpenAPI 3][open-api] schema generation tool with explicit focus on extensibility, -customizability and client generation. Usage patterns are very similar to [drf-yasg][drf-yasg]. - -It aims to extract as much schema information as possible, while providing decorators and extensions for easy -customization. There is explicit support for [swagger-codegen][swagger], [SwaggerUI][swagger-ui] and [Redoc][redoc], -i18n, versioning, authentication, polymorphism (dynamic requests and responses), query/path/header parameters, -documentation and more. Several popular plugins for DRF are supported out-of-the-box as well. - ---- ## Self describing APIs From c0d95cb9678b1693f8f1a8658d4665c51de87ddf Mon Sep 17 00:00:00 2001 From: Denny Biasiolli Date: Tue, 22 Nov 2022 15:21:16 +0100 Subject: [PATCH 040/178] Fix #8771 - Checking for authentication even if `_ignore_model_permissions = True` (#8772) --- rest_framework/permissions.py | 8 ++++---- tests/test_permissions.py | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index f3ebbcffa7..71de226f98 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -228,15 +228,15 @@ def _queryset(self, view): return view.queryset def has_permission(self, request, view): + if not request.user or ( + not request.user.is_authenticated and self.authenticated_users_only): + return False + # Workaround to ensure DjangoModelPermissions are not applied # to the root view when using DefaultRouter. if getattr(view, '_ignore_model_permissions', False): return True - if not request.user or ( - not request.user.is_authenticated and self.authenticated_users_only): - return False - queryset = self._queryset(view) perms = self.get_required_permissions(request.method, queryset.model) diff --git a/tests/test_permissions.py b/tests/test_permissions.py index f00b57ec19..aefff981ee 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -55,11 +55,16 @@ class EmptyListView(generics.ListCreateAPIView): permission_classes = [permissions.DjangoModelPermissions] +class IgnoredGetQuerySetListView(GetQuerySetListView): + _ignore_model_permissions = True + + root_view = RootView.as_view() api_root_view = DefaultRouter().get_api_root_view() instance_view = InstanceView.as_view() get_queryset_list_view = GetQuerySetListView.as_view() empty_list_view = EmptyListView.as_view() +ignored_get_queryset_list_view = IgnoredGetQuerySetListView.as_view() def basic_auth_header(username, password): @@ -107,6 +112,27 @@ def test_api_root_view_discard_default_django_model_permission(self): response = api_root_view(request) self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_ignore_model_permissions_with_unauthenticated_user(self): + """ + We check that the ``_ignore_model_permissions`` attribute + doesn't ignore the authentication. + """ + request = factory.get('/', format='json') + request.resolver_match = ResolverMatch('get', (), {}) + response = ignored_get_queryset_list_view(request) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_ignore_model_permissions_with_authenticated_user(self): + """ + We check that the ``_ignore_model_permissions`` attribute + with an authenticated user. + """ + request = factory.get('/', format='json', + HTTP_AUTHORIZATION=self.permitted_credentials) + request.resolver_match = ResolverMatch('get', (), {}) + response = ignored_get_queryset_list_view(request) + self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_get_queryset_has_create_permissions(self): request = factory.post('/', {'text': 'foobar'}, format='json', HTTP_AUTHORIZATION=self.permitted_credentials) From ebde56b932e638c83d3a164ca89b618c520d0ca2 Mon Sep 17 00:00:00 2001 From: hashlash Date: Wed, 23 Nov 2022 21:42:06 +0700 Subject: [PATCH 041/178] Represent SafeString as plain string on schema rendering. (#8429) * Use SafeString.represent_str to represent SafeString as str * Add SafeString yaml rendering test --- rest_framework/renderers.py | 2 ++ tests/schemas/test_openapi.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 3f47580f15..8de0a77a1a 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -19,6 +19,7 @@ from django.template import engines, loader from django.urls import NoReverseMatch from django.utils.html import mark_safe +from django.utils.safestring import SafeString from rest_framework import VERSION, exceptions, serializers, status from rest_framework.compat import ( @@ -1055,6 +1056,7 @@ def render(self, data, media_type=None, renderer_context=None): class Dumper(yaml.Dumper): def ignore_aliases(self, data): return True + Dumper.add_representer(SafeString, Dumper.represent_str) return yaml.dump(data, default_flow_style=False, sort_keys=False, Dumper=Dumper).encode('utf-8') diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 7542bb6152..57007e9e84 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -5,6 +5,7 @@ from django.db import models from django.test import RequestFactory, TestCase, override_settings from django.urls import path +from django.utils.safestring import SafeString from django.utils.translation import gettext_lazy as _ from rest_framework import filters, generics, pagination, routers, serializers @@ -583,6 +584,11 @@ def test_openapi_yaml_rendering_without_aliases(self): renderer.render(data) == b'o2:\n test: test\no1:\n test: test\n' # py <= 3.5 ) + def test_openapi_yaml_safestring_render(self): + renderer = OpenAPIRenderer() + data = {'o1': SafeString('test')} + assert renderer.render(data) == b'o1: test\n' + def test_serializer_filefield(self): path = '/{id}/' method = 'POST' From 9e328a9549b8cc5fbeff7b70cf2523fa3d97518f Mon Sep 17 00:00:00 2001 From: Shinya Ohyanagi Date: Thu, 24 Nov 2022 16:48:05 +0900 Subject: [PATCH 042/178] Fix OpenAPI operation name plural appropriately (#8017) --- requirements/requirements-optionals.txt | 1 + rest_framework/schemas/openapi.py | 5 +++-- tests/schemas/test_openapi.py | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt index 3de07d1c7b..3fffcd529c 100644 --- a/requirements/requirements-optionals.txt +++ b/requirements/requirements-optionals.txt @@ -3,6 +3,7 @@ coreapi==2.3.1 coreschema==0.0.4 django-filter>=2.4.0,<3.0 django-guardian>=2.4.0,<2.5 +inflection==0.5.1 markdown==3.3 psycopg2-binary>=2.9.5,<2.10 pygments==2.12 diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 2f9fb9f28f..8e0f400caa 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -11,6 +11,7 @@ ) from django.db import models from django.utils.encoding import force_str +from inflection import pluralize from rest_framework import ( RemovedInDRF315Warning, exceptions, renderers, serializers @@ -247,8 +248,8 @@ def get_operation_id_base(self, path, method, action): if name.endswith(action.title()): # ListView, UpdateAPIView, ThingDelete ... name = name[:-len(action)] - if action == 'list' and not name.endswith('s'): # listThings instead of listThing - name += 's' + if action == 'list': + name = pluralize(name) return name diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 57007e9e84..e39c9df473 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -715,6 +715,21 @@ def test_operation_id_custom_name(self): operationId = inspector.get_operation_id(path, method) assert operationId == 'listUlysses' + def test_operation_id_plural(self): + path = '/' + method = 'GET' + + view = create_view( + views.ExampleGenericAPIView, + method, + create_request(path), + ) + inspector = AutoSchema(operation_id_base='City') + inspector.view = view + + operationId = inspector.get_operation_id(path, method) + assert operationId == 'listCities' + def test_operation_id_override_get(self): class CustomSchema(AutoSchema): def get_operation_id(self, path, method): From 52f4139674ed8156d6956e6fca507f5aea64f627 Mon Sep 17 00:00:00 2001 From: Peter Thomassen <4242683+peterthomassen@users.noreply.github.com> Date: Thu, 24 Nov 2022 12:27:45 +0100 Subject: [PATCH 043/178] Properly handle OverflowError in DurationField deserialization (#8042) Related: https://github.com/django/django/pull/8870/files --- rest_framework/fields.py | 6 +++++- tests/test_fields.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 4bea5c419b..4eae004dc6 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1329,6 +1329,7 @@ class DurationField(Field): 'invalid': _('Duration has wrong format. Use one of these formats instead: {format}.'), 'max_value': _('Ensure this value is less than or equal to {max_value}.'), 'min_value': _('Ensure this value is greater than or equal to {min_value}.'), + 'overflow': _('The number of days must be between {min_days} and {max_days}.'), } def __init__(self, **kwargs): @@ -1347,7 +1348,10 @@ def __init__(self, **kwargs): def to_internal_value(self, value): if isinstance(value, datetime.timedelta): return value - parsed = parse_duration(str(value)) + try: + parsed = parse_duration(str(value)) + except OverflowError: + self.fail('overflow', min_days=datetime.timedelta.min.days, max_days=datetime.timedelta.max.days) if parsed is not None: return parsed self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]') diff --git a/tests/test_fields.py b/tests/test_fields.py index bcdcf15d86..648c2a9c9a 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1642,10 +1642,14 @@ class TestDurationField(FieldValues): '08:01': datetime.timedelta(minutes=8, seconds=1), datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123): datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123), 3600: datetime.timedelta(hours=1), + '-999999999 00': datetime.timedelta(days=-999999999), + '999999999 00': datetime.timedelta(days=999999999), } invalid_inputs = { 'abc': ['Duration has wrong format. Use one of these formats instead: [DD] [HH:[MM:]]ss[.uuuuuu].'], '3 08:32 01.123': ['Duration has wrong format. Use one of these formats instead: [DD] [HH:[MM:]]ss[.uuuuuu].'], + '-1000000000 00': ['The number of days must be between -999999999 and 999999999.'], + '1000000000 00': ['The number of days must be between -999999999 and 999999999.'], } outputs = { datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123): '3 08:32:01.000123', From 4ef0fc1ca0c492d3b577c35704b5b8a059139915 Mon Sep 17 00:00:00 2001 From: Giebisch <39874595+Giebisch@users.noreply.github.com> Date: Sun, 27 Nov 2022 11:45:54 +0100 Subject: [PATCH 044/178] Implemented Verbose Name Translation for TokenProxy (#8713) --- .../migrations/0004_alter_tokenproxy_options.py | 17 +++++++++++++++++ rest_framework/authtoken/models.py | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 rest_framework/authtoken/migrations/0004_alter_tokenproxy_options.py diff --git a/rest_framework/authtoken/migrations/0004_alter_tokenproxy_options.py b/rest_framework/authtoken/migrations/0004_alter_tokenproxy_options.py new file mode 100644 index 0000000000..0ca9f5d796 --- /dev/null +++ b/rest_framework/authtoken/migrations/0004_alter_tokenproxy_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.3 on 2022-11-24 21:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('authtoken', '0003_tokenproxy'), + ] + + operations = [ + migrations.AlterModelOptions( + name='tokenproxy', + options={'verbose_name': 'Token', 'verbose_name_plural': 'Tokens'}, + ), + ] diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 5a143d936c..6a17c24525 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -51,4 +51,5 @@ def pk(self): class Meta: proxy = 'rest_framework.authtoken' in settings.INSTALLED_APPS abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS - verbose_name = "token" + verbose_name = _("Token") + verbose_name_plural = _("Tokens") From 3bf611787a6ca8fedcf69dde3776a308da1c7f30 Mon Sep 17 00:00:00 2001 From: Thomas Irgang Date: Sun, 27 Nov 2022 13:53:48 +0100 Subject: [PATCH 045/178] Register Django urls (#8778) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without adding the URLs manually, the users and groups APIs were not usable. My env: (venv) ➜ tutorial pip freeze asgiref==3.5.2 astroid==2.12.13 autopep8==2.0.0 dill==0.3.6 Django==4.1.3 djangorestframework==3.14.0 isort==5.10.1 lazy-object-proxy==1.8.0 mccabe==0.7.0 platformdirs==2.5.4 pycodestyle==2.10.0 pylint==2.15.6 pylint-django==2.5.3 pylint-plugin-utils==0.7 pytz==2022.6 sqlparse==0.4.3 tomli==2.0.1 tomlkit==0.11.6 wrapt==1.14.1 --- docs/tutorial/quickstart.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 754b46f9ab..09f249f6e1 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -132,6 +132,8 @@ 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. From 5435b2c9f0ea58196236a1609e915cc1ed130ba4 Mon Sep 17 00:00:00 2001 From: "T. Franzel" Date: Mon, 28 Nov 2022 04:11:10 +0100 Subject: [PATCH 046/178] mitigate global dependency on inflection #8017 (#8781) --- rest_framework/schemas/openapi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 8e0f400caa..840f6977ec 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -11,7 +11,6 @@ ) from django.db import models from django.utils.encoding import force_str -from inflection import pluralize from rest_framework import ( RemovedInDRF315Warning, exceptions, renderers, serializers @@ -249,6 +248,8 @@ def get_operation_id_base(self, path, method, action): name = name[:-len(action)] if action == 'list': + from inflection import pluralize + name = pluralize(name) return name From f0095b4de2686b062aff1e1e7494f41c034ba3c6 Mon Sep 17 00:00:00 2001 From: "T. Franzel" Date: Mon, 28 Nov 2022 14:41:08 +0100 Subject: [PATCH 047/178] Remove Core API mentions from docs (#8780) * remove coreapi generator mentions & hidden docs * remove coreapi doc & redirect broken links to github snapshot --- docs/api-guide/filtering.md | 9 - docs/api-guide/pagination.md | 9 - docs/community/3.10-announcement.md | 2 +- docs/community/3.4-announcement.md | 2 +- docs/community/3.6-announcement.md | 4 +- .../coreapi/7-schemas-and-client-libraries.md | 242 ----- docs/coreapi/from-documenting-your-api.md | 182 ---- docs/coreapi/index.md | 29 - docs/coreapi/schemas.md | 854 ------------------ docs/topics/api-clients.md | 527 ----------- mkdocs.yml | 1 - 11 files changed, 4 insertions(+), 1857 deletions(-) delete mode 100644 docs/coreapi/7-schemas-and-client-libraries.md delete mode 100644 docs/coreapi/from-documenting-your-api.md delete mode 100644 docs/coreapi/index.md delete mode 100644 docs/coreapi/schemas.md delete mode 100644 docs/topics/api-clients.md diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index 9a4ae27457..be8d10e9c1 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -335,15 +335,6 @@ Generic filters may also present an interface in the browsable API. To do so you The method should return a rendered HTML string. -## Filtering & schemas - -You can also make the filter controls available to the schema autogeneration -that REST framework provides, by implementing a `get_schema_fields()` method. This method should have the following signature: - -`get_schema_fields(self, view)` - -The method should return a list of `coreapi.Field` instances. - # Third party packages The following third party packages provide additional filter implementations. diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index 3081e2bc36..6754fd5c09 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -264,15 +264,6 @@ API responses for list endpoints will now include a `Link` header, instead of in *A custom pagination style, using the 'Link' header'* -## Pagination & schemas - -You can also make the pagination controls available to the schema autogeneration -that REST framework provides, by implementing a `get_schema_fields()` method. This method should have the following signature: - -`get_schema_fields(self, view)` - -The method should return a list of `coreapi.Field` instances. - --- # HTML pagination controls diff --git a/docs/community/3.10-announcement.md b/docs/community/3.10-announcement.md index 19748aa40d..23b6330a7b 100644 --- a/docs/community/3.10-announcement.md +++ b/docs/community/3.10-announcement.md @@ -142,6 +142,6 @@ continued development by **[signing up for a paid plan][funding]**. *Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), and [Lights On Software](https://lightsonsoftware.com).* -[legacy-core-api-docs]:https://github.com/encode/django-rest-framework/blob/master/docs/coreapi/index.md +[legacy-core-api-docs]:https://github.com/encode/django-rest-framework/blob/3.14.0/docs/coreapi/index.md [sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors [funding]: funding.md diff --git a/docs/community/3.4-announcement.md b/docs/community/3.4-announcement.md index 6d5f0399c5..2954b36b81 100644 --- a/docs/community/3.4-announcement.md +++ b/docs/community/3.4-announcement.md @@ -187,7 +187,7 @@ The full set of itemized release notes [are available here][release-notes]. [api-blueprint]: https://apiblueprint.org/ [tut-7]: ../tutorial/7-schemas-and-client-libraries/ [schema-generation]: ../api-guide/schemas/ -[api-clients]: ../topics/api-clients.md +[api-clients]: https://github.com/encode/django-rest-framework/blob/3.14.0/docs/topics/api-clients.md [milestone]: https://github.com/encode/django-rest-framework/milestone/35 [release-notes]: release-notes#34 [metadata]: ../api-guide/metadata/#custom-metadata-classes diff --git a/docs/community/3.6-announcement.md b/docs/community/3.6-announcement.md index 35704eb583..9e45473eee 100644 --- a/docs/community/3.6-announcement.md +++ b/docs/community/3.6-announcement.md @@ -195,5 +195,5 @@ on realtime support, for the 3.7 release. [sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors [funding]: funding.md [api-docs]: ../topics/documenting-your-api.md -[js-docs]: ../topics/api-clients.md#javascript-client-library -[py-docs]: ../topics/api-clients.md#python-client-library +[js-docs]: https://github.com/encode/django-rest-framework/blob/3.14.0/docs/topics/api-clients.md#javascript-client-library +[py-docs]: https://github.com/encode/django-rest-framework/blob/3.14.0/docs/topics/api-clients.md#python-client-library diff --git a/docs/coreapi/7-schemas-and-client-libraries.md b/docs/coreapi/7-schemas-and-client-libraries.md deleted file mode 100644 index d95019dab6..0000000000 --- a/docs/coreapi/7-schemas-and-client-libraries.md +++ /dev/null @@ -1,242 +0,0 @@ -# Tutorial 7: Schemas & client libraries - ----- - -**DEPRECATION NOTICE:** Use of CoreAPI-based schemas were deprecated with the introduction of native OpenAPI-based schema generation as of Django REST Framework v3.10. See the [Version 3.10 Release Announcement](../community/3.10-announcement.md) for more details. - -If you are looking for information regarding schemas, you might want to look at these updated resources: - -1. [Schema](../api-guide/schemas.md) -2. [Documenting your API](../topics/documenting-your-api.md) - ----- - -A schema is a machine-readable document that describes the available API -endpoints, their URLS, and what operations they support. - -Schemas can be a useful tool for auto-generated documentation, and can also -be used to drive dynamic client libraries that can interact with the API. - -## Core API - -In order to provide schema support REST framework uses [Core API][coreapi]. - -Core API is a document specification for describing APIs. It is used to provide -an internal representation format of the available endpoints and possible -interactions that an API exposes. It can either be used server-side, or -client-side. - -When used server-side, Core API allows an API to support rendering to a wide -range of schema or hypermedia formats. - -When used client-side, Core API allows for dynamically driven client libraries -that can interact with any API that exposes a supported schema or hypermedia -format. - -## Adding a schema - -REST framework supports either explicitly defined schema views, or -automatically generated schemas. Since we're using viewsets and routers, -we can simply use the automatic schema generation. - -You'll need to install the `coreapi` python package in order to include an -API schema, and `pyyaml` to render the schema into the commonly used -YAML-based OpenAPI format. - - $ pip install coreapi pyyaml - -We can now include a schema for our API, by including an autogenerated schema -view in our URL configuration. - -```python -from rest_framework.schemas import get_schema_view - -schema_view = get_schema_view(title='Pastebin API') - -urlpatterns = [ -    path('schema/', schema_view), - ... -] -``` - -If you visit the `/schema/` endpoint in a browser you should now see `corejson` -representation become available as an option. - -![Schema format](../img/corejson-format.png) - -We can also request the schema from the command line, by specifying the desired -content type in the `Accept` header. - - $ http http://127.0.0.1:8000/schema/ Accept:application/coreapi+json - HTTP/1.0 200 OK - Allow: GET, HEAD, OPTIONS - Content-Type: application/coreapi+json - - { - "_meta": { - "title": "Pastebin API" - }, - "_type": "document", - ... - -The default output style is to use the [Core JSON][corejson] encoding. - -Other schema formats, such as [Open API][openapi] (formerly Swagger) are -also supported. - -## Using a command line client - -Now that our API is exposing a schema endpoint, we can use a dynamic client -library to interact with the API. To demonstrate this, let's use the -Core API command line client. - -The command line client is available as the `coreapi-cli` package: - - $ pip install coreapi-cli - -Now check that it is available on the command line... - - $ coreapi - Usage: coreapi [OPTIONS] COMMAND [ARGS]... - - Command line client for interacting with CoreAPI services. - - Visit https://www.coreapi.org/ for more information. - - Options: - --version Display the package version number. - --help Show this message and exit. - - Commands: - ... - -First we'll load the API schema using the command line client. - - $ coreapi get http://127.0.0.1:8000/schema/ - - snippets: { - highlight(id) - list() - read(id) - } - users: { - list() - read(id) - } - -We haven't authenticated yet, so right now we're only able to see the read only -endpoints, in line with how we've set up the permissions on the API. - -Let's try listing the existing snippets, using the command line client: - - $ coreapi action snippets list - [ - { - "url": "http://127.0.0.1:8000/snippets/1/", - "id": 1, - "highlight": "http://127.0.0.1:8000/snippets/1/highlight/", - "owner": "lucy", - "title": "Example", - "code": "print('hello, world!')", - "linenos": true, - "language": "python", - "style": "friendly" - }, - ... - -Some of the API endpoints require named parameters. For example, to get back -the highlight HTML for a particular snippet we need to provide an id. - - $ coreapi action snippets highlight --param id=1 - - - - - Example - ... - -## Authenticating our client - -If we want to be able to create, edit and delete snippets, we'll need to -authenticate as a valid user. In this case we'll just use basic auth. - -Make sure to replace the `` and `` below with your -actual username and password. - - $ coreapi credentials add 127.0.0.1 : --auth basic - Added credentials - 127.0.0.1 "Basic <...>" - -Now if we fetch the schema again, we should be able to see the full -set of available interactions. - - $ coreapi reload - Pastebin API "http://127.0.0.1:8000/schema/"> - snippets: { - create(code, [title], [linenos], [language], [style]) - delete(id) - highlight(id) - list() - partial_update(id, [title], [code], [linenos], [language], [style]) - read(id) - update(id, code, [title], [linenos], [language], [style]) - } - users: { - list() - read(id) - } - -We're now able to interact with these endpoints. For example, to create a new -snippet: - - $ coreapi action snippets create --param title="Example" --param code="print('hello, world')" - { - "url": "http://127.0.0.1:8000/snippets/7/", - "id": 7, - "highlight": "http://127.0.0.1:8000/snippets/7/highlight/", - "owner": "lucy", - "title": "Example", - "code": "print('hello, world')", - "linenos": false, - "language": "python", - "style": "friendly" - } - -And to delete a snippet: - - $ coreapi action snippets delete --param id=7 - -As well as the command line client, developers can also interact with your -API using client libraries. The Python client library is the first of these -to be available, and a Javascript client library is planned to be released -soon. - -For more details on customizing schema generation and using Core API -client libraries you'll need to refer to the full documentation. - -## Reviewing our work - -With an incredibly small amount of code, we've now got a complete pastebin Web API, which is fully web browsable, includes a schema-driven client library, and comes complete with authentication, per-object permissions, and multiple renderer formats. - -We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views. - -You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox]. - -## Onwards and upwards - -We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here are a few places you can start: - -* Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests. -* Join the [REST framework discussion group][group], and help build the community. -* Follow [the author][twitter] on Twitter and say hi. - -**Now go build awesome things.** - -[coreapi]: https://www.coreapi.org/ -[corejson]: https://www.coreapi.org/specification/encoding/#core-json-encoding -[openapi]: https://openapis.org/ -[repo]: https://github.com/encode/rest-framework-tutorial -[sandbox]: https://restframework.herokuapp.com/ -[github]: https://github.com/encode/django-rest-framework -[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework -[twitter]: https://twitter.com/_tomchristie diff --git a/docs/coreapi/from-documenting-your-api.md b/docs/coreapi/from-documenting-your-api.md deleted file mode 100644 index f8ddff019e..0000000000 --- a/docs/coreapi/from-documenting-your-api.md +++ /dev/null @@ -1,182 +0,0 @@ - -## Built-in API documentation - ----- - -**DEPRECATION NOTICE:** Use of CoreAPI-based schemas were deprecated with the introduction of native OpenAPI-based schema generation as of Django REST Framework v3.10. See the [Version 3.10 Release Announcement](../community/3.10-announcement.md) for more details. - -If you are looking for information regarding schemas, you might want to look at these updated resources: - -1. [Schema](../api-guide/schemas.md) -2. [Documenting your API](../topics/documenting-your-api.md) - ----- - -The built-in API documentation includes: - -* Documentation of API endpoints. -* Automatically generated code samples for each of the available API client libraries. -* Support for API interaction. - -### Installation - -The `coreapi` library is required as a dependency for the API docs. Make sure -to install the latest version. The `Pygments` and `Markdown` libraries -are optional but recommended. - -To install the API documentation, you'll need to include it in your project's URLconf: - - from rest_framework.documentation import include_docs_urls - - urlpatterns = [ - ... - path('docs/', include_docs_urls(title='My API title')) - ] - -This will include two different views: - - * `/docs/` - The documentation page itself. - * `/docs/schema.js` - A JavaScript resource that exposes the API schema. - ---- - -**Note**: By default `include_docs_urls` configures the underlying `SchemaView` to generate _public_ schemas. -This means that views will not be instantiated with a `request` instance. i.e. Inside the view `self.request` will be `None`. - -To be compatible with this behavior, methods (such as `get_serializer` or `get_serializer_class` etc.) which inspect `self.request` or, particularly, `self.request.user` may need to be adjusted to handle this case. - -You may ensure views are given a `request` instance by calling `include_docs_urls` with `public=False`: - - from rest_framework.documentation import include_docs_urls - - urlpatterns = [ - ... - # Generate schema with valid `request` instance: - path('docs/', include_docs_urls(title='My API title', public=False)) - ] - - ---- - - -### Documenting your views - -You can document your views by including docstrings that describe each of the available actions. -For example: - - class UserList(generics.ListAPIView): - """ - Return a list of all the existing users. - """ - -If a view supports multiple methods, you should split your documentation using `method:` style delimiters. - - class UserList(generics.ListCreateAPIView): - """ - get: - Return a list of all the existing users. - - post: - Create a new user instance. - """ - -When using viewsets, you should use the relevant action names as delimiters. - - class UserViewSet(viewsets.ModelViewSet): - """ - retrieve: - Return the given user. - - list: - Return a list of all the existing users. - - create: - Create a new user instance. - """ - -Custom actions on viewsets can also be documented in a similar way using the method names -as delimiters or by attaching the documentation to action mapping methods. - - class UserViewSet(viewsets.ModelViewset): - ... - - @action(detail=False, methods=['get', 'post']) - def some_action(self, request, *args, **kwargs): - """ - get: - A description of the get method on the custom action. - - post: - A description of the post method on the custom action. - """ - - @some_action.mapping.put - def put_some_action(): - """ - A description of the put method on the custom action. - """ - - -### `documentation` API Reference - -The `rest_framework.documentation` module provides three helper functions to help configure the interactive API documentation, `include_docs_urls` (usage shown above), `get_docs_view` and `get_schemajs_view`. - - `include_docs_urls` employs `get_docs_view` and `get_schemajs_view` to generate the url patterns for the documentation page and JavaScript resource that exposes the API schema respectively. They expose the following options for customisation. (`get_docs_view` and `get_schemajs_view` ultimately call `rest_frameworks.schemas.get_schema_view()`, see the Schemas docs for more options there.) - -#### `include_docs_urls` - -* `title`: Default `None`. May be used to provide a descriptive title for the schema definition. -* `description`: Default `None`. May be used to provide a description for the schema definition. -* `schema_url`: Default `None`. May be used to pass a canonical base URL for the schema. -* `public`: Default `True`. Should the schema be considered _public_? If `True` schema is generated without a `request` instance being passed to views. -* `patterns`: Default `None`. A list of URLs to inspect when generating the schema. If `None` project's URL conf will be used. -* `generator_class`: Default `rest_framework.schemas.SchemaGenerator`. May be used to specify a `SchemaGenerator` subclass to be passed to the `SchemaView`. -* `authentication_classes`: Default `api_settings.DEFAULT_AUTHENTICATION_CLASSES`. May be used to pass custom authentication classes to the `SchemaView`. -* `permission_classes`: Default `api_settings.DEFAULT_PERMISSION_CLASSES` May be used to pass custom permission classes to the `SchemaView`. -* `renderer_classes`: Default `None`. May be used to pass custom renderer classes to the `SchemaView`. - -#### `get_docs_view` - -* `title`: Default `None`. May be used to provide a descriptive title for the schema definition. -* `description`: Default `None`. May be used to provide a description for the schema definition. -* `schema_url`: Default `None`. May be used to pass a canonical base URL for the schema. -* `public`: Default `True`. If `True` schema is generated without a `request` instance being passed to views. -* `patterns`: Default `None`. A list of URLs to inspect when generating the schema. If `None` project's URL conf will be used. -* `generator_class`: Default `rest_framework.schemas.SchemaGenerator`. May be used to specify a `SchemaGenerator` subclass to be passed to the `SchemaView`. -* `authentication_classes`: Default `api_settings.DEFAULT_AUTHENTICATION_CLASSES`. May be used to pass custom authentication classes to the `SchemaView`. -* `permission_classes`: Default `api_settings.DEFAULT_PERMISSION_CLASSES`. May be used to pass custom permission classes to the `SchemaView`. -* `renderer_classes`: Default `None`. May be used to pass custom renderer classes to the `SchemaView`. If `None` the `SchemaView` will be configured with `DocumentationRenderer` and `CoreJSONRenderer` renderers, corresponding to the (default) `html` and `corejson` formats. - -#### `get_schemajs_view` - -* `title`: Default `None`. May be used to provide a descriptive title for the schema definition. -* `description`: Default `None`. May be used to provide a description for the schema definition. -* `schema_url`: Default `None`. May be used to pass a canonical base URL for the schema. -* `public`: Default `True`. If `True` schema is generated without a `request` instance being passed to views. -* `patterns`: Default `None`. A list of URLs to inspect when generating the schema. If `None` project's URL conf will be used. -* `generator_class`: Default `rest_framework.schemas.SchemaGenerator`. May be used to specify a `SchemaGenerator` subclass to be passed to the `SchemaView`. -* `authentication_classes`: Default `api_settings.DEFAULT_AUTHENTICATION_CLASSES`. May be used to pass custom authentication classes to the `SchemaView`. -* `permission_classes`: Default `api_settings.DEFAULT_PERMISSION_CLASSES` May be used to pass custom permission classes to the `SchemaView`. - - -### Customising code samples - -The built-in API documentation includes automatically generated code samples for -each of the available API client libraries. - -You may customise these samples by subclassing `DocumentationRenderer`, setting -`languages` to the list of languages you wish to support: - - from rest_framework.renderers import DocumentationRenderer - - - class CustomRenderer(DocumentationRenderer): - languages = ['ruby', 'go'] - -For each language you need to provide an `intro` template, detailing installation instructions and such, -plus a generic template for making API requests, that can be filled with individual request details. -See the [templates for the bundled languages][client-library-templates] for examples. - ---- - -[client-library-templates]: https://github.com/encode/django-rest-framework/tree/master/rest_framework/templates/rest_framework/docs/langs \ No newline at end of file diff --git a/docs/coreapi/index.md b/docs/coreapi/index.md deleted file mode 100644 index dbcb115840..0000000000 --- a/docs/coreapi/index.md +++ /dev/null @@ -1,29 +0,0 @@ -# Legacy CoreAPI Schemas Docs - -Use of CoreAPI-based schemas were deprecated with the introduction of native OpenAPI-based schema generation as of Django REST Framework v3.10. - -See the [Version 3.10 Release Announcement](../community/3.10-announcement.md) for more details. - ----- - -You can continue to use CoreAPI schemas by setting the appropriate default schema class: - -```python -# In settings.py -REST_FRAMEWORK = { - 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', -} -``` - -Under-the-hood, any subclass of `coreapi.AutoSchema` here will trigger use of the old CoreAPI schemas. -**Otherwise** you will automatically be opted-in to the new OpenAPI schemas. - -All CoreAPI related code will be removed in Django REST Framework v3.12. Switch to OpenAPI schemas by then. - ----- - -For reference this folder contains the old CoreAPI related documentation: - -* [Tutorial 7: Schemas & client libraries](https://github.com/encode/django-rest-framework/blob/master/docs/coreapi//7-schemas-and-client-libraries.md). -* [Excerpts from _Documenting your API_ topic page](https://github.com/encode/django-rest-framework/blob/master/docs/coreapi//from-documenting-your-api.md). -* [`rest_framework.schemas` API Reference](https://github.com/encode/django-rest-framework/blob/master/docs/coreapi//schemas.md). diff --git a/docs/coreapi/schemas.md b/docs/coreapi/schemas.md deleted file mode 100644 index 73fc7b67d1..0000000000 --- a/docs/coreapi/schemas.md +++ /dev/null @@ -1,854 +0,0 @@ -source: schemas.py - -# Schemas - ----- - -**DEPRECATION NOTICE:** Use of CoreAPI-based schemas were deprecated with the introduction of native OpenAPI-based schema generation as of Django REST Framework v3.10. See the [Version 3.10 Release Announcement](../community/3.10-announcement.md) for more details. - -You are probably looking for [this page](../api-guide/schemas.md) if you want latest information regarding schemas. - ----- - -> A machine-readable [schema] describes what resources are available via the API, what their URLs are, how they are represented and what operations they support. -> -> — Heroku, [JSON Schema for the Heroku Platform API][cite] - -API schemas are a useful tool that allow for a range of use cases, including -generating reference documentation, or driving dynamic client libraries that -can interact with your API. - -## Install Core API & PyYAML - -You'll need to install the `coreapi` package in order to add schema support -for REST framework. You probably also want to install `pyyaml`, so that you -can render the schema into the commonly used YAML-based OpenAPI format. - - pip install coreapi pyyaml - -## Quickstart - -There are two different ways you can serve a schema description for your API. - -### Generating a schema with the `generateschema` management command - -To generate a static API schema, use the `generateschema` management command. - -```shell -$ python manage.py generateschema > schema.yml -``` - -Once you've generated a schema in this way you can annotate it with any -additional information that cannot be automatically inferred by the schema -generator. - -You might want to check your API schema into version control and update it -with each new release, or serve the API schema from your site's static media. - -### Adding a view with `get_schema_view` - -To add a dynamically generated schema view to your API, use `get_schema_view`. - -```python -from rest_framework.schemas import get_schema_view -from django.urls import path - -schema_view = get_schema_view(title="Example API") - -urlpatterns = [ - path('schema', schema_view), - ... -] -``` - -See below [for more details](#the-get_schema_view-shortcut) on customizing a -dynamically generated schema view. - -## Internal schema representation - -REST framework uses [Core API][coreapi] in order to model schema information in -a format-independent representation. This information can then be rendered -into various different schema formats, or used to generate API documentation. - -When using Core API, a schema is represented as a `Document` which is the -top-level container object for information about the API. Available API -interactions are represented using `Link` objects. Each link includes a URL, -HTTP method, and may include a list of `Field` instances, which describe any -parameters that may be accepted by the API endpoint. The `Link` and `Field` -instances may also include descriptions, that allow an API schema to be -rendered into user documentation. - -Here's an example of an API description that includes a single `search` -endpoint: - - coreapi.Document( - title='Flight Search API', - url='https://api.example.org/', - content={ - 'search': coreapi.Link( - url='/search/', - action='get', - fields=[ - coreapi.Field( - name='from', - required=True, - location='query', - description='City name or airport code.' - ), - coreapi.Field( - name='to', - required=True, - location='query', - description='City name or airport code.' - ), - coreapi.Field( - name='date', - required=True, - location='query', - description='Flight date in "YYYY-MM-DD" format.' - ) - ], - description='Return flight availability and prices.' - ) - } - ) - -## Schema output formats - -In order to be presented in an HTTP response, the internal representation -has to be rendered into the actual bytes that are used in the response. - -REST framework includes a few different renderers that you can use for -encoding the API schema. - -* `renderers.OpenAPIRenderer` - Renders into YAML-based [OpenAPI][open-api], the most widely used API schema format. -* `renderers.JSONOpenAPIRenderer` - Renders into JSON-based [OpenAPI][open-api]. -* `renderers.CoreJSONRenderer` - Renders into [Core JSON][corejson], a format designed for -use with the `coreapi` client library. - - -[Core JSON][corejson] is designed as a canonical format for use with Core API. -REST framework includes a renderer class for handling this media type, which -is available as `renderers.CoreJSONRenderer`. - - -## Schemas vs Hypermedia - -It's worth pointing out here that Core API can also be used to model hypermedia -responses, which present an alternative interaction style to API schemas. - -With an API schema, the entire available interface is presented up-front -as a single endpoint. Responses to individual API endpoints are then typically -presented as plain data, without any further interactions contained in each -response. - -With Hypermedia, the client is instead presented with a document containing -both data and available interactions. Each interaction results in a new -document, detailing both the current state and the available interactions. - -Further information and support on building Hypermedia APIs with REST framework -is planned for a future version. - - ---- - -# Creating a schema - -REST framework includes functionality for auto-generating a schema, -or allows you to specify one explicitly. - -## Manual Schema Specification - -To manually specify a schema you create a Core API `Document`, similar to the -example above. - - schema = coreapi.Document( - title='Flight Search API', - content={ - ... - } - ) - - -## Automatic Schema Generation - -Automatic schema generation is provided by the `SchemaGenerator` class. - -`SchemaGenerator` processes a list of routed URL patterns and compiles the -appropriately structured Core API Document. - -Basic usage is just to provide the title for your schema and call -`get_schema()`: - - generator = schemas.SchemaGenerator(title='Flight Search API') - schema = generator.get_schema() - -## Per-View Schema Customisation - -By default, view introspection is performed by an `AutoSchema` instance -accessible via the `schema` attribute on `APIView`. This provides the -appropriate Core API `Link` object for the view, request method and path: - - auto_schema = view.schema - coreapi_link = auto_schema.get_link(...) - -(In compiling the schema, `SchemaGenerator` calls `view.schema.get_link()` for -each view, allowed method and path.) - ---- - -**Note**: For basic `APIView` subclasses, default introspection is essentially -limited to the URL kwarg path parameters. For `GenericAPIView` -subclasses, which includes all the provided class based views, `AutoSchema` will -attempt to introspect serializer, pagination and filter fields, as well as -provide richer path field descriptions. (The key hooks here are the relevant -`GenericAPIView` attributes and methods: `get_serializer`, `pagination_class`, -`filter_backends` and so on.) - ---- - -To customise the `Link` generation you may: - -* Instantiate `AutoSchema` on your view with the `manual_fields` kwarg: - - from rest_framework.views import APIView - from rest_framework.schemas import AutoSchema - - class CustomView(APIView): - ... - schema = AutoSchema( - manual_fields=[ - coreapi.Field("extra_field", ...), - ] - ) - - This allows extension for the most common case without subclassing. - -* Provide an `AutoSchema` subclass with more complex customisation: - - from rest_framework.views import APIView - from rest_framework.schemas import AutoSchema - - class CustomSchema(AutoSchema): - def get_link(...): - # Implement custom introspection here (or in other sub-methods) - - class CustomView(APIView): - ... - schema = CustomSchema() - - This provides complete control over view introspection. - -* Instantiate `ManualSchema` on your view, providing the Core API `Fields` for - the view explicitly: - - from rest_framework.views import APIView - from rest_framework.schemas import ManualSchema - - class CustomView(APIView): - ... - schema = ManualSchema(fields=[ - coreapi.Field( - "first_field", - required=True, - location="path", - schema=coreschema.String() - ), - coreapi.Field( - "second_field", - required=True, - location="path", - schema=coreschema.String() - ), - ]) - - This allows manually specifying the schema for some views whilst maintaining - automatic generation elsewhere. - -You may disable schema generation for a view by setting `schema` to `None`: - - class CustomView(APIView): - ... - schema = None # Will not appear in schema - -This also applies to extra actions for `ViewSet`s: - - class CustomViewSet(viewsets.ModelViewSet): - - @action(detail=True, schema=None) - def extra_action(self, request, pk=None): - ... - ---- - -**Note**: For full details on `SchemaGenerator` plus the `AutoSchema` and -`ManualSchema` descriptors see the [API Reference below](#api-reference). - ---- - -# Adding a schema view - -There are a few different ways to add a schema view to your API, depending on -exactly what you need. - -## The get_schema_view shortcut - -The simplest way to include a schema in your project is to use the -`get_schema_view()` function. - - from rest_framework.schemas import get_schema_view - - schema_view = get_schema_view(title="Server Monitoring API") - - urlpatterns = [ - path('', schema_view), - ... - ] - -Once the view has been added, you'll be able to make API requests to retrieve -the auto-generated schema definition. - - $ http http://127.0.0.1:8000/ Accept:application/coreapi+json - HTTP/1.0 200 OK - Allow: GET, HEAD, OPTIONS - Content-Type: application/vnd.coreapi+json - - { - "_meta": { - "title": "Server Monitoring API" - }, - "_type": "document", - ... - } - -The arguments to `get_schema_view()` are: - -#### `title` - -May be used to provide a descriptive title for the schema definition. - -#### `url` - -May be used to pass a canonical URL for the schema. - - schema_view = get_schema_view( - title='Server Monitoring API', - url='https://www.example.org/api/' - ) - -#### `urlconf` - -A string representing the import path to the URL conf that you want -to generate an API schema for. This defaults to the value of Django's -ROOT_URLCONF setting. - - schema_view = get_schema_view( - title='Server Monitoring API', - url='https://www.example.org/api/', - urlconf='myproject.urls' - ) - -#### `renderer_classes` - -May be used to pass the set of renderer classes that can be used to render the API root endpoint. - - from rest_framework.schemas import get_schema_view - from rest_framework.renderers import JSONOpenAPIRenderer - - schema_view = get_schema_view( - title='Server Monitoring API', - url='https://www.example.org/api/', - renderer_classes=[JSONOpenAPIRenderer] - ) - -#### `patterns` - -List of url patterns to limit the schema introspection to. If you only want the `myproject.api` urls -to be exposed in the schema: - - schema_url_patterns = [ - path('api/', include('myproject.api.urls')), - ] - - schema_view = get_schema_view( - title='Server Monitoring API', - url='https://www.example.org/api/', - patterns=schema_url_patterns, - ) - -#### `generator_class` - -May be used to specify a `SchemaGenerator` subclass to be passed to the -`SchemaView`. - -#### `authentication_classes` - -May be used to specify the list of authentication classes that will apply to the schema endpoint. -Defaults to `settings.DEFAULT_AUTHENTICATION_CLASSES` - -#### `permission_classes` - -May be used to specify the list of permission classes that will apply to the schema endpoint. -Defaults to `settings.DEFAULT_PERMISSION_CLASSES` - -## Using an explicit schema view - -If you need a little more control than the `get_schema_view()` shortcut gives you, -then you can use the `SchemaGenerator` class directly to auto-generate the -`Document` instance, and to return that from a view. - -This option gives you the flexibility of setting up the schema endpoint -with whatever behavior you want. For example, you can apply different -permission, throttling, or authentication policies to the schema endpoint. - -Here's an example of using `SchemaGenerator` together with a view to -return the schema. - -**views.py:** - - from rest_framework.decorators import api_view, renderer_classes - from rest_framework import renderers, response, schemas - - generator = schemas.SchemaGenerator(title='Bookings API') - - @api_view() - @renderer_classes([renderers.OpenAPIRenderer]) - def schema_view(request): - schema = generator.get_schema(request) - return response.Response(schema) - -**urls.py:** - - urlpatterns = [ - path('', schema_view), - ... - ] - -You can also serve different schemas to different users, depending on the -permissions they have available. This approach can be used to ensure that -unauthenticated requests are presented with a different schema to -authenticated requests, or to ensure that different parts of the API are -made visible to different users depending on their role. - -In order to present a schema with endpoints filtered by user permissions, -you need to pass the `request` argument to the `get_schema()` method, like so: - - @api_view() - @renderer_classes([renderers.OpenAPIRenderer]) - def schema_view(request): - generator = schemas.SchemaGenerator(title='Bookings API') - return response.Response(generator.get_schema(request=request)) - -## Explicit schema definition - -An alternative to the auto-generated approach is to specify the API schema -explicitly, by declaring a `Document` object in your codebase. Doing so is a -little more work, but ensures that you have full control over the schema -representation. - - import coreapi - from rest_framework.decorators import api_view, renderer_classes - from rest_framework import renderers, response - - schema = coreapi.Document( - title='Bookings API', - content={ - ... - } - ) - - @api_view() - @renderer_classes([renderers.OpenAPIRenderer]) - def schema_view(request): - return response.Response(schema) - ---- - -# Schemas as documentation - -One common usage of API schemas is to use them to build documentation pages. - -The schema generation in REST framework uses docstrings to automatically -populate descriptions in the schema document. - -These descriptions will be based on: - -* The corresponding method docstring if one exists. -* A named section within the class docstring, which can be either single line or multi-line. -* The class docstring. - -## Examples - -An `APIView`, with an explicit method docstring. - - class ListUsernames(APIView): - def get(self, request): - """ - Return a list of all user names in the system. - """ - usernames = [user.username for user in User.objects.all()] - return Response(usernames) - -A `ViewSet`, with an explicit action docstring. - - class ListUsernames(ViewSet): - def list(self, request): - """ - Return a list of all user names in the system. - """ - usernames = [user.username for user in User.objects.all()] - return Response(usernames) - -A generic view with sections in the class docstring, using single-line style. - - class UserList(generics.ListCreateAPIView): - """ - get: List all the users. - post: Create a new user. - """ - queryset = User.objects.all() - serializer_class = UserSerializer - permission_classes = [IsAdminUser] - -A generic viewset with sections in the class docstring, using multi-line style. - - class UserViewSet(viewsets.ModelViewSet): - """ - API endpoint that allows users to be viewed or edited. - - retrieve: - Return a user instance. - - list: - Return all users, ordered by most recently joined. - """ - queryset = User.objects.all().order_by('-date_joined') - serializer_class = UserSerializer - ---- - -# API Reference - -## SchemaGenerator - -A class that walks a list of routed URL patterns, requests the schema for each view, -and collates the resulting CoreAPI Document. - -Typically you'll instantiate `SchemaGenerator` with a single argument, like so: - - generator = SchemaGenerator(title='Stock Prices API') - -Arguments: - -* `title` **required** - The name of the API. -* `url` - The root URL of the API schema. This option is not required unless the schema is included under path prefix. -* `patterns` - A list of URLs to inspect when generating the schema. Defaults to the project's URL conf. -* `urlconf` - A URL conf module name to use when generating the schema. Defaults to `settings.ROOT_URLCONF`. - -### get_schema(self, request) - -Returns a `coreapi.Document` instance that represents the API schema. - - @api_view - @renderer_classes([renderers.OpenAPIRenderer]) - def schema_view(request): - generator = schemas.SchemaGenerator(title='Bookings API') - return Response(generator.get_schema()) - -The `request` argument is optional, and may be used if you want to apply per-user -permissions to the resulting schema generation. - -### get_links(self, request) - -Return a nested dictionary containing all the links that should be included in the API schema. - -This is a good point to override if you want to modify the resulting structure of the generated schema, -as you can build a new dictionary with a different layout. - - -## AutoSchema - -A class that deals with introspection of individual views for schema generation. - -`AutoSchema` is attached to `APIView` via the `schema` attribute. - -The `AutoSchema` constructor takes a single keyword argument `manual_fields`. - -**`manual_fields`**: a `list` of `coreapi.Field` instances that will be added to -the generated fields. Generated fields with a matching `name` will be overwritten. - - class CustomView(APIView): - schema = AutoSchema(manual_fields=[ - coreapi.Field( - "my_extra_field", - required=True, - location="path", - schema=coreschema.String() - ), - ]) - -For more advanced customisation subclass `AutoSchema` to customise schema generation. - - class CustomViewSchema(AutoSchema): - """ - Overrides `get_link()` to provide Custom Behavior X - """ - - def get_link(self, path, method, base_url): - link = super().get_link(path, method, base_url) - # Do something to customize link here... - return link - - class MyView(APIView): - schema = CustomViewSchema() - -The following methods are available to override. - -### get_link(self, path, method, base_url) - -Returns a `coreapi.Link` instance corresponding to the given view. - -This is the main entry point. -You can override this if you need to provide custom behaviors for particular views. - -### get_description(self, path, method) - -Returns a string to use as the link description. By default this is based on the -view docstring as described in the "Schemas as Documentation" section above. - -### get_encoding(self, path, method) - -Returns a string to indicate the encoding for any request body, when interacting -with the given view. Eg. `'application/json'`. May return a blank string for views -that do not expect a request body. - -### get_path_fields(self, path, method): - -Return a list of `coreapi.Field()` instances. One for each path parameter in the URL. - -### get_serializer_fields(self, path, method) - -Return a list of `coreapi.Field()` instances. One for each field in the serializer class used by the view. - -### get_pagination_fields(self, path, method) - -Return a list of `coreapi.Field()` instances, as returned by the `get_schema_fields()` method on any pagination class used by the view. - -### get_filter_fields(self, path, method) - -Return a list of `coreapi.Field()` instances, as returned by the `get_schema_fields()` method of any filter classes used by the view. - -### get_manual_fields(self, path, method) - -Return a list of `coreapi.Field()` instances to be added to or replace generated fields. Defaults to (optional) `manual_fields` passed to `AutoSchema` constructor. - -May be overridden to customise manual fields by `path` or `method`. For example, a per-method adjustment may look like this: - -```python -def get_manual_fields(self, path, method): - """Example adding per-method fields.""" - - extra_fields = [] - if method == 'GET': - extra_fields = ... # list of extra fields for GET - if method == 'POST': - extra_fields = ... # list of extra fields for POST - - manual_fields = super().get_manual_fields(path, method) - return manual_fields + extra_fields -``` - -### update_fields(fields, update_with) - -Utility `staticmethod`. Encapsulates logic to add or replace fields from a list -by `Field.name`. May be overridden to adjust replacement criteria. - - -## ManualSchema - -Allows manually providing a list of `coreapi.Field` instances for the schema, -plus an optional description. - - class MyView(APIView): - schema = ManualSchema(fields=[ - coreapi.Field( - "first_field", - required=True, - location="path", - schema=coreschema.String() - ), - coreapi.Field( - "second_field", - required=True, - location="path", - schema=coreschema.String() - ), - ] - ) - -The `ManualSchema` constructor takes two arguments: - -**`fields`**: A list of `coreapi.Field` instances. Required. - -**`description`**: A string description. Optional. - -**`encoding`**: Default `None`. A string encoding, e.g `application/json`. Optional. - ---- - -## Core API - -This documentation gives a brief overview of the components within the `coreapi` -package that are used to represent an API schema. - -Note that these classes are imported from the `coreapi` package, rather than -from the `rest_framework` package. - -### Document - -Represents a container for the API schema. - -#### `title` - -A name for the API. - -#### `url` - -A canonical URL for the API. - -#### `content` - -A dictionary, containing the `Link` objects that the schema contains. - -In order to provide more structure to the schema, the `content` dictionary -may be nested, typically to a second level. For example: - - content={ - "bookings": { - "list": Link(...), - "create": Link(...), - ... - }, - "venues": { - "list": Link(...), - ... - }, - ... - } - -### Link - -Represents an individual API endpoint. - -#### `url` - -The URL of the endpoint. May be a URI template, such as `/users/{username}/`. - -#### `action` - -The HTTP method associated with the endpoint. Note that URLs that support -more than one HTTP method, should correspond to a single `Link` for each. - -#### `fields` - -A list of `Field` instances, describing the available parameters on the input. - -#### `description` - -A short description of the meaning and intended usage of the endpoint. - -### Field - -Represents a single input parameter on a given API endpoint. - -#### `name` - -A descriptive name for the input. - -#### `required` - -A boolean, indicated if the client is required to included a value, or if -the parameter can be omitted. - -#### `location` - -Determines how the information is encoded into the request. Should be one of -the following strings: - -**"path"** - -Included in a templated URI. For example a `url` value of `/products/{product_code}/` could be used together with a `"path"` field, to handle API inputs in a URL path such as `/products/slim-fit-jeans/`. - -These fields will normally correspond with [named arguments in the project URL conf][named-arguments]. - -**"query"** - -Included as a URL query parameter. For example `?search=sale`. Typically for `GET` requests. - -These fields will normally correspond with pagination and filtering controls on a view. - -**"form"** - -Included in the request body, as a single item of a JSON object or HTML form. For example `{"colour": "blue", ...}`. Typically for `POST`, `PUT` and `PATCH` requests. Multiple `"form"` fields may be included on a single link. - -These fields will normally correspond with serializer fields on a view. - -**"body"** - -Included as the complete request body. Typically for `POST`, `PUT` and `PATCH` requests. No more than one `"body"` field may exist on a link. May not be used together with `"form"` fields. - -These fields will normally correspond with views that use `ListSerializer` to validate the request input, or with file upload views. - -#### `encoding` - -**"application/json"** - -JSON encoded request content. Corresponds to views using `JSONParser`. -Valid only if either one or more `location="form"` fields, or a single -`location="body"` field is included on the `Link`. - -**"multipart/form-data"** - -Multipart encoded request content. Corresponds to views using `MultiPartParser`. -Valid only if one or more `location="form"` fields is included on the `Link`. - -**"application/x-www-form-urlencoded"** - -URL encoded request content. Corresponds to views using `FormParser`. Valid -only if one or more `location="form"` fields is included on the `Link`. - -**"application/octet-stream"** - -Binary upload request content. Corresponds to views using `FileUploadParser`. -Valid only if a `location="body"` field is included on the `Link`. - -#### `description` - -A short description of the meaning and intended usage of the input field. - - ---- - -# Third party packages - -## drf-yasg - Yet Another Swagger Generator - -[drf-yasg][drf-yasg] generates [OpenAPI][open-api] documents suitable for code generation - nested schemas, -named models, response bodies, enum/pattern/min/max validators, form parameters, etc. - - -## drf-spectacular - Sane and flexible OpenAPI 3.0 schema generation for Django REST framework - -[drf-spectacular][drf-spectacular] is a [OpenAPI 3][open-api] schema generation tool with explicit focus on extensibility, -customizability and client generation. It's usage patterns are very similar to [drf-yasg][drf-yasg]. - -[cite]: https://blog.heroku.com/archives/2014/1/8/json_schema_for_heroku_platform_api -[coreapi]: https://www.coreapi.org/ -[corejson]: https://www.coreapi.org/specification/encoding/#core-json-encoding -[drf-yasg]: https://github.com/axnsan12/drf-yasg/ -[drf-spectacular]: https://github.com/tfranzel/drf-spectacular/ -[open-api]: https://openapis.org/ -[json-hyperschema]: https://json-schema.org/latest/json-schema-hypermedia.html -[api-blueprint]: https://apiblueprint.org/ -[static-files]: https://docs.djangoproject.com/en/stable/howto/static-files/ -[named-arguments]: https://docs.djangoproject.com/en/stable/topics/http/urls/#named-groups diff --git a/docs/topics/api-clients.md b/docs/topics/api-clients.md deleted file mode 100644 index 3732d3f93d..0000000000 --- a/docs/topics/api-clients.md +++ /dev/null @@ -1,527 +0,0 @@ -# API Clients - -An API client handles the underlying details of how network requests are made -and how responses are decoded. They present the developer with an application -interface to work against, rather than working directly with the network interface. - -The API clients documented here are not restricted to APIs built with Django REST framework. - They can be used with any API that exposes a supported schema format. - -For example, [the Heroku platform API][heroku-api] exposes a schema in the JSON -Hyperschema format. As a result, the Core API command line client and Python -client library can be [used to interact with the Heroku API][heroku-example]. - -## Client-side Core API - -[Core API][core-api] is a document specification that can be used to describe APIs. It can -be used either server-side, as is done with REST framework's [schema generation][schema-generation], -or used client-side, as described here. - -When used client-side, Core API allows for *dynamically driven client libraries* -that can interact with any API that exposes a supported schema or hypermedia -format. - -Using a dynamically driven client has a number of advantages over interacting -with an API by building HTTP requests directly. - -#### More meaningful interaction - -API interactions are presented in a more meaningful way. You're working at -the application interface layer, rather than the network interface layer. - -#### Resilience & evolvability - -The client determines what endpoints are available, what parameters exist -against each particular endpoint, and how HTTP requests are formed. - -This also allows for a degree of API evolvability. URLs can be modified -without breaking existing clients, or more efficient encodings can be used -on-the-wire, with clients transparently upgrading. - -#### Self-descriptive APIs - -A dynamically driven client is able to present documentation on the API to the -end user. This documentation allows the user to discover the available endpoints -and parameters, and better understand the API they are working with. - -Because this documentation is driven by the API schema it will always be fully -up to date with the most recently deployed version of the service. - ---- - -# Command line client - -The command line client allows you to inspect and interact with any API that -exposes a supported schema format. - -## Getting started - -To install the Core API command line client, use `pip`. - -Note that the command-line client is a separate package to the -python client library. Make sure to install `coreapi-cli`. - - $ pip install coreapi-cli - -To start inspecting and interacting with an API the schema must first be loaded -from the network. - - $ coreapi get http://api.example.org/ - - snippets: { - create(code, [title], [linenos], [language], [style]) - destroy(pk) - highlight(pk) - list([page]) - partial_update(pk, [title], [code], [linenos], [language], [style]) - retrieve(pk) - update(pk, code, [title], [linenos], [language], [style]) - } - users: { - list([page]) - retrieve(pk) - } - -This will then load the schema, displaying the resulting `Document`. This -`Document` includes all the available interactions that may be made against the API. - -To interact with the API, use the `action` command. This command requires a list -of keys that are used to index into the link. - - $ coreapi action users list - [ - { - "url": "http://127.0.0.1:8000/users/2/", - "id": 2, - "username": "aziz", - "snippets": [] - }, - ... - ] - -To inspect the underlying HTTP request and response, use the `--debug` flag. - - $ coreapi action users list --debug - > GET /users/ HTTP/1.1 - > Accept: application/vnd.coreapi+json, */* - > Authorization: Basic bWF4Om1heA== - > Host: 127.0.0.1 - > User-Agent: coreapi - < 200 OK - < Allow: GET, HEAD, OPTIONS - < Content-Type: application/json - < Date: Thu, 30 Jun 2016 10:51:46 GMT - < Server: WSGIServer/0.1 Python/2.7.10 - < Vary: Accept, Cookie - < - < [{"url":"http://127.0.0.1/users/2/","id":2,"username":"aziz","snippets":[]},{"url":"http://127.0.0.1/users/3/","id":3,"username":"amy","snippets":["http://127.0.0.1/snippets/3/"]},{"url":"http://127.0.0.1/users/4/","id":4,"username":"max","snippets":["http://127.0.0.1/snippets/4/","http://127.0.0.1/snippets/5/","http://127.0.0.1/snippets/6/","http://127.0.0.1/snippets/7/"]},{"url":"http://127.0.0.1/users/5/","id":5,"username":"jose","snippets":[]},{"url":"http://127.0.0.1/users/6/","id":6,"username":"admin","snippets":["http://127.0.0.1/snippets/1/","http://127.0.0.1/snippets/2/"]}] - - [ - ... - ] - -Some actions may include optional or required parameters. - - $ coreapi action users create --param username=example - -When using `--param`, the type of the input will be determined automatically. - -If you want to be more explicit about the parameter type then use `--data` for -any null, numeric, boolean, list, or object inputs, and use `--string` for string inputs. - - $ coreapi action users edit --string username=tomchristie --data is_admin=true - -## Authentication & headers - -The `credentials` command is used to manage the request `Authentication:` header. -Any credentials added are always linked to a particular domain, so as to ensure -that credentials are not leaked across differing APIs. - -The format for adding a new credential is: - - $ coreapi credentials add - -For instance: - - $ coreapi credentials add api.example.org "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" - -The optional `--auth` flag also allows you to add specific types of authentication, -handling the encoding for you. Currently only `"basic"` is supported as an option here. -For example: - - $ coreapi credentials add api.example.org tomchristie:foobar --auth basic - -You can also add specific request headers, using the `headers` command: - - $ coreapi headers add api.example.org x-api-version 2 - -For more information and a listing of the available subcommands use `coreapi -credentials --help` or `coreapi headers --help`. - -## Codecs - -By default the command line client only includes support for reading Core JSON -schemas, however it includes a plugin system for installing additional codecs. - - $ pip install openapi-codec jsonhyperschema-codec hal-codec - $ coreapi codecs show - Codecs - corejson application/vnd.coreapi+json encoding, decoding - hal application/hal+json encoding, decoding - openapi application/openapi+json encoding, decoding - jsonhyperschema application/schema+json decoding - json application/json data - text text/* data - -## Utilities - -The command line client includes functionality for bookmarking API URLs -under a memorable name. For example, you can add a bookmark for the -existing API, like so... - - $ coreapi bookmarks add accountmanagement - -There is also functionality for navigating forward or backward through the -history of which API URLs have been accessed. - - $ coreapi history show - $ coreapi history back - -For more information and a listing of the available subcommands use -`coreapi bookmarks --help` or `coreapi history --help`. - -## Other commands - -To display the current `Document`: - - $ coreapi show - -To reload the current `Document` from the network: - - $ coreapi reload - -To load a schema file from disk: - - $ coreapi load my-api-schema.json --format corejson - -To dump the current document to console in a given format: - - $ coreapi dump --format openapi - -To remove the current document, along with all currently saved history, -credentials, headers and bookmarks: - - $ coreapi clear - ---- - -# Python client library - -The `coreapi` Python package allows you to programmatically interact with any -API that exposes a supported schema format. - -## Getting started - -You'll need to install the `coreapi` package using `pip` before you can get -started. - - $ pip install coreapi - -In order to start working with an API, we first need a `Client` instance. The -client holds any configuration around which codecs and transports are supported -when interacting with an API, which allows you to provide for more advanced -kinds of behavior. - - import coreapi - client = coreapi.Client() - -Once we have a `Client` instance, we can fetch an API schema from the network. - - schema = client.get('https://api.example.org/') - -The object returned from this call will be a `Document` instance, which is -a representation of the API schema. - -## Authentication - -Typically you'll also want to provide some authentication credentials when -instantiating the client. - -#### Token authentication - -The `TokenAuthentication` class can be used to support REST framework's built-in -`TokenAuthentication`, as well as OAuth and JWT schemes. - - auth = coreapi.auth.TokenAuthentication( - scheme='JWT', - token='' - ) - client = coreapi.Client(auth=auth) - -When using TokenAuthentication you'll probably need to implement a login flow -using the CoreAPI client. - -A suggested pattern for this would be to initially make an unauthenticated client -request to an "obtain token" endpoint - -For example, using the "Django REST framework JWT" package - - client = coreapi.Client() - schema = client.get('https://api.example.org/') - - action = ['api-token-auth', 'create'] - params = {"username": "example", "password": "secret"} - result = client.action(schema, action, params) - - auth = coreapi.auth.TokenAuthentication( - scheme='JWT', - token=result['token'] - ) - client = coreapi.Client(auth=auth) - -#### Basic authentication - -The `BasicAuthentication` class can be used to support HTTP Basic Authentication. - - auth = coreapi.auth.BasicAuthentication( - username='', - password='' - ) - client = coreapi.Client(auth=auth) - -## Interacting with the API - -Now that we have a client and have fetched our schema `Document`, we can now -start to interact with the API: - - users = client.action(schema, ['users', 'list']) - -Some endpoints may include named parameters, which might be either optional or required: - - new_user = client.action(schema, ['users', 'create'], params={"username": "max"}) - -## Codecs - -Codecs are responsible for encoding or decoding Documents. - -The decoding process is used by a client to take a bytestring of an API schema -definition, and returning the Core API `Document` that represents that interface. - -A codec should be associated with a particular media type, such as `'application/coreapi+json'`. - -This media type is used by the server in the response `Content-Type` header, -in order to indicate what kind of data is being returned in the response. - -#### Configuring codecs - -The codecs that are available can be configured when instantiating a client. -The keyword argument used here is `decoders`, because in the context of a -client the codecs are only for *decoding* responses. - -In the following example we'll configure a client to only accept `Core JSON` -and `JSON` responses. This will allow us to receive and decode a Core JSON schema, -and subsequently to receive JSON responses made against the API. - - from coreapi import codecs, Client - - decoders = [codecs.CoreJSONCodec(), codecs.JSONCodec()] - client = Client(decoders=decoders) - -#### Loading and saving schemas - -You can use a codec directly, in order to load an existing schema definition, -and return the resulting `Document`. - - input_file = open('my-api-schema.json', 'rb') - schema_definition = input_file.read() - codec = codecs.CoreJSONCodec() - schema = codec.load(schema_definition) - -You can also use a codec directly to generate a schema definition given a `Document` instance: - - schema_definition = codec.dump(schema) - output_file = open('my-api-schema.json', 'rb') - output_file.write(schema_definition) - -## Transports - -Transports are responsible for making network requests. The set of transports -that a client has installed determines which network protocols it is able to -support. - -Currently the `coreapi` library only includes an HTTP/HTTPS transport, but -other protocols can also be supported. - -#### Configuring transports - -The behavior of the network layer can be customized by configuring the -transports that the client is instantiated with. - - import requests - from coreapi import transports, Client - - credentials = {'api.example.org': 'Token 3bd44a009d16ff'} - transports = transports.HTTPTransport(credentials=credentials) - client = Client(transports=transports) - -More complex customizations can also be achieved, for example modifying the -underlying `requests.Session` instance to [attach transport adaptors][transport-adaptors] -that modify the outgoing requests. - ---- - -# JavaScript Client Library - -The JavaScript client library allows you to interact with your API either from a browser, or using node. - -## Installing the JavaScript client - -There are two separate JavaScript resources that you need to include in your HTML pages in order to use the JavaScript client library. These are a static `coreapi.js` file, which contains the code for the dynamic client library, and a templated `schema.js` resource, which exposes your API schema. - -First, install the API documentation views. These will include the schema resource that'll allow you to load the schema directly from an HTML page, without having to make an asynchronous AJAX call. - - from rest_framework.documentation import include_docs_urls - - urlpatterns = [ - ... - path('docs/', include_docs_urls(title='My API service'), name='api-docs'), - ] - -Once the API documentation URLs are installed, you'll be able to include both the required JavaScript resources. Note that the ordering of these two lines is important, as the schema loading requires CoreAPI to already be installed. - - - {% load static %} - - - -The `coreapi` library, and the `schema` object will now both be available on the `window` instance. - - const coreapi = window.coreapi; - const schema = window.schema; - -## Instantiating a client - -In order to interact with the API you'll need a client instance. - - var client = new coreapi.Client(); - -Typically you'll also want to provide some authentication credentials when -instantiating the client. - -#### Session authentication - -The `SessionAuthentication` class allows session cookies to provide the user -authentication. You'll want to provide a standard HTML login flow, to allow -the user to login, and then instantiate a client using session authentication: - - let auth = new coreapi.auth.SessionAuthentication({ - csrfCookieName: 'csrftoken', - csrfHeaderName: 'X-CSRFToken', - }); - let client = new coreapi.Client({auth: auth}); - -The authentication scheme will handle including a CSRF header in any outgoing -requests for unsafe HTTP methods. - -#### Token authentication - -The `TokenAuthentication` class can be used to support REST framework's built-in -`TokenAuthentication`, as well as OAuth and JWT schemes. - - let auth = new coreapi.auth.TokenAuthentication({ - scheme: 'JWT', - token: '', - }); - let client = new coreapi.Client({auth: auth}); - -When using TokenAuthentication you'll probably need to implement a login flow -using the CoreAPI client. - -A suggested pattern for this would be to initially make an unauthenticated client -request to an "obtain token" endpoint - -For example, using the "Django REST framework JWT" package - - // Setup some globally accessible state - window.client = new coreapi.Client(); - window.loggedIn = false; - - function loginUser(username, password) { - let action = ["api-token-auth", "obtain-token"]; - let params = {username: username, password: password}; - client.action(schema, action, params).then(function(result) { - // On success, instantiate an authenticated client. - let auth = window.coreapi.auth.TokenAuthentication({ - scheme: 'JWT', - token: result['token'], - }) - window.client = coreapi.Client({auth: auth}); - window.loggedIn = true; - }).catch(function (error) { - // Handle error case where eg. user provides incorrect credentials. - }) - } - -#### Basic authentication - -The `BasicAuthentication` class can be used to support HTTP Basic Authentication. - - let auth = new coreapi.auth.BasicAuthentication({ - username: '', - password: '', - }) - let client = new coreapi.Client({auth: auth}); - -## Using the client - -Making requests: - - let action = ["users", "list"]; - client.action(schema, action).then(function(result) { - // Return value is in 'result' - }) - -Including parameters: - - let action = ["users", "create"]; - let params = {username: "example", email: "example@example.com"}; - client.action(schema, action, params).then(function(result) { - // Return value is in 'result' - }) - -Handling errors: - - client.action(schema, action, params).then(function(result) { - // Return value is in 'result' - }).catch(function (error) { - // Error value is in 'error' - }) - -## Installation with node - -The coreapi package is available on NPM. - - $ npm install coreapi - $ node - const coreapi = require('coreapi') - -You'll either want to include the API schema in your codebase directly, by copying it from the `schema.js` resource, or else load the schema asynchronously. For example: - - let client = new coreapi.Client(); - let schema = null; - client.get("https://api.example.org/").then(function(data) { - // Load a CoreJSON API schema. - schema = data; - console.log('schema loaded'); - }) - -[heroku-api]: https://devcenter.heroku.com/categories/platform-api -[heroku-example]: https://www.coreapi.org/tools-and-resources/example-services/#heroku-json-hyper-schema -[core-api]: https://www.coreapi.org/ -[schema-generation]: ../api-guide/schemas.md -[transport-adaptors]: http://docs.python-requests.org/en/master/user/advanced/#transport-adapters diff --git a/mkdocs.yml b/mkdocs.yml index c58e083183..dcef68987b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -53,7 +53,6 @@ nav: - 'Settings': 'api-guide/settings.md' - Topics: - 'Documenting your API': 'topics/documenting-your-api.md' - - 'API Clients': 'topics/api-clients.md' - 'Internationalization': 'topics/internationalization.md' - 'AJAX, CSRF & CORS': 'topics/ajax-csrf-cors.md' - 'HTML & Forms': 'topics/html-and-forms.md' From 6ec6ddea9b845f65b8677fd7b514b2a8a98de7b5 Mon Sep 17 00:00:00 2001 From: Luka Jeran Date: Tue, 29 Nov 2022 17:10:32 +0100 Subject: [PATCH 048/178] Avoid inline script execution for injecting CSRF token (#7016) Scripts with type="application/json" or "text/plain" are not executed, so we can use them to inject dynamic CSRF data, without allowing inline-script execution in Content-Security-Policy. --- rest_framework/static/rest_framework/js/csrf.js | 1 + rest_framework/templates/rest_framework/admin.html | 10 +++++----- rest_framework/templates/rest_framework/base.html | 10 +++++----- tests/test_templates.py | 4 ++-- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/rest_framework/static/rest_framework/js/csrf.js b/rest_framework/static/rest_framework/js/csrf.js index 6e4bf39a79..0d8444f279 100644 --- a/rest_framework/static/rest_framework/js/csrf.js +++ b/rest_framework/static/rest_framework/js/csrf.js @@ -38,6 +38,7 @@ function sameOrigin(url) { !(/^(\/\/|http:|https:).*/.test(url)); } +window.drf = JSON.parse(document.getElementById('drf_csrf').textContent); var csrftoken = window.drf.csrfToken; $.ajaxSetup({ diff --git a/rest_framework/templates/rest_framework/admin.html b/rest_framework/templates/rest_framework/admin.html index 1281220b28..caa114c8c2 100644 --- a/rest_framework/templates/rest_framework/admin.html +++ b/rest_framework/templates/rest_framework/admin.html @@ -244,11 +244,11 @@ {% endif %} {% block script %} - diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index a88e1591c6..82afac1e5b 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -287,11 +287,11 @@

{{ name }}

{% endif %} {% block script %} - diff --git a/tests/test_templates.py b/tests/test_templates.py index 0dba78ea22..5b5bb29d8b 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -6,7 +6,7 @@ def test_base_template_with_context(): context = {'request': True, 'csrf_token': 'TOKEN'} result = render({}, 'rest_framework/base.html', context=context) - assert re.search(r'\bcsrfToken: "TOKEN"', result.content.decode()) + assert re.search(r'"csrfToken": "TOKEN"', result.content.decode()) def test_base_template_with_no_context(): @@ -14,4 +14,4 @@ def test_base_template_with_no_context(): # so it can be easily extended. result = render({}, 'rest_framework/base.html') # note that this response will not include a valid CSRF token - assert re.search(r'\bcsrfToken: ""', result.content.decode()) + assert re.search(r'"csrfToken": ""', result.content.decode()) From 1ae812ea209392ad76cc5d2f35f9f7fb337f63e4 Mon Sep 17 00:00:00 2001 From: juspence <87657842+juspence@users.noreply.github.com> Date: Tue, 29 Nov 2022 20:49:41 +0000 Subject: [PATCH 049/178] Make browsable API compatbile with strong CSP (#8784) Co-authored-by: Alexander Bliskovsky --- rest_framework/static/rest_framework/js/load-ajax-form.js | 3 +++ rest_framework/templates/rest_framework/admin.html | 6 +----- rest_framework/templates/rest_framework/base.html | 6 +----- 3 files changed, 5 insertions(+), 10 deletions(-) create mode 100644 rest_framework/static/rest_framework/js/load-ajax-form.js diff --git a/rest_framework/static/rest_framework/js/load-ajax-form.js b/rest_framework/static/rest_framework/js/load-ajax-form.js new file mode 100644 index 0000000000..09daf0888a --- /dev/null +++ b/rest_framework/static/rest_framework/js/load-ajax-form.js @@ -0,0 +1,3 @@ +$(document).ready(function() { + $('form').ajaxForm(); +}); diff --git a/rest_framework/templates/rest_framework/admin.html b/rest_framework/templates/rest_framework/admin.html index caa114c8c2..50833af28a 100644 --- a/rest_framework/templates/rest_framework/admin.html +++ b/rest_framework/templates/rest_framework/admin.html @@ -256,11 +256,7 @@ - + {% endblock %} {% endblock %} diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 82afac1e5b..46666281bd 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -299,11 +299,7 @@

{{ name }}

- + {% endblock %} From 614bd87b601c84f7687c94de616daacad8a3734a Mon Sep 17 00:00:00 2001 From: Floris Hoogenboom Date: Thu, 1 Dec 2022 16:13:14 +0000 Subject: [PATCH 050/178] Fix bug in validators documentation (#8779) Function validators seem to have to return their value (which will become part of `validated_data`) as part of their signature. --- docs/api-guide/validators.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index bb8466a2c8..d48d1df7b3 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -271,6 +271,7 @@ A validator may be any callable that raises a `serializers.ValidationError` on f def even_number(value): if value % 2 != 0: raise serializers.ValidationError('This field must be an even number.') + return value #### Field-level validation From a02bb79d87a17c5382bfbebebc99c1faaa765d77 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 2 Dec 2022 05:40:15 +0000 Subject: [PATCH 051/178] Update admin.py (#8534) Use `autocomplete_fields` to keep the select user field manageable. --- rest_framework/authtoken/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_framework/authtoken/admin.py b/rest_framework/authtoken/admin.py index b359e4cfe8..e41eb00023 100644 --- a/rest_framework/authtoken/admin.py +++ b/rest_framework/authtoken/admin.py @@ -25,6 +25,7 @@ class TokenAdmin(admin.ModelAdmin): fields = ('user',) ordering = ('-created',) actions = None # Actions not compatible with mapped IDs. + autocomplete_fields = ("user",) def get_changelist(self, request, **kwargs): return TokenChangeList From cc3c89a11c7ee9cf7cfd732e0a329c318ace71b2 Mon Sep 17 00:00:00 2001 From: juspence <87657842+juspence@users.noreply.github.com> Date: Fri, 2 Dec 2022 05:47:04 +0000 Subject: [PATCH 052/178] Remove a bit of inline CSS. Add CSP nonce where it might be required and is available. (#8783) Co-authored-by: Craig Anderson --- .../static/rest_framework/css/bootstrap-tweaks.css | 4 ++++ rest_framework/templates/rest_framework/base.html | 6 +++--- .../templates/rest_framework/pagination/numbers.html | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css index c2fcb303d9..5c033be376 100644 --- a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css +++ b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css @@ -231,3 +231,7 @@ body a:hover { margin-left: 5px; margin-right: 5px; } + +.pagination { + margin: 5px 0 10px 0; +} diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 46666281bd..533db13780 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -158,19 +158,19 @@

Are you sure you want to delete this {{ name }}?

-
+
{% block description %} {{ description }} {% endblock %}
{% if paginator %} -