From bda84372d445471ce4291547edf36fe683143b42 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Sat, 25 Apr 2020 09:36:10 +0200 Subject: [PATCH 01/13] Fix viewsets action urls with namespaces (#7287) Use the current request's namespace to resolve action urls. --- rest_framework/viewsets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index cad032dd9..9cb48729e 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -150,6 +150,11 @@ class ViewSetMixin: Reverse the action for the given `url_name`. """ url_name = '%s-%s' % (self.basename, url_name) + namespace = None + if self.request and self.request.resolver_match: + namespace = self.request.resolver_match.namespace + if namespace: + url_name = namespace + ':' + url_name kwargs.setdefault('request', self.request) return reverse(url_name, *args, **kwargs) From b677b7b15dc85c3e2ba3f4407d7985f54a11eaad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rignon=20No=C3=ABl?= Date: Thu, 11 Jun 2020 13:33:04 -0400 Subject: [PATCH 02/13] Update link to dry-rest-permissions (#7374) dry-rest-permissions was no maintain since 2018, so FJNR-inc just got a new PyPi version on their fork --- docs/api-guide/permissions.md | 2 +- docs/community/third-party-packages.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index ac2924a83..d035243d5 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -322,7 +322,7 @@ The [Django Rest Framework Role Filters][django-rest-framework-role-filters] pac [filtering]: filtering.md [composed-permissions]: https://github.com/niwibe/djangorestframework-composed-permissions [rest-condition]: https://github.com/caxap/rest_condition -[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions +[dry-rest-permissions]: https://github.com/FJNR-inc/dry-rest-permissions [django-rest-framework-roles]: https://github.com/computer-lab/django-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 diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 2033d97ab..b47dc098f 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -327,7 +327,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [django-versatileimagefield-drf-docs]:https://django-versatileimagefield.readthedocs.io/en/latest/drf_integration.html [drf-tracking]: https://github.com/aschn/drf-tracking [django-rest-framework-braces]: https://github.com/dealertrack/django-rest-framework-braces -[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions +[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 From e2bd3b6a57c32b187671625544d002eae018e0df Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 15 Jun 2020 12:43:09 +0200 Subject: [PATCH 03/13] Adjusted token admin to map to user ID. (#7341) Closes #6131. * Adds a proxy model for Token that uses the user.pk, rather than it's own. * Adjusts Admin to map back from User ID to token instance. --- rest_framework/authtoken/admin.py | 45 ++++++++++++++++++++++++++++-- rest_framework/authtoken/models.py | 13 +++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/rest_framework/authtoken/admin.py b/rest_framework/authtoken/admin.py index f0cf646f4..b359e4cfe 100644 --- a/rest_framework/authtoken/admin.py +++ b/rest_framework/authtoken/admin.py @@ -1,10 +1,51 @@ from django.contrib import admin +from django.contrib.admin.utils import quote +from django.contrib.admin.views.main import ChangeList +from django.contrib.auth import get_user_model +from django.core.exceptions import ValidationError +from django.urls import reverse -from rest_framework.authtoken.models import Token +from rest_framework.authtoken.models import Token, TokenProxy + +User = get_user_model() + + +class TokenChangeList(ChangeList): + """Map to matching User id""" + def url_for_result(self, result): + pk = result.user.pk + return reverse('admin:%s_%s_change' % (self.opts.app_label, + self.opts.model_name), + args=(quote(pk),), + current_app=self.model_admin.admin_site.name) -@admin.register(Token) class TokenAdmin(admin.ModelAdmin): list_display = ('key', 'user', 'created') fields = ('user',) ordering = ('-created',) + actions = None # Actions not compatible with mapped IDs. + + def get_changelist(self, request, **kwargs): + return TokenChangeList + + def get_object(self, request, object_id, from_field=None): + """ + Map from User ID to matching Token. + """ + queryset = self.get_queryset(request) + field = User._meta.pk + try: + object_id = field.to_python(object_id) + user = User.objects.get(**{field.name: object_id}) + return queryset.get(user=user) + except (queryset.model.DoesNotExist, User.DoesNotExist, ValidationError, ValueError): + return None + + def delete_model(self, request, obj): + # Map back to actual Token, since delete() uses pk. + token = Token.objects.get(key=obj.key) + return super().delete_model(request, token) + + +admin.site.register(TokenProxy, TokenAdmin) diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index bff42d3de..f8a871bf1 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -37,3 +37,16 @@ class Token(models.Model): def __str__(self): return self.key + + +class TokenProxy(Token): + """ + Proxy mapping pk to user pk for use in admin. + """ + @property + def pk(self): + return self.user.pk + + class Meta: + proxy = True + verbose_name = "token" From e18e40d6ae42457f60ca9c68054ad40d15ba8433 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 16 Jun 2020 13:33:48 +0200 Subject: [PATCH 04/13] Updated Schema docs. (#7268) --- docs/api-guide/schemas.md | 461 ++++++++++++++++++++------------------ 1 file changed, 243 insertions(+), 218 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 3dc3f5628..402315ef9 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -16,6 +16,25 @@ can interact with your API. Django REST Framework provides support for automatic generation of [OpenAPI][openapi] schemas. +## Overview + +Schema generation has several moving parts. It's worth having an overview: + +* `SchemaGenerator` is a top-level class that is responsible for walking your + configured URL patterns, finding `APIView` subclasses, enquiring for their + schema representation, and compiling the final schema object. +* `AutoSchema` encapsulates all the details necessary for per-view schema + introspection. Is attached to each view via the `schema` attribute. You + subclass `AutoSchema` in order to customize your schema. +* The `generateschema` management command allows you to generate a static schema + offline. +* Alternatively, you can route `SchemaView` to dynamically generate and serve + your schema. +* `settings.DEFAULT_SCHEMA_CLASS` allows you to specify an `AutoSchema` + subclass to serve as your project's default. + +The following sections explain more. + ## Generating an OpenAPI Schema ### Install dependencies @@ -115,23 +134,20 @@ The `get_schema_view()` helper takes the following keyword arguments: * `renderer_classes`: May be used to pass the set of renderer classes that can be used to render the API root endpoint. -## Customizing Schema Generation -You may customize schema generation at the level of the schema as a whole, or -on a per-view basis. +## SchemaGenerator -### Schema Level Customization +**Schema-level customization** -In order to customize the top-level schema subclass -`rest_framework.schemas.openapi.SchemaGenerator` and provide it as an argument -to the `generateschema` command or `get_schema_view()` helper function. +```python +from rest_framework.schemas.openapi import SchemaGenerator +``` -#### SchemaGenerator +`SchemaGenerator` is a class that walks a list of routed URL patterns, requests +the schema for each view and collates the resulting OpenAPI schema. -A class that walks a list of routed URL patterns, requests the schema for each -view and collates the resulting OpenAPI schema. - -Typically you'll instantiate `SchemaGenerator` with a `title` argument, like so: +Typically you won't need to instantiate `SchemaGenerator` yourself, but you can +do so like so: generator = SchemaGenerator(title='Stock Prices API') @@ -144,7 +160,12 @@ Arguments: * `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) +In order to customize the top-level schema, subclass +`rest_framework.schemas.openapi.SchemaGenerator` and provide your subclass +as an argument to the `generateschema` command or `get_schema_view()` helper +function. + +### get_schema(self, request) Returns a dictionary that represents the OpenAPI schema: @@ -155,234 +176,237 @@ The `request` argument is optional, and may be used if you want to apply per-user permissions to the resulting schema generation. This is a good point to override if you want to customize the generated -dictionary, for example to add custom -[specification extensions][openapi-specification-extensions]. +dictionary For example you might wish to add terms of service to the [top-level +`info` object][info-object]: -### Per-View Customization +``` +class TOSSchemaGenerator(SchemaGenerator): + def get_schema(self): + schema = super().get_schema() + schema["info"]["termsOfService"] = "https://example.com/tos.html" + return schema +``` + +## AutoSchema + +**Per-View Customization** + +```python +from rest_framework.schemas.openapi import AutoSchema +``` By default, view introspection is performed by an `AutoSchema` instance -accessible via the `schema` attribute on `APIView`. This provides the -appropriate [Open API operation object][openapi-operation] for the view, -request method and path: +accessible via the `schema` attribute on `APIView`. - auto_schema = view.schema - operation = auto_schema.get_operation(...) + auto_schema = some_view.schema -In compiling the schema, `SchemaGenerator` calls `view.schema.get_operation()` -for each view, allowed method, and path. +`AutoSchema` provides the OpenAPI elements needed for each view, request 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.) - ---- - -In order to customize the operation generation, you should provide an `AutoSchema` subclass, overriding `get_operation()` as you need: - - from rest_framework.views import APIView - from rest_framework.schemas.openapi import AutoSchema - - class CustomSchema(AutoSchema): - def get_operation(...): - # Implement custom introspection here (or in other sub-methods) - - class CustomView(APIView): - """APIView subclass with custom schema introspection.""" - schema = CustomSchema() - -This provides complete control over view introspection. - -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): - ... - -If you wish to provide a base `AutoSchema` subclass to be used throughout your -project you may adjust `settings.DEFAULT_SCHEMA_CLASS` appropriately. - - -### Grouping Operations With Tags - -Tags can be used to group logical operations. Each tag name in the list MUST be unique. - ---- -#### Django REST Framework generates tags automatically with the following logic: - -Tag name will be first element from the path. Also, any `_` in path name will be replaced by a `-`. -Consider below examples. - -Example 1: Consider a user management system. The following table will illustrate the tag generation logic. -Here first element from the paths is: `users`. Hence tag wil be `users` - -Http Method | Path | Tags --------------------------------------|-------------------|------------- -PUT, PATCH, GET(Retrieve), DELETE | /users/{id}/ | ['users'] -POST, GET(List) | /users/ | ['users'] - -Example 2: Consider a restaurant management system. The System has restaurants. Each restaurant has branches. -Consider REST APIs to deal with a branch of a particular restaurant. -Here first element from the paths is: `restaurants`. Hence tag wil be `restaurants`. - -Http Method | Path | Tags --------------------------------------|----------------------------------------------------|------------------- -PUT, PATCH, GET(Retrieve), DELETE: | /restaurants/{restaurant_id}/branches/{branch_id} | ['restaurants'] -POST, GET(List): | /restaurants/{restaurant_id}/branches/ | ['restaurants'] - -Example 3: Consider Order items for an e commerce company. - -Http Method | Path | Tags --------------------------------------|-------------------------|------------- -PUT, PATCH, GET(Retrieve), DELETE | /order_items/{id}/ | ['order-items'] -POST, GET(List) | /order_items/ | ['order-items'] - - ---- -#### Overriding auto generated tags: -You can override auto-generated tags by passing `tags` argument to the constructor of `AutoSchema`. `tags` argument must be a list or tuple of string. -```python -from rest_framework.schemas.openapi import AutoSchema -from rest_framework.views import APIView - -class MyView(APIView): - schema = AutoSchema(tags=['tag1', 'tag2']) - ... -``` - -If you need more customization, you can override the `get_tags` method of `AutoSchema` class. Consider the following example: +* A list of [OpenAPI components][openapi-components]. In DRF terms these are + mappings of serializers that describe request and response bodies. +* The appropriate [OpenAPI operation object][openapi-operation] that describes + the endpoint, including path and query parameters for pagination, filtering, + and so on. ```python -from rest_framework.schemas.openapi import AutoSchema -from rest_framework.views import APIView - -class MySchema(AutoSchema): - ... - def get_tags(self, path, method): - if method == 'POST': - tags = ['tag1', 'tag2'] - elif method == 'GET': - tags = ['tag2', 'tag3'] - elif path == '/example/path/': - tags = ['tag3', 'tag4'] - else: - tags = ['tag5', 'tag6', 'tag7'] - - return tags - -class MyView(APIView): - schema = MySchema() - ... +components = auto_schema.get_components(...) +operation = auto_schema.get_operation(...) ``` -### OperationId +In compiling the schema, `SchemaGenerator` calls `get_components()` and +`get_operation()` for each view, allowed method, and path. -The schema generator generates an [operationid][openapi-operationid] for each operation. This `operationId` is deduced from the model name, serializer name or view name. The operationId may looks like "listItems", "retrieveItem", "updateItem", etc.. -The `operationId` is camelCase by convention. +---- -If you have several views with the same model, the generator may generate duplicate operationId. -In order to work around this, you can override the second part of the operationId: operation name. +**Note**: The automatic introspection of components, and many operation +parameters relies on the relevant attributes and methods of +`GenericAPIView`: `get_serializer()`, `pagination_class`, `filter_backends`, +etc. For basic `APIView` subclasses, default introspection is essentially limited to +the URL kwarg path parameters for this reason. -```python -from rest_framework.schemas.openapi import AutoSchema +---- -class ExampleView(APIView): - """APIView subclass with custom schema introspection.""" - schema = AutoSchema(operation_id_base="Custom") -``` +`AutoSchema` encapsulates the view introspection needed for schema generation. +Because of this all the schema generation logic is kept in a single place, +rather than being spread around the already extensive view, serializer and +field APIs. -The previous example will generate the following operationId: "listCustoms", "retrieveCustom", "updateCustom", "partialUpdateCustom", "destroyCustom". -You need to provide the singular form of he operation name. For the list operation, a "s" will be appended at the end of the operation. - -If you need more configuration over the `operationId` field, you can override the `get_operation_id_base` and `get_operation_id` methods from the `AutoSchema` class: +Keeping with this pattern, try not to let schema logic leak into your own +views, serializers, or fields when customizing the schema generation. You might +be tempted to do something like this: ```python class CustomSchema(AutoSchema): - def get_operation_id_base(self, path, method, action): - pass - - def get_operation_id(self, path, method): - pass - -class MyView(APIView): - schema = AutoSchema(component_name="Ulysses") -``` - -### Components - -Since DRF 3.12, Schema uses the [OpenAPI Components][openapi-components]. This method defines components in the schema and [references them][openapi-reference] inside request and response objects. By default, the component's name is deduced from the Serializer's name. - -Using OpenAPI's components provides the following advantages: - -* The schema is more readable and lightweight. -* If you use the schema to generate an SDK (using [openapi-generator][openapi-generator] or [swagger-codegen][swagger-codegen]). The generator can name your SDK's models. - -### Handling component's schema errors - -You may get the following error while generating the schema: -``` -"Serializer" is an invalid class name for schema generation. -Serializer's class name should be unique and explicit. e.g. "ItemSerializer". -``` - -This error occurs when the Serializer name is "Serializer". You should choose a component's name unique across your schema and different than "Serializer". - -You may also get the following warning: -``` -Schema component "ComponentName" has been overriden with a different value. -``` - -This warning occurs when different components have the same name in one schema. Your component name should be unique across your project. This is likely an error that may lead to an invalid schema. - -You have two ways to solve the previous issues: - -* You can rename your serializer with a unique name and another name than "Serializer". -* You can set the `component_name` kwarg parameter of the AutoSchema constructor (see below). -* You can override the `get_component_name` method of the AutoSchema class (see below). - -#### Set a custom component's name for your view - -To override the component's name in your view, you can use the `component_name` parameter of the AutoSchema constructor: - -```python -from rest_framework.schemas.openapi import AutoSchema - -class MyView(APIView): - schema = AutoSchema(component_name="Ulysses") -``` - -#### Override the default implementation - -If you want to have more control and customization about how the schema's components are generated, you can override the `get_component_name` and `get_components` method from the AutoSchema class. - -```python -from rest_framework.schemas.openapi import AutoSchema - -class CustomSchema(AutoSchema): - def get_components(self, path, method): - # Implement your custom implementation - - def get_component_name(self, serializer): - # Implement your custom implementation + """ + AutoSchema subclass using schema_extra_info on the view. + """ + ... + +class CustomView(APIView): + schema = CustomSchema() + schema_extra_info = ... some extra info ... +``` + +Here, the `AutoSchema` subclass goes looking for `schema_extra_info` on the +view. This is _OK_ (it doesn't actually hurt) but it means you'll end up with +your schema logic spread out in a number of different places. + +Instead try to subclass `AutoSchema` such that the `extra_info` doesn't leak +out into the view: + +```python +class BaseSchema(AutoSchema): + """ + AutoSchema subclass that knows how to use extra_info. + """ + ... + +class CustomSchema(BaseSchema): + extra_info = ... some extra info ... class CustomView(APIView): - """APIView subclass with custom schema introspection.""" schema = CustomSchema() ``` +This style is slightly more verbose but maintains the encapsulation of the +schema related code. It's more _cohesive_ in the _parlance_. It'll keep the +rest of your API code more tidy. + +If an option applies to many view classes, rather than creating a specific +subclass per-view, you may find it more convenient to allow specifying the +option as an `__init__()` kwarg to your base `AutoSchema` subclass: + +```python +class CustomSchema(BaseSchema): + def __init__(self, **kwargs): + # store extra_info for later + self.extra_info = kwargs.pop("extra_info") + super().__init__(**kwargs) + +class CustomView(APIView): + schema = CustomSchema( + extra_info=... some extra info ... + ) +``` + +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 +the more commonly needed options do. + +### `AutoSchema` methods + +#### `get_components()` + +Generates the OpenAPI components that describe request and response bodies, +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 +`get_components()` to return multiple pairs if your view uses multiple +serializers. + +#### `get_component_name()` + +Computes the component's name from the serializer. + +You may see warnings if your API has duplicate component names. If so you can override `get_component_name()` or pass the `component_name` `__init__()` kwarg (see below) to provide different names. + +#### `map_serializer()` + +Maps serializers to their OpenAPI representations. + +Most serializers should conform to the standard OpenAPI `object` type, but you may +wish to override `map_serializer()` in order to customize this or other +serializer-level fields. + +#### `map_field()` + +Maps individual serializer fields to their schema representation. The base implementation +will handle the default fields that Django REST Framework provides. + +For `SerializerMethodField` instances, for which the schema is unknown, or custom field subclasses you should override `map_field()` to generate the correct schema: + +```python +class CustomSchema(AutoSchema): + """Extension of ``AutoSchema`` to add support for custom field schemas.""" + + def map_field(self, field): + # Handle SerializerMethodFields or custom fields here... + # ... + return super().map_field(field) +``` + +Authors of third-party packages should aim to provide an `AutoSchema` subclass, +and a mixin, overriding `map_field()` so that users can easily generate schemas +for their custom fields. + +#### `get_tags()` + +OpenAPI groups operations by tags. By default tags taken from the first path +segment of the routed URL. For example, a URL like `/users/{id}/` will generate +the tag `users`. + +You can pass an `__init__()` kwarg to manually specify tags (see below), or +override `get_tags()` to provide custom logic. + +#### `get_operation()` + +Returns the [OpenAPI operation object][openapi-operation] that describes the +endpoint, including path and query parameters for pagination, filtering, and so +on. + +Together with `get_components()`, this is the main entry point to the view +introspection. + +#### `get_operation_id()` + +There must be a unique [operationid](openapi-operationid) for each operation. +By default the `operationId` is deduced from the model name, serializer name or +view name. The operationId looks like "listItems", "retrieveItem", +"updateItem", etc. The `operationId` is camelCase by convention. + +#### `get_operation_id_base()` + +If you have several views with the same model name, you may see duplicate +operationIds. + +In order to work around this, you can override `get_operation_id_base()` to +provide a different base for name part of the ID. + +### `AutoSchema.__init__()` kwargs + +`AutoSchema` provides a number of `__init__()` kwargs that can be used for +common customizations, if the default generated values are not appropriate. + +The available kwargs are: + +* `tags`: Specify a list of tags. +* `component_name`: Specify the component name. +* `operation_id_base`: Specify the resource-name part of operation IDs. + +You pass the kwargs when declaring the `AutoSchema` instance on your view: + +``` +class PetDetailView(generics.RetrieveUpdateDestroyAPIView): + schema = AutoSchema( + tags=['Pets'], + component_name='Pet', + operation_id_base='Pet', + ) + ... +``` + +Assuming a `Pet` model and `PetSerializer` serializer, the kwargs in this +example are probably not needed. Often, though, you'll need to pass the kwargs +if you have multiple view targeting the same model, or have multiple views with +identically named serializers. + +If your views have related customizations that are needed frequently, you can +create a base `AutoSchema` subclass for your project that takes additional +`__init__()` kwargs to save subclassing `AutoSchema` for each view. + [openapi]: https://github.com/OAI/OpenAPI-Specification [openapi-specification-extensions]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification-extensions [openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject @@ -392,3 +416,4 @@ class CustomView(APIView): [openapi-reference]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#referenceObject [openapi-generator]: https://github.com/OpenAPITools/openapi-generator [swagger-codegen]: https://github.com/swagger-api/swagger-codegen +[info-object]: https://swagger.io/specification/#infoObject From 7a04269209d398a79fe5969044fab27470808f47 Mon Sep 17 00:00:00 2001 From: w Date: Sat, 20 Jun 2020 01:29:41 +0800 Subject: [PATCH 05/13] Fixed docs typo (#7382) --- docs/community/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index 0e634aa1e..d6f56317d 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -187,7 +187,7 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10. * Fixed Javascript `e.indexOf` is not a function error [#5982][gh5982] * Fix schemas for extra actions [#5992][gh5992] * Improved get_error_detail to use error_dict/error_list [#5785][gh5785] -* Imprvied URLs in Admin renderer [#5988][gh5988] +* Improved URLs in Admin renderer [#5988][gh5988] * Add "Community" section to docs, minor cleanup [#5993][gh5993] * Moved guardian imports out of compat [#6054][gh6054] * Deprecate the `DjangoObjectPermissionsFilter` class, moved to the `djangorestframework-guardian` package. [#6075][gh6075] From 19915d19170c48961cc4cb97d773c99cba11aff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Thanh=20L=E1=BB=A3i=20=28Leonn=29?= Date: Tue, 23 Jun 2020 04:24:50 +0700 Subject: [PATCH 06/13] Fix docs typo (#7387) --- docs/api-guide/fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index b2bdd50c8..7722a1f27 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -595,7 +595,7 @@ If you want to create a custom field, you'll need to subclass `Field` and then o The `.to_representation()` method is called to convert the initial datatype into a primitive, serializable datatype. -The `to_internal_value()` method is called to restore a primitive datatype into its internal python representation. This method should raise a `serializers.ValidationError` if the data is invalid. +The `.to_internal_value()` method is called to restore a primitive datatype into its internal python representation. This method should raise a `serializers.ValidationError` if the data is invalid. ## Examples From 5ce237e00471d885f05e6d979ec777552809b3b1 Mon Sep 17 00:00:00 2001 From: Dhaval Mehta <20968146+dhaval-mehta@users.noreply.github.com> Date: Sun, 28 Jun 2020 17:58:59 +0530 Subject: [PATCH 07/13] Corrected regex serialization for OpenAPI. (#7389) * replace \Z by \z in regex * fix test cases for Django >= 3.0 * fix isort * Added comment for why `\z`. Co-authored-by: Carlton Gibson --- rest_framework/schemas/openapi.py | 4 +++- tests/schemas/test_openapi.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 9b3082822..9774a94c7 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -554,7 +554,9 @@ class AutoSchema(ViewInspector): if isinstance(v, URLValidator): schema['format'] = 'uri' if isinstance(v, RegexValidator): - schema['pattern'] = v.regex.pattern + # In Python, the token \Z does what \z does in other engines. + # https://stackoverflow.com/questions/53283160 + schema['pattern'] = v.regex.pattern.replace('\\Z', '\\z') elif isinstance(v, MaxLengthValidator): attr_name = 'maxLength' if isinstance(field, serializers.ListField): diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 0e86a7f50..d483f3d45 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -855,6 +855,7 @@ class TestOperationIntrospection(TestCase): assert properties['url']['type'] == 'string' assert properties['url']['nullable'] is True assert properties['url']['default'] == 'http://www.example.com' + assert '\\Z' not in properties['url']['pattern'] assert properties['uuid']['type'] == 'string' assert properties['uuid']['format'] == 'uuid' From 36bd1b30d827ef3daaa929ac35c5c5b6ca38b435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20=C3=87elikarslan?= Date: Mon, 6 Jul 2020 13:56:46 +0300 Subject: [PATCH 08/13] drf-encrypt-content added into third party list in documentation's serializer page (#7398) --- docs/api-guide/serializers.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 4f566ff59..ceec319de 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -1178,6 +1178,11 @@ The [html-json-forms][html-json-forms] package provides an algorithm and seriali The [drf-writable-nested][drf-writable-nested] package provides writable nested model serializer which allows to create/update models with nested related data. +## 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. + + [cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion [relations]: relations.md [model-managers]: https://docs.djangoproject.com/en/stable/topics/db/managers/ @@ -1199,3 +1204,4 @@ The [drf-writable-nested][drf-writable-nested] package provides writable nested [drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions [djangorestframework-queryfields]: https://djangorestframework-queryfields.readthedocs.io/ [drf-writable-nested]: https://github.com/beda-software/drf-writable-nested +[drf-encrypt-content]: https://github.com/oguzhancelikarslan/drf-encrypt-content From d46d5cbaaaa5626f5b72b3bdefbf8438b8ed401d Mon Sep 17 00:00:00 2001 From: Minjae Kim Date: Tue, 7 Jul 2020 17:58:32 +0900 Subject: [PATCH 09/13] Not include charset when charset is None (#7400) --- rest_framework/test.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index ab16c2787..f2581cacc 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -179,9 +179,11 @@ class APIRequestFactory(DjangoRequestFactory): ret = renderer.render(data) # Determine the content-type header from the renderer - content_type = "{}; charset={}".format( - renderer.media_type, renderer.charset - ) + content_type = renderer.media_type + if renderer.charset: + content_type = "{}; charset={}".format( + content_type, renderer.charset + ) # Coerce text to bytes if required. if isinstance(ret, str): From 76232437d458c818dcf0e52502d9d561b8aa7180 Mon Sep 17 00:00:00 2001 From: Anton Agestam Date: Tue, 7 Jul 2020 11:05:36 +0200 Subject: [PATCH 10/13] Allow type checkers to make serializers generic (#7385) --- rest_framework/serializers.py | 4 ++++ tests/test_serializer.py | 8 ++++++++ tests/test_serializer_lists.py | 9 +++++++++ 3 files changed, 21 insertions(+) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index cfb54de13..916f8bec4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -121,6 +121,10 @@ class BaseSerializer(Field): return cls.many_init(*args, **kwargs) return super().__new__(cls, *args, **kwargs) + # Allow type checkers to make serializers generic. + def __class_getitem__(cls, *args, **kwargs): + return cls + @classmethod def many_init(cls, *args, **kwargs): """ diff --git a/tests/test_serializer.py b/tests/test_serializer.py index a58c46b2d..afefd70e1 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1,6 +1,7 @@ import inspect import pickle import re +import sys from collections import ChainMap from collections.abc import Mapping @@ -204,6 +205,13 @@ class TestSerializer: exceptions.ErrorDetail(string='Raised error', code='invalid') ]} + @pytest.mark.skipif( + sys.version_info < (3, 7), + reason="subscriptable classes requires Python 3.7 or higher", + ) + def test_serializer_is_subscriptable(self): + assert serializers.Serializer is serializers.Serializer["foo"] + class TestValidateMethod: def test_non_field_error_validate_method(self): diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index 98e72385a..f35c4fcc9 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -1,3 +1,5 @@ +import sys + import pytest from django.http import QueryDict from django.utils.datastructures import MultiValueDict @@ -55,6 +57,13 @@ class TestListSerializer: assert serializer.is_valid() assert serializer.validated_data == expected_output + @pytest.mark.skipif( + sys.version_info < (3, 7), + reason="subscriptable classes requires Python 3.7 or higher", + ) + def test_list_serializer_is_subscriptable(self): + assert serializers.ListSerializer is serializers.ListSerializer["foo"] + class TestListSerializerContainingNestedSerializer: """ From 4b06e0a5a114b7631bd237e29e2d6d22412a50bd Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sun, 12 Jul 2020 11:08:40 +0200 Subject: [PATCH 11/13] Fix tests crash on SQLite without JSON1 extension. (#7409) --- tests/test_serializer_nested.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index a614e05d1..09ac37f24 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -311,6 +311,9 @@ if postgres_fields: """Model declaring a postgres JSONField""" data = postgres_fields.JSONField() + class Meta: + required_db_features = {'supports_json_field'} + @pytest.mark.skipif(not postgres_fields, reason='psycopg2 is not installed') class TestNestedNonRelationalFieldWrite: From 1e164c5eebfe0284444dd8538ac2a93f76306324 Mon Sep 17 00:00:00 2001 From: Anirudh Bagri Date: Thu, 23 Jul 2020 21:04:34 +0530 Subject: [PATCH 12/13] Just smalling cleaning up, causing confusion (#7426) --- docs/tutorial/quickstart.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 546144670..ee839790f 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -178,7 +178,7 @@ We can now access our API, both from the command-line, using tools like `curl`.. }, { "email": "tom@example.com", - "groups": [ ], + "groups": [], "url": "http://127.0.0.1:8000/users/2/", "username": "tom" } @@ -204,7 +204,7 @@ Or using the [httpie][httpie], command line tool... }, { "email": "tom@example.com", - "groups": [ ], + "groups": [], "url": "http://127.0.0.1:8000/users/2/", "username": "tom" } From 599e2b183db846a632b1efd148e6bc97d926ee5c Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Fri, 24 Jul 2020 00:26:35 +0700 Subject: [PATCH 13/13] urlpatterns: Remove unnecessary branching (#7405) Functions path and register_converter are constants. --- rest_framework/urlpatterns.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index 5b0bb4440..efcfd8401 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -105,12 +105,9 @@ def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None): else: suffix_pattern = r'\.(?P<%s>[a-z0-9]+)/?$' % suffix_kwarg - if path and register_converter: - converter_name, suffix_converter = _get_format_path_converter(suffix_kwarg, allowed) - register_converter(suffix_converter, converter_name) + converter_name, suffix_converter = _get_format_path_converter(suffix_kwarg, allowed) + register_converter(suffix_converter, converter_name) - suffix_route = '<%s:%s>' % (converter_name, suffix_kwarg) - else: - suffix_route = None + suffix_route = '<%s:%s>' % (converter_name, suffix_kwarg) return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_route)