diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9502f476..63865fbc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,7 +26,7 @@ any parts of the framework not mentioned in the documentation should generally b
* Removed support for Python 3.8.
* Removed support for Django REST framework 3.14.
* Removed support for Django 5.0.
-
+* Removed built-in support for generating OpenAPI schema. Use [drf-spectacular-json-api](https://github.com/jokiefer/drf-spectacular-json-api/) instead.
## [7.1.0] - 2024-10-25
diff --git a/README.rst b/README.rst
index c0e95a19..05c01c04 100644
--- a/README.rst
+++ b/README.rst
@@ -114,7 +114,6 @@ Install using ``pip``...
$ # for optional package integrations
$ pip install djangorestframework-jsonapi['django-filter']
$ pip install djangorestframework-jsonapi['django-polymorphic']
- $ pip install djangorestframework-jsonapi['openapi']
or from source...
@@ -156,8 +155,6 @@ installed and activated:
Browse to
* http://localhost:8000 for the list of available collections (in a non-JSON:API format!),
-* http://localhost:8000/swagger-ui/ for a Swagger user interface to the dynamic schema view, or
-* http://localhost:8000/openapi for the schema view's OpenAPI specification document.
-----
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 4052450b..81040e8e 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -69,7 +69,6 @@ Install using `pip`...
# for optional package integrations
pip install djangorestframework-jsonapi['django-filter']
pip install djangorestframework-jsonapi['django-polymorphic']
- pip install djangorestframework-jsonapi['openapi']
or from source...
@@ -100,8 +99,6 @@ and add `rest_framework_json_api` to your `INSTALLED_APPS` setting below `rest_f
Browse to
* [http://localhost:8000](http://localhost:8000) for the list of available collections (in a non-JSON:API format!),
-* [http://localhost:8000/swagger-ui/](http://localhost:8000/swagger-ui/) for a Swagger user interface to the dynamic schema view, or
-* [http://localhost:8000/openapi](http://localhost:8000/openapi) for the schema view's OpenAPI specification document.
## Running Tests
diff --git a/docs/usage.md b/docs/usage.md
index 1a2cb195..00c12ee8 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -1054,139 +1054,6 @@ The `prefetch_related` case will issue 4 queries, but they will be small and fas
### Errors
-->
-## Generating an OpenAPI Specification (OAS) 3.0 schema document
-
-DRF has a [OAS schema functionality](https://www.django-rest-framework.org/api-guide/schemas/) to generate an
-[OAS 3.0 schema](https://www.openapis.org/) as a YAML or JSON file.
-
-DJA extends DRF's schema support to generate an OAS schema in the JSON:API format.
-
----
-
-**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. Therefore we have also deprecated the schema support in
-Django REST framework JSON:API. The built-in support will be retired over the
-next releases.
-
-As a full-fledged replacement, we recommend the [drf-spectacular-json-api] package.
-
----
-
-### AutoSchema Settings
-
-In order to produce an OAS schema that properly represents the JSON:API structure
-you have to either add a `schema` attribute to each view class or set the `REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS']`
-to DJA's version of AutoSchema.
-
-#### View-based
-
-```python
-from rest_framework_json_api.schemas.openapi import AutoSchema
-
-class MyViewset(ModelViewSet):
- schema = AutoSchema
- ...
-```
-
-#### Default schema class
-
-```python
-REST_FRAMEWORK = {
- # ...
- 'DEFAULT_SCHEMA_CLASS': 'rest_framework_json_api.schemas.openapi.AutoSchema',
-}
-```
-
-### Adding additional OAS schema content
-
-You can extend the OAS schema document by subclassing
-[`SchemaGenerator`](https://www.django-rest-framework.org/api-guide/schemas/#schemagenerator)
-and extending `get_schema`.
-
-
-Here's an example that adds OAS `info` and `servers` objects.
-
-```python
-from rest_framework_json_api.schemas.openapi import SchemaGenerator as JSONAPISchemaGenerator
-
-
-class MySchemaGenerator(JSONAPISchemaGenerator):
- """
- Describe my OAS schema info in detail (overriding what DRF put in) and list the servers where it can be found.
- """
- def get_schema(self, request, public):
- schema = super().get_schema(request, public)
- schema['info'] = {
- 'version': '1.0',
- 'title': 'my demo API',
- 'description': 'A demonstration of [OAS 3.0](https://www.openapis.org)',
- 'contact': {
- 'name': 'my name'
- },
- 'license': {
- 'name': 'BSD 2 clause',
- 'url': 'https://github.com/django-json-api/django-rest-framework-json-api/blob/main/LICENSE',
- }
- }
- schema['servers'] = [
- {'url': 'http://localhost/v1', 'description': 'local docker'},
- {'url': 'http://localhost:8000/v1', 'description': 'local dev'},
- {'url': 'https://api.example.com/v1', 'description': 'demo server'},
- {'url': '{serverURL}', 'description': 'provide your server URL',
- 'variables': {'serverURL': {'default': 'http://localhost:8000/v1'}}}
- ]
- return schema
-```
-
-### Generate a Static Schema on Command Line
-
-See [DRF documentation for generateschema](https://www.django-rest-framework.org/api-guide/schemas/#generating-a-static-schema-with-the-generateschema-management-command)
-To generate a static OAS schema document, using the `generateschema` management command, you **must override DRF's default** `generator_class` with the DJA-specific version:
-
-```text
-$ ./manage.py generateschema --generator_class rest_framework_json_api.schemas.openapi.SchemaGenerator
-```
-
-You can then use any number of OAS tools such as
-[swagger-ui-watcher](https://www.npmjs.com/package/swagger-ui-watcher)
-to render the schema:
-```text
-$ swagger-ui-watcher myschema.yaml
-```
-
-Note: Swagger-ui-watcher will complain that "DELETE operations cannot have a requestBody"
-but it will still work. This [error](https://github.com/OAI/OpenAPI-Specification/pull/2117)
-in the OAS specification will be fixed when [OAS 3.1.0](https://www.openapis.org/blog/2020/06/18/openapi-3-1-0-rc0-its-here)
-is published.
-
-([swagger-ui](https://www.npmjs.com/package/swagger-ui) will work silently.)
-
-### Generate a Dynamic Schema in a View
-
-See [DRF documentation for a Dynamic Schema](https://www.django-rest-framework.org/api-guide/schemas/#generating-a-dynamic-schema-with-schemaview).
-
-```python
-from rest_framework.schemas import get_schema_view
-
-urlpatterns = [
- ...
- path('openapi', get_schema_view(
- title="Example API",
- description="API for all things …",
- version="1.0.0",
- generator_class=MySchemaGenerator,
- ), name='openapi-schema'),
- path('swagger-ui/', TemplateView.as_view(
- template_name='swagger-ui.html',
- extra_context={'schema_url': 'openapi-schema'}
- ), name='swagger-ui'),
- ...
-]
-```
-
## Third Party Packages
### About Third Party Packages
diff --git a/example/settings/dev.py b/example/settings/dev.py
index 7b40e61f..05cab4d1 100644
--- a/example/settings/dev.py
+++ b/example/settings/dev.py
@@ -83,7 +83,6 @@
"rest_framework_json_api.renderers.BrowsableAPIRenderer",
),
"DEFAULT_METADATA_CLASS": "rest_framework_json_api.metadata.JSONAPIMetadata",
- "DEFAULT_SCHEMA_CLASS": "rest_framework_json_api.schemas.openapi.AutoSchema",
"DEFAULT_FILTER_BACKENDS": (
"rest_framework_json_api.filters.OrderingFilter",
"rest_framework_json_api.django_filters.DjangoFilterBackend",
diff --git a/example/templates/swagger-ui.html b/example/templates/swagger-ui.html
deleted file mode 100644
index 29776491..00000000
--- a/example/templates/swagger-ui.html
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
- Swagger
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/example/tests/__snapshots__/test_openapi.ambr b/example/tests/__snapshots__/test_openapi.ambr
deleted file mode 100644
index f72c6ff8..00000000
--- a/example/tests/__snapshots__/test_openapi.ambr
+++ /dev/null
@@ -1,1414 +0,0 @@
-# serializer version: 1
-# name: test_delete_request
- '''
- {
- "description": "",
- "operationId": "destroy/authors/{id}",
- "parameters": [
- {
- "description": "A unique integer value identifying this author.",
- "in": "path",
- "name": "id",
- "required": true,
- "schema": {
- "type": "string"
- }
- }
- ],
- "responses": {
- "200": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/onlymeta"
- }
- }
- },
- "description": "[OK](https://jsonapi.org/format/#crud-deleting-responses-200)"
- },
- "202": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/datum"
- }
- }
- },
- "description": "Accepted for [asynchronous processing](https://jsonapi.org/recommendations/#asynchronous-processing)"
- },
- "204": {
- "description": "[no content](https://jsonapi.org/format/#crud-deleting-responses-204)"
- },
- "400": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "bad request"
- },
- "401": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "not authorized"
- },
- "404": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "[Resource does not exist](https://jsonapi.org/format/#crud-deleting-responses-404)"
- },
- "429": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "too many requests"
- }
- },
- "tags": [
- "authors"
- ]
- }
- '''
-# ---
-# name: test_patch_request
- '''
- {
- "description": "",
- "operationId": "update/authors/{id}",
- "parameters": [
- {
- "description": "A unique integer value identifying this author.",
- "in": "path",
- "name": "id",
- "required": true,
- "schema": {
- "type": "string"
- }
- }
- ],
- "requestBody": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "properties": {
- "data": {
- "additionalProperties": false,
- "properties": {
- "attributes": {
- "properties": {
- "defaults": {
- "default": "default",
- "description": "help for defaults",
- "maxLength": 20,
- "minLength": 3,
- "type": "string",
- "writeOnly": true
- },
- "email": {
- "format": "email",
- "maxLength": 254,
- "type": "string"
- },
- "fullName": {
- "maxLength": 50,
- "type": "string"
- },
- "name": {
- "maxLength": 50,
- "type": "string"
- }
- },
- "type": "object"
- },
- "id": {
- "$ref": "#/components/schemas/id"
- },
- "links": {
- "properties": {
- "self": {
- "$ref": "#/components/schemas/link"
- }
- },
- "type": "object"
- },
- "relationships": {
- "properties": {
- "authorType": {
- "$ref": "#/components/schemas/reltoone"
- },
- "bio": {
- "$ref": "#/components/schemas/reltoone"
- },
- "comments": {
- "$ref": "#/components/schemas/reltomany"
- },
- "entries": {
- "$ref": "#/components/schemas/reltomany"
- },
- "firstEntry": {
- "$ref": "#/components/schemas/reltoone"
- }
- },
- "type": "object"
- },
- "type": {
- "$ref": "#/components/schemas/type"
- }
- },
- "required": [
- "type",
- "id"
- ],
- "type": "object"
- }
- },
- "required": [
- "data"
- ]
- }
- }
- }
- },
- "responses": {
- "200": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "properties": {
- "data": {
- "$ref": "#/components/schemas/Author"
- },
- "included": {
- "items": {
- "$ref": "#/components/schemas/include"
- },
- "type": "array",
- "uniqueItems": true
- },
- "jsonapi": {
- "$ref": "#/components/schemas/jsonapi"
- },
- "links": {
- "allOf": [
- {
- "$ref": "#/components/schemas/links"
- },
- {
- "$ref": "#/components/schemas/pagination"
- }
- ],
- "description": "Link members related to primary data"
- }
- },
- "required": [
- "data"
- ],
- "type": "object"
- }
- }
- },
- "description": "update/authors/{id}"
- },
- "400": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "bad request"
- },
- "401": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "not authorized"
- },
- "403": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "[Forbidden](https://jsonapi.org/format/#crud-updating-responses-403)"
- },
- "404": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "[Related resource does not exist](https://jsonapi.org/format/#crud-updating-responses-404)"
- },
- "409": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "[Conflict]([Conflict](https://jsonapi.org/format/#crud-updating-responses-409)"
- },
- "429": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "too many requests"
- }
- },
- "tags": [
- "authors"
- ]
- }
- '''
-# ---
-# name: test_path_with_id_parameter
- '''
- {
- "description": "",
- "operationId": "retrieve/authors/{id}/",
- "parameters": [
- {
- "description": "A unique integer value identifying this author.",
- "in": "path",
- "name": "id",
- "required": true,
- "schema": {
- "type": "string"
- }
- },
- {
- "$ref": "#/components/parameters/include"
- },
- {
- "$ref": "#/components/parameters/fields"
- },
- {
- "description": "[list of fields to sort by](https://jsonapi.org/format/#fetching-sorting)",
- "in": "query",
- "name": "sort",
- "required": false,
- "schema": {
- "type": "string"
- }
- },
- {
- "description": "author_type",
- "in": "query",
- "name": "filter[authorType]",
- "required": false,
- "schema": {
- "type": "string"
- }
- },
- {
- "description": "name",
- "in": "query",
- "name": "filter[name]",
- "required": false,
- "schema": {
- "type": "string"
- }
- },
- {
- "description": "A search term.",
- "in": "query",
- "name": "filter[search]",
- "required": false,
- "schema": {
- "type": "string"
- }
- }
- ],
- "responses": {
- "200": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "properties": {
- "data": {
- "$ref": "#/components/schemas/AuthorDetail"
- },
- "included": {
- "items": {
- "$ref": "#/components/schemas/include"
- },
- "type": "array",
- "uniqueItems": true
- },
- "jsonapi": {
- "$ref": "#/components/schemas/jsonapi"
- },
- "links": {
- "allOf": [
- {
- "$ref": "#/components/schemas/links"
- },
- {
- "$ref": "#/components/schemas/pagination"
- }
- ],
- "description": "Link members related to primary data"
- }
- },
- "required": [
- "data"
- ],
- "type": "object"
- }
- }
- },
- "description": "retrieve/authors/{id}/"
- },
- "400": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "bad request"
- },
- "401": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "not authorized"
- },
- "404": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "not found"
- },
- "429": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "too many requests"
- }
- },
- "tags": [
- "authors"
- ]
- }
- '''
-# ---
-# name: test_path_without_parameters
- '''
- {
- "description": "",
- "operationId": "List/authors/",
- "parameters": [
- {
- "$ref": "#/components/parameters/include"
- },
- {
- "$ref": "#/components/parameters/fields"
- },
- {
- "description": "A page number within the paginated result set.",
- "in": "query",
- "name": "page[number]",
- "required": false,
- "schema": {
- "type": "integer"
- }
- },
- {
- "description": "Number of results to return per page.",
- "in": "query",
- "name": "page[size]",
- "required": false,
- "schema": {
- "type": "integer"
- }
- },
- {
- "description": "[list of fields to sort by](https://jsonapi.org/format/#fetching-sorting)",
- "in": "query",
- "name": "sort",
- "required": false,
- "schema": {
- "type": "string"
- }
- },
- {
- "description": "author_type",
- "in": "query",
- "name": "filter[authorType]",
- "required": false,
- "schema": {
- "type": "string"
- }
- },
- {
- "description": "name",
- "in": "query",
- "name": "filter[name]",
- "required": false,
- "schema": {
- "type": "string"
- }
- },
- {
- "description": "A search term.",
- "in": "query",
- "name": "filter[search]",
- "required": false,
- "schema": {
- "type": "string"
- }
- }
- ],
- "responses": {
- "200": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "properties": {
- "data": {
- "items": {
- "$ref": "#/components/schemas/AuthorList"
- },
- "type": "array"
- },
- "included": {
- "items": {
- "$ref": "#/components/schemas/include"
- },
- "type": "array",
- "uniqueItems": true
- },
- "jsonapi": {
- "$ref": "#/components/schemas/jsonapi"
- },
- "links": {
- "allOf": [
- {
- "$ref": "#/components/schemas/links"
- },
- {
- "$ref": "#/components/schemas/pagination"
- }
- ],
- "description": "Link members related to primary data"
- }
- },
- "required": [
- "data"
- ],
- "type": "object"
- }
- }
- },
- "description": "List/authors/"
- },
- "400": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "bad request"
- },
- "401": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "not authorized"
- },
- "404": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "not found"
- },
- "429": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "too many requests"
- }
- },
- "tags": [
- "authors"
- ]
- }
- '''
-# ---
-# name: test_post_request
- '''
- {
- "description": "",
- "operationId": "create/authors/",
- "parameters": [],
- "requestBody": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "properties": {
- "data": {
- "additionalProperties": false,
- "properties": {
- "attributes": {
- "properties": {
- "defaults": {
- "default": "default",
- "description": "help for defaults",
- "maxLength": 20,
- "minLength": 3,
- "type": "string",
- "writeOnly": true
- },
- "email": {
- "format": "email",
- "maxLength": 254,
- "type": "string"
- },
- "fullName": {
- "maxLength": 50,
- "type": "string"
- },
- "name": {
- "maxLength": 50,
- "type": "string"
- }
- },
- "required": [
- "name",
- "fullName",
- "email"
- ],
- "type": "object"
- },
- "id": {
- "$ref": "#/components/schemas/id"
- },
- "links": {
- "properties": {
- "self": {
- "$ref": "#/components/schemas/link"
- }
- },
- "type": "object"
- },
- "relationships": {
- "properties": {
- "authorType": {
- "$ref": "#/components/schemas/reltoone"
- },
- "bio": {
- "$ref": "#/components/schemas/reltoone"
- },
- "comments": {
- "$ref": "#/components/schemas/reltomany"
- },
- "entries": {
- "$ref": "#/components/schemas/reltomany"
- },
- "firstEntry": {
- "$ref": "#/components/schemas/reltoone"
- }
- },
- "required": [
- "bio",
- "entries",
- "comments"
- ],
- "type": "object"
- },
- "type": {
- "$ref": "#/components/schemas/type"
- }
- },
- "required": [
- "type"
- ],
- "type": "object"
- }
- },
- "required": [
- "data"
- ]
- }
- }
- }
- },
- "responses": {
- "201": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "properties": {
- "data": {
- "$ref": "#/components/schemas/Author"
- },
- "included": {
- "items": {
- "$ref": "#/components/schemas/include"
- },
- "type": "array",
- "uniqueItems": true
- },
- "jsonapi": {
- "$ref": "#/components/schemas/jsonapi"
- },
- "links": {
- "allOf": [
- {
- "$ref": "#/components/schemas/links"
- },
- {
- "$ref": "#/components/schemas/pagination"
- }
- ],
- "description": "Link members related to primary data"
- }
- },
- "required": [
- "data"
- ],
- "type": "object"
- }
- }
- },
- "description": "[Created](https://jsonapi.org/format/#crud-creating-responses-201). Assigned `id` and/or any other changes are in this response."
- },
- "202": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/datum"
- }
- }
- },
- "description": "Accepted for [asynchronous processing](https://jsonapi.org/recommendations/#asynchronous-processing)"
- },
- "204": {
- "description": "[Created](https://jsonapi.org/format/#crud-creating-responses-204) with the supplied `id`. No other changes from what was POSTed."
- },
- "400": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "bad request"
- },
- "401": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "not authorized"
- },
- "403": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "[Forbidden](https://jsonapi.org/format/#crud-creating-responses-403)"
- },
- "404": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "[Related resource does not exist](https://jsonapi.org/format/#crud-creating-responses-404)"
- },
- "409": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "[Conflict](https://jsonapi.org/format/#crud-creating-responses-409)"
- },
- "429": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "too many requests"
- }
- },
- "tags": [
- "authors"
- ]
- }
- '''
-# ---
-# name: test_schema_construction
- '''
- {
- "components": {
- "parameters": {
- "fields": {
- "description": "[sparse fieldsets](https://jsonapi.org/format/#fetching-sparse-fieldsets).\nUse fields[\\]=field1,field2,...,fieldN",
- "explode": true,
- "in": "query",
- "name": "fields",
- "required": false,
- "schema": {
- "type": "object"
- },
- "style": "deepObject"
- },
- "include": {
- "description": "[list of included related resources](https://jsonapi.org/format/#fetching-includes)",
- "in": "query",
- "name": "include",
- "required": false,
- "schema": {
- "type": "string"
- },
- "style": "form"
- }
- },
- "schemas": {
- "AuthorList": {
- "additionalProperties": false,
- "properties": {
- "attributes": {
- "properties": {
- "defaults": {
- "default": "default",
- "description": "help for defaults",
- "maxLength": 20,
- "minLength": 3,
- "type": "string",
- "writeOnly": true
- },
- "email": {
- "format": "email",
- "maxLength": 254,
- "type": "string"
- },
- "fullName": {
- "maxLength": 50,
- "type": "string"
- },
- "initials": {
- "readOnly": true,
- "type": "string"
- },
- "name": {
- "maxLength": 50,
- "type": "string"
- }
- },
- "required": [
- "name",
- "fullName",
- "email"
- ],
- "type": "object"
- },
- "id": {
- "$ref": "#/components/schemas/id"
- },
- "links": {
- "properties": {
- "self": {
- "$ref": "#/components/schemas/link"
- }
- },
- "type": "object"
- },
- "relationships": {
- "properties": {
- "authorType": {
- "$ref": "#/components/schemas/reltoone"
- },
- "bio": {
- "$ref": "#/components/schemas/reltoone"
- },
- "comments": {
- "$ref": "#/components/schemas/reltomany"
- },
- "entries": {
- "$ref": "#/components/schemas/reltomany"
- },
- "firstEntry": {
- "$ref": "#/components/schemas/reltoone"
- }
- },
- "required": [
- "bio",
- "entries",
- "comments"
- ],
- "type": "object"
- },
- "type": {
- "$ref": "#/components/schemas/type"
- }
- },
- "required": [
- "type",
- "id"
- ],
- "type": "object"
- },
- "ResourceIdentifierObject": {
- "oneOf": [
- {
- "$ref": "#/components/schemas/relationshipToOne"
- },
- {
- "$ref": "#/components/schemas/relationshipToMany"
- }
- ]
- },
- "datum": {
- "description": "singular item",
- "properties": {
- "data": {
- "$ref": "#/components/schemas/resource"
- }
- }
- },
- "error": {
- "additionalProperties": false,
- "properties": {
- "code": {
- "type": "string"
- },
- "detail": {
- "type": "string"
- },
- "id": {
- "type": "string"
- },
- "links": {
- "$ref": "#/components/schemas/links"
- },
- "source": {
- "properties": {
- "meta": {
- "$ref": "#/components/schemas/meta"
- },
- "parameter": {
- "description": "A string indicating which query parameter caused the error.",
- "type": "string"
- },
- "pointer": {
- "description": "A [JSON Pointer](https://tools.ietf.org/html/rfc6901) to the associated entity in the request document [e.g. `/data` for a primary data object, or `/data/attributes/title` for a specific attribute.",
- "type": "string"
- }
- },
- "type": "object"
- },
- "status": {
- "type": "string"
- },
- "title": {
- "type": "string"
- }
- },
- "type": "object"
- },
- "errors": {
- "items": {
- "$ref": "#/components/schemas/error"
- },
- "type": "array",
- "uniqueItems": true
- },
- "failure": {
- "properties": {
- "errors": {
- "$ref": "#/components/schemas/errors"
- },
- "jsonapi": {
- "$ref": "#/components/schemas/jsonapi"
- },
- "links": {
- "$ref": "#/components/schemas/links"
- },
- "meta": {
- "$ref": "#/components/schemas/meta"
- }
- },
- "required": [
- "errors"
- ],
- "type": "object"
- },
- "id": {
- "description": "Each resource object\u2019s type and id pair MUST [identify](https://jsonapi.org/format/#document-resource-object-identification) a single, unique resource.",
- "type": "string"
- },
- "include": {
- "additionalProperties": false,
- "properties": {
- "attributes": {
- "additionalProperties": true,
- "type": "object"
- },
- "id": {
- "$ref": "#/components/schemas/id"
- },
- "links": {
- "$ref": "#/components/schemas/links"
- },
- "meta": {
- "$ref": "#/components/schemas/meta"
- },
- "relationships": {
- "additionalProperties": true,
- "type": "object"
- },
- "type": {
- "$ref": "#/components/schemas/type"
- }
- },
- "required": [
- "type",
- "id"
- ],
- "type": "object"
- },
- "jsonapi": {
- "additionalProperties": false,
- "description": "The server's implementation",
- "properties": {
- "meta": {
- "$ref": "#/components/schemas/meta"
- },
- "version": {
- "type": "string"
- }
- },
- "type": "object"
- },
- "link": {
- "oneOf": [
- {
- "description": "a string containing the link's URL",
- "format": "uri-reference",
- "type": "string"
- },
- {
- "properties": {
- "href": {
- "description": "a string containing the link's URL",
- "format": "uri-reference",
- "type": "string"
- },
- "meta": {
- "$ref": "#/components/schemas/meta"
- }
- },
- "required": [
- "href"
- ],
- "type": "object"
- }
- ]
- },
- "linkage": {
- "description": "the 'type' and 'id'",
- "properties": {
- "id": {
- "$ref": "#/components/schemas/id"
- },
- "meta": {
- "$ref": "#/components/schemas/meta"
- },
- "type": {
- "$ref": "#/components/schemas/type"
- }
- },
- "required": [
- "type",
- "id"
- ],
- "type": "object"
- },
- "links": {
- "additionalProperties": {
- "$ref": "#/components/schemas/link"
- },
- "type": "object"
- },
- "meta": {
- "additionalProperties": true,
- "type": "object"
- },
- "nulltype": {
- "default": null,
- "nullable": true,
- "type": "object"
- },
- "onlymeta": {
- "additionalProperties": false,
- "properties": {
- "meta": {
- "$ref": "#/components/schemas/meta"
- }
- }
- },
- "pageref": {
- "oneOf": [
- {
- "format": "uri-reference",
- "type": "string"
- },
- {
- "$ref": "#/components/schemas/nulltype"
- }
- ]
- },
- "pagination": {
- "properties": {
- "first": {
- "$ref": "#/components/schemas/pageref"
- },
- "last": {
- "$ref": "#/components/schemas/pageref"
- },
- "next": {
- "$ref": "#/components/schemas/pageref"
- },
- "prev": {
- "$ref": "#/components/schemas/pageref"
- }
- },
- "type": "object"
- },
- "relationshipLinks": {
- "additionalProperties": true,
- "description": "optional references to other resource objects",
- "properties": {
- "related": {
- "$ref": "#/components/schemas/link"
- },
- "self": {
- "$ref": "#/components/schemas/link"
- }
- },
- "type": "object"
- },
- "relationshipToMany": {
- "description": "An array of objects each containing the 'type' and 'id' for to-many relationships",
- "items": {
- "$ref": "#/components/schemas/linkage"
- },
- "type": "array",
- "uniqueItems": true
- },
- "relationshipToOne": {
- "anyOf": [
- {
- "$ref": "#/components/schemas/nulltype"
- },
- {
- "$ref": "#/components/schemas/linkage"
- }
- ],
- "description": "reference to other resource in a to-one relationship"
- },
- "reltomany": {
- "description": "a multiple 'to-many' relationship",
- "properties": {
- "data": {
- "$ref": "#/components/schemas/relationshipToMany"
- },
- "links": {
- "$ref": "#/components/schemas/relationshipLinks"
- },
- "meta": {
- "$ref": "#/components/schemas/meta"
- }
- },
- "type": "object"
- },
- "reltoone": {
- "description": "a singular 'to-one' relationship",
- "properties": {
- "data": {
- "$ref": "#/components/schemas/relationshipToOne"
- },
- "links": {
- "$ref": "#/components/schemas/relationshipLinks"
- },
- "meta": {
- "$ref": "#/components/schemas/meta"
- }
- },
- "type": "object"
- },
- "resource": {
- "additionalProperties": false,
- "properties": {
- "attributes": {
- "type": "object"
- },
- "id": {
- "$ref": "#/components/schemas/id"
- },
- "links": {
- "$ref": "#/components/schemas/links"
- },
- "meta": {
- "$ref": "#/components/schemas/meta"
- },
- "relationships": {
- "type": "object"
- },
- "type": {
- "$ref": "#/components/schemas/type"
- }
- },
- "required": [
- "type",
- "id"
- ],
- "type": "object"
- },
- "type": {
- "description": "The [type](https://jsonapi.org/format/#document-resource-object-identification) member is used to describe resource objects that share common attributes and relationships.",
- "type": "string"
- }
- }
- },
- "info": {
- "title": "",
- "version": ""
- },
- "openapi": "3.0.2",
- "paths": {
- "/authors/": {
- "get": {
- "description": "",
- "operationId": "List/authors/",
- "parameters": [
- {
- "$ref": "#/components/parameters/include"
- },
- {
- "$ref": "#/components/parameters/fields"
- },
- {
- "description": "A page number within the paginated result set.",
- "in": "query",
- "name": "page[number]",
- "required": false,
- "schema": {
- "type": "integer"
- }
- },
- {
- "description": "Number of results to return per page.",
- "in": "query",
- "name": "page[size]",
- "required": false,
- "schema": {
- "type": "integer"
- }
- },
- {
- "description": "[list of fields to sort by](https://jsonapi.org/format/#fetching-sorting)",
- "in": "query",
- "name": "sort",
- "required": false,
- "schema": {
- "type": "string"
- }
- },
- {
- "description": "author_type",
- "in": "query",
- "name": "filter[authorType]",
- "required": false,
- "schema": {
- "type": "string"
- }
- },
- {
- "description": "name",
- "in": "query",
- "name": "filter[name]",
- "required": false,
- "schema": {
- "type": "string"
- }
- },
- {
- "description": "A search term.",
- "in": "query",
- "name": "filter[search]",
- "required": false,
- "schema": {
- "type": "string"
- }
- }
- ],
- "responses": {
- "200": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "properties": {
- "data": {
- "items": {
- "$ref": "#/components/schemas/AuthorList"
- },
- "type": "array"
- },
- "included": {
- "items": {
- "$ref": "#/components/schemas/include"
- },
- "type": "array",
- "uniqueItems": true
- },
- "jsonapi": {
- "$ref": "#/components/schemas/jsonapi"
- },
- "links": {
- "allOf": [
- {
- "$ref": "#/components/schemas/links"
- },
- {
- "$ref": "#/components/schemas/pagination"
- }
- ],
- "description": "Link members related to primary data"
- }
- },
- "required": [
- "data"
- ],
- "type": "object"
- }
- }
- },
- "description": "List/authors/"
- },
- "400": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "bad request"
- },
- "401": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "not authorized"
- },
- "404": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "not found"
- },
- "429": {
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "$ref": "#/components/schemas/failure"
- }
- }
- },
- "description": "too many requests"
- }
- },
- "tags": [
- "authors"
- ]
- }
- }
- }
- }
- '''
-# ---
diff --git a/example/tests/test_openapi.py b/example/tests/test_openapi.py
deleted file mode 100644
index fa2f9c73..00000000
--- a/example/tests/test_openapi.py
+++ /dev/null
@@ -1,230 +0,0 @@
-# largely based on DRF's test_openapi
-import json
-
-import pytest
-from django.test import RequestFactory, override_settings
-from django.urls import re_path
-from rest_framework.request import Request
-
-from rest_framework_json_api.schemas.openapi import AutoSchema, SchemaGenerator
-
-from example import views
-
-pytestmark = pytest.mark.filterwarnings("ignore:Built-in support")
-
-
-def create_request(path):
- factory = RequestFactory()
- request = Request(factory.get(path))
- return request
-
-
-def create_view_with_kw(view_cls, method, request, initkwargs):
- generator = SchemaGenerator()
- view = generator.create_view(view_cls.as_view(initkwargs), method, request)
- return view
-
-
-def test_path_without_parameters(snapshot):
- path = "/authors/"
- method = "GET"
-
- view = create_view_with_kw(
- views.AuthorViewSet, method, create_request(path), {"get": "list"}
- )
- inspector = AutoSchema()
- inspector.view = view
-
- operation = inspector.get_operation(path, method)
- assert snapshot == json.dumps(operation, indent=2, sort_keys=True)
-
-
-def test_path_with_id_parameter(snapshot):
- path = "/authors/{id}/"
- method = "GET"
-
- view = create_view_with_kw(
- views.AuthorViewSet, method, create_request(path), {"get": "retrieve"}
- )
- inspector = AutoSchema()
- inspector.view = view
-
- operation = inspector.get_operation(path, method)
- assert snapshot == json.dumps(operation, indent=2, sort_keys=True)
-
-
-def test_post_request(snapshot):
- method = "POST"
- path = "/authors/"
-
- view = create_view_with_kw(
- views.AuthorViewSet, method, create_request(path), {"post": "create"}
- )
- inspector = AutoSchema()
- inspector.view = view
-
- operation = inspector.get_operation(path, method)
- assert snapshot == json.dumps(operation, indent=2, sort_keys=True)
-
-
-def test_patch_request(snapshot):
- method = "PATCH"
- path = "/authors/{id}"
-
- view = create_view_with_kw(
- views.AuthorViewSet, method, create_request(path), {"patch": "update"}
- )
- inspector = AutoSchema()
- inspector.view = view
-
- operation = inspector.get_operation(path, method)
- assert snapshot == json.dumps(operation, indent=2, sort_keys=True)
-
-
-def test_delete_request(snapshot):
- method = "DELETE"
- path = "/authors/{id}"
-
- view = create_view_with_kw(
- views.AuthorViewSet, method, create_request(path), {"delete": "delete"}
- )
- inspector = AutoSchema()
- inspector.view = view
-
- operation = inspector.get_operation(path, method)
- assert snapshot == json.dumps(operation, indent=2, sort_keys=True)
-
-
-@override_settings(
- REST_FRAMEWORK={
- "DEFAULT_SCHEMA_CLASS": "rest_framework_json_api.schemas.openapi.AutoSchema"
- }
-)
-def test_schema_construction(snapshot):
- """Construction of the top level dictionary."""
- patterns = [
- re_path("^authors/?$", views.AuthorViewSet.as_view({"get": "list"})),
- ]
- generator = SchemaGenerator(patterns=patterns)
-
- request = create_request("/")
- schema = generator.get_schema(request=request)
-
- assert snapshot == json.dumps(schema, indent=2, sort_keys=True)
-
-
-def test_schema_id_field():
- """ID field is only included in the root, not the attributes."""
- patterns = [
- re_path("^companies/?$", views.CompanyViewset.as_view({"get": "list"})),
- ]
- generator = SchemaGenerator(patterns=patterns)
-
- request = create_request("/")
- schema = generator.get_schema(request=request)
-
- company_properties = schema["components"]["schemas"]["Company"]["properties"]
- assert company_properties["id"] == {"$ref": "#/components/schemas/id"}
- assert "id" not in company_properties["attributes"]["properties"]
-
-
-def test_schema_subserializers():
- """Schema for child Serializers reflects the actual response structure."""
- patterns = [
- re_path(
- "^questionnaires/?$", views.QuestionnaireViewset.as_view({"get": "list"})
- ),
- ]
- generator = SchemaGenerator(patterns=patterns)
-
- request = create_request("/")
- schema = generator.get_schema(request=request)
-
- assert {
- "type": "object",
- "properties": {
- "metadata": {
- "type": "object",
- "properties": {
- "author": {"type": "string"},
- "producer": {"type": "string"},
- },
- "required": ["author"],
- },
- "questions": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "text": {"type": "string"},
- "required": {"type": "boolean", "default": False},
- },
- "required": ["text"],
- },
- },
- "name": {"type": "string", "maxLength": 100},
- },
- "required": ["name", "questions", "metadata"],
- } == schema["components"]["schemas"]["Questionnaire"]["properties"]["attributes"]
-
-
-def test_schema_parameters_include():
- """Include paramater is only used when serializer defines included_serializers."""
- patterns = [
- re_path("^authors/?$", views.AuthorViewSet.as_view({"get": "list"})),
- re_path("^project-types/?$", views.ProjectTypeViewset.as_view({"get": "list"})),
- ]
- generator = SchemaGenerator(patterns=patterns)
-
- request = create_request("/")
- schema = generator.get_schema(request=request)
-
- include_ref = {"$ref": "#/components/parameters/include"}
- assert include_ref in schema["paths"]["/authors/"]["get"]["parameters"]
- assert include_ref not in schema["paths"]["/project-types/"]["get"]["parameters"]
-
-
-def test_schema_serializer_method_resource_related_field():
- """SerializerMethodResourceRelatedField fieds have the correct relation ref."""
- patterns = [
- re_path("^entries/?$", views.EntryViewSet.as_view({"get": "list"})),
- ]
- generator = SchemaGenerator(patterns=patterns)
-
- request = Request(RequestFactory().get("/", {"include": "featured"}))
- schema = generator.get_schema(request=request)
-
- entry_schema = schema["components"]["schemas"]["Entry"]
- entry_relationships = entry_schema["properties"]["relationships"]["properties"]
-
- rel_to_many_ref = {"$ref": "#/components/schemas/reltomany"}
- assert entry_relationships["suggested"] == rel_to_many_ref
- assert entry_relationships["suggestedHyperlinked"] == rel_to_many_ref
-
- rel_to_one_ref = {"$ref": "#/components/schemas/reltoone"}
- assert entry_relationships["featured"] == rel_to_one_ref
- assert entry_relationships["featuredHyperlinked"] == rel_to_one_ref
-
-
-def test_schema_related_serializers():
- """
- Confirm that paths are generated for related fields. For example:
- /authors/{pk}/{related_field>}
- /authors/{id}/comments/
- /authors/{id}/entries/
- /authors/{id}/first_entry/
- and confirm that the schema for the related field is properly rendered
- """
- generator = SchemaGenerator()
- request = create_request("/")
- schema = generator.get_schema(request=request)
- # make sure the path's relationship and related {related_field}'s got expanded
- assert "/authors/{id}/relationships/{related_field}" in schema["paths"]
- assert "/authors/{id}/comments/" in schema["paths"]
- assert "/authors/{id}/entries/" in schema["paths"]
- assert "/authors/{id}/first_entry/" in schema["paths"]
- first_get = schema["paths"]["/authors/{id}/first_entry/"]["get"]["responses"]["200"]
- first_schema = first_get["content"]["application/vnd.api+json"]["schema"]
- first_props = first_schema["properties"]["data"]
- assert "$ref" in first_props
- assert first_props["$ref"] == "#/components/schemas/Entry"
diff --git a/example/tests/unit/test_filter_schema_params.py b/example/tests/unit/test_filter_schema_params.py
deleted file mode 100644
index d7cb4fb8..00000000
--- a/example/tests/unit/test_filter_schema_params.py
+++ /dev/null
@@ -1,107 +0,0 @@
-from rest_framework import filters as drf_filters
-
-from rest_framework_json_api import filters as dja_filters
-from rest_framework_json_api.django_filters import backends
-
-from example.views import EntryViewSet
-
-
-class DummyEntryViewSet(EntryViewSet):
- filter_backends = (
- dja_filters.QueryParameterValidationFilter,
- dja_filters.OrderingFilter,
- backends.DjangoFilterBackend,
- drf_filters.SearchFilter,
- )
- filterset_fields = {
- "id": ("exact",),
- "headline": ("exact", "contains"),
- "blog__name": ("contains",),
- }
-
- def __init__(self, **kwargs):
- # dummy up self.request since PreloadIncludesMixin expects it to be defined
- self.request = None
- super().__init__(**kwargs)
-
-
-def test_filters_get_schema_params():
- """
- test all my filters for `get_schema_operation_parameters()`
- """
- # list of tuples: (filter, expected result)
- filters = [
- (dja_filters.QueryParameterValidationFilter, []),
- (
- backends.DjangoFilterBackend,
- [
- {
- "name": "filter[id]",
- "required": False,
- "in": "query",
- "description": "id",
- "schema": {"type": "string"},
- },
- {
- "name": "filter[headline]",
- "required": False,
- "in": "query",
- "description": "headline",
- "schema": {"type": "string"},
- },
- {
- "name": "filter[headline.contains]",
- "required": False,
- "in": "query",
- "description": "headline__contains",
- "schema": {"type": "string"},
- },
- {
- "name": "filter[blog.name.contains]",
- "required": False,
- "in": "query",
- "description": "blog__name__contains",
- "schema": {"type": "string"},
- },
- ],
- ),
- (
- dja_filters.OrderingFilter,
- [
- {
- "name": "sort",
- "required": False,
- "in": "query",
- "description": "[list of fields to sort by]"
- "(https://jsonapi.org/format/#fetching-sorting)",
- "schema": {"type": "string"},
- }
- ],
- ),
- (
- drf_filters.SearchFilter,
- [
- {
- "name": "filter[search]",
- "required": False,
- "in": "query",
- "description": "A search term.",
- "schema": {"type": "string"},
- }
- ],
- ),
- ]
- view = DummyEntryViewSet()
-
- for c, expected in filters:
- f = c()
- result = f.get_schema_operation_parameters(view)
- assert len(result) == len(expected)
- if len(result) == 0:
- continue
- # py35: the result list/dict ordering isn't guaranteed
- for res_item in result:
- assert "name" in res_item
- for exp_item in expected:
- if res_item["name"] == exp_item["name"]:
- assert res_item == exp_item
diff --git a/example/urls.py b/example/urls.py
index 413d058d..471fbe81 100644
--- a/example/urls.py
+++ b/example/urls.py
@@ -1,9 +1,5 @@
from django.urls import include, path, re_path
-from django.views.generic import TemplateView
from rest_framework import routers
-from rest_framework.schemas import get_schema_view
-
-from rest_framework_json_api.schemas.openapi import SchemaGenerator
from example.views import (
AuthorRelationshipView,
@@ -87,22 +83,4 @@
AuthorRelationshipView.as_view(),
name="author-relationships",
),
- path(
- "openapi",
- get_schema_view(
- title="Example API",
- description="API for all things …",
- version="1.0.0",
- generator_class=SchemaGenerator,
- ),
- name="openapi-schema",
- ),
- path(
- "swagger-ui/",
- TemplateView.as_view(
- template_name="swagger-ui.html",
- extra_context={"schema_url": "openapi-schema"},
- ),
- name="swagger-ui",
- ),
]
diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt
index 589636e6..3db600e2 100644
--- a/requirements/requirements-optionals.txt
+++ b/requirements/requirements-optionals.txt
@@ -3,5 +3,3 @@ django-filter==24.3
# should be set to pinned version again
# see https://github.com/django-polymorphic/django-polymorphic/pull/541
django-polymorphic@git+https://github.com/django-polymorphic/django-polymorphic@master # pyup: ignore
-pyyaml==6.0.2
-uritemplate==4.1.1
diff --git a/rest_framework_json_api/django_filters/backends.py b/rest_framework_json_api/django_filters/backends.py
index c0044839..70e543c1 100644
--- a/rest_framework_json_api/django_filters/backends.py
+++ b/rest_framework_json_api/django_filters/backends.py
@@ -4,7 +4,7 @@
from rest_framework.exceptions import ValidationError
from rest_framework.settings import api_settings
-from rest_framework_json_api.utils import format_field_name, undo_format_field_name
+from rest_framework_json_api.utils import undo_format_field_name
class DjangoFilterBackend(DjangoFilterBackend):
@@ -129,18 +129,3 @@ def get_filterset_kwargs(self, request, queryset, view):
"request": request,
"filter_keys": filter_keys,
}
-
- def get_schema_operation_parameters(self, view):
- """
- Convert backend filter `name` to JSON:API-style `filter[name]`.
- For filters that are relationship paths, rewrite ORM-style `__` to our preferred `.`.
- For example: `blog__name__contains` becomes `filter[blog.name.contains]`.
-
- This is basically the reverse of `get_filterset_kwargs` above.
- """
- result = super().get_schema_operation_parameters(view)
- for res in result:
- if "name" in res:
- name = format_field_name(res["name"].replace("__", "."))
- res["name"] = f"filter[{name}]"
- return result
diff --git a/rest_framework_json_api/schemas/openapi.py b/rest_framework_json_api/schemas/openapi.py
deleted file mode 100644
index 6892e991..00000000
--- a/rest_framework_json_api/schemas/openapi.py
+++ /dev/null
@@ -1,905 +0,0 @@
-import warnings
-from urllib.parse import urljoin
-
-from rest_framework.fields import empty
-from rest_framework.relations import ManyRelatedField
-from rest_framework.schemas import openapi as drf_openapi
-from rest_framework.schemas.utils import is_list_view
-
-from rest_framework_json_api import serializers, views
-from rest_framework_json_api.relations import ManySerializerMethodResourceRelatedField
-from rest_framework_json_api.utils import format_field_name
-
-
-class SchemaGenerator(drf_openapi.SchemaGenerator):
- """
- Extend DRF's SchemaGenerator to implement JSON:API flavored generateschema command.
- """
-
- #: These JSON:API component definitions are referenced by the generated OAS schema.
- #: If you need to add more or change these static component definitions, extend this dict.
- jsonapi_components = {
- "schemas": {
- "jsonapi": {
- "type": "object",
- "description": "The server's implementation",
- "properties": {
- "version": {"type": "string"},
- "meta": {"$ref": "#/components/schemas/meta"},
- },
- "additionalProperties": False,
- },
- "resource": {
- "type": "object",
- "required": ["type", "id"],
- "additionalProperties": False,
- "properties": {
- "type": {"$ref": "#/components/schemas/type"},
- "id": {"$ref": "#/components/schemas/id"},
- "attributes": {
- "type": "object",
- # ...
- },
- "relationships": {
- "type": "object",
- # ...
- },
- "links": {"$ref": "#/components/schemas/links"},
- "meta": {"$ref": "#/components/schemas/meta"},
- },
- },
- "include": {
- "type": "object",
- "required": ["type", "id"],
- "additionalProperties": False,
- "properties": {
- "type": {"$ref": "#/components/schemas/type"},
- "id": {"$ref": "#/components/schemas/id"},
- "attributes": {
- "type": "object",
- "additionalProperties": True,
- # ...
- },
- "relationships": {
- "type": "object",
- "additionalProperties": True,
- # ...
- },
- "links": {"$ref": "#/components/schemas/links"},
- "meta": {"$ref": "#/components/schemas/meta"},
- },
- },
- "link": {
- "oneOf": [
- {
- "description": "a string containing the link's URL",
- "type": "string",
- "format": "uri-reference",
- },
- {
- "type": "object",
- "required": ["href"],
- "properties": {
- "href": {
- "description": "a string containing the link's URL",
- "type": "string",
- "format": "uri-reference",
- },
- "meta": {"$ref": "#/components/schemas/meta"},
- },
- },
- ]
- },
- "links": {
- "type": "object",
- "additionalProperties": {"$ref": "#/components/schemas/link"},
- },
- "reltoone": {
- "description": "a singular 'to-one' relationship",
- "type": "object",
- "properties": {
- "links": {"$ref": "#/components/schemas/relationshipLinks"},
- "data": {"$ref": "#/components/schemas/relationshipToOne"},
- "meta": {"$ref": "#/components/schemas/meta"},
- },
- },
- "relationshipToOne": {
- "description": "reference to other resource in a to-one relationship",
- "anyOf": [
- {"$ref": "#/components/schemas/nulltype"},
- {"$ref": "#/components/schemas/linkage"},
- ],
- },
- "reltomany": {
- "description": "a multiple 'to-many' relationship",
- "type": "object",
- "properties": {
- "links": {"$ref": "#/components/schemas/relationshipLinks"},
- "data": {"$ref": "#/components/schemas/relationshipToMany"},
- "meta": {"$ref": "#/components/schemas/meta"},
- },
- },
- "relationshipLinks": {
- "description": "optional references to other resource objects",
- "type": "object",
- "additionalProperties": True,
- "properties": {
- "self": {"$ref": "#/components/schemas/link"},
- "related": {"$ref": "#/components/schemas/link"},
- },
- },
- "relationshipToMany": {
- "description": "An array of objects each containing the "
- "'type' and 'id' for to-many relationships",
- "type": "array",
- "items": {"$ref": "#/components/schemas/linkage"},
- "uniqueItems": True,
- },
- # A RelationshipView uses a ResourceIdentifierObjectSerializer (hence the name
- # ResourceIdentifierObject returned by get_component_name()) which serializes type
- # and id. These can be lists or individual items depending on whether the
- # relationship is toMany or toOne so offer both options since we are not iterating
- # over all the possible {related_field}'s but rather rendering one path schema
- # which may represent toMany and toOne relationships.
- "ResourceIdentifierObject": {
- "oneOf": [
- {"$ref": "#/components/schemas/relationshipToOne"},
- {"$ref": "#/components/schemas/relationshipToMany"},
- ]
- },
- "linkage": {
- "type": "object",
- "description": "the 'type' and 'id'",
- "required": ["type", "id"],
- "properties": {
- "type": {"$ref": "#/components/schemas/type"},
- "id": {"$ref": "#/components/schemas/id"},
- "meta": {"$ref": "#/components/schemas/meta"},
- },
- },
- "pagination": {
- "type": "object",
- "properties": {
- "first": {"$ref": "#/components/schemas/pageref"},
- "last": {"$ref": "#/components/schemas/pageref"},
- "prev": {"$ref": "#/components/schemas/pageref"},
- "next": {"$ref": "#/components/schemas/pageref"},
- },
- },
- "pageref": {
- "oneOf": [
- {"type": "string", "format": "uri-reference"},
- {"$ref": "#/components/schemas/nulltype"},
- ]
- },
- "failure": {
- "type": "object",
- "required": ["errors"],
- "properties": {
- "errors": {"$ref": "#/components/schemas/errors"},
- "meta": {"$ref": "#/components/schemas/meta"},
- "jsonapi": {"$ref": "#/components/schemas/jsonapi"},
- "links": {"$ref": "#/components/schemas/links"},
- },
- },
- "errors": {
- "type": "array",
- "items": {"$ref": "#/components/schemas/error"},
- "uniqueItems": True,
- },
- "error": {
- "type": "object",
- "additionalProperties": False,
- "properties": {
- "id": {"type": "string"},
- "status": {"type": "string"},
- "links": {"$ref": "#/components/schemas/links"},
- "code": {"type": "string"},
- "title": {"type": "string"},
- "detail": {"type": "string"},
- "source": {
- "type": "object",
- "properties": {
- "pointer": {
- "type": "string",
- "description": (
- "A [JSON Pointer](https://tools.ietf.org/html/rfc6901) "
- "to the associated entity in the request document "
- "[e.g. `/data` for a primary data object, or "
- "`/data/attributes/title` for a specific attribute."
- ),
- },
- "parameter": {
- "type": "string",
- "description": "A string indicating which query parameter "
- "caused the error.",
- },
- "meta": {"$ref": "#/components/schemas/meta"},
- },
- },
- },
- },
- "onlymeta": {
- "additionalProperties": False,
- "properties": {"meta": {"$ref": "#/components/schemas/meta"}},
- },
- "meta": {"type": "object", "additionalProperties": True},
- "datum": {
- "description": "singular item",
- "properties": {"data": {"$ref": "#/components/schemas/resource"}},
- },
- "nulltype": {"type": "object", "nullable": True, "default": None},
- "type": {
- "type": "string",
- "description": "The [type]"
- "(https://jsonapi.org/format/#document-resource-object-identification) "
- "member is used to describe resource objects that share common attributes "
- "and relationships.",
- },
- "id": {
- "type": "string",
- "description": "Each resource object’s type and id pair MUST "
- "[identify]"
- "(https://jsonapi.org/format/#document-resource-object-identification) "
- "a single, unique resource.",
- },
- },
- "parameters": {
- "include": {
- "name": "include",
- "in": "query",
- "description": "[list of included related resources]"
- "(https://jsonapi.org/format/#fetching-includes)",
- "required": False,
- "style": "form",
- "schema": {"type": "string"},
- },
- # TODO: deepObject not well defined/supported:
- # https://github.com/OAI/OpenAPI-Specification/issues/1706
- "fields": {
- "name": "fields",
- "in": "query",
- "description": "[sparse fieldsets]"
- "(https://jsonapi.org/format/#fetching-sparse-fieldsets).\n"
- "Use fields[\\]=field1,field2,...,fieldN",
- "required": False,
- "style": "deepObject",
- "schema": {
- "type": "object",
- },
- "explode": True,
- },
- },
- }
-
- def get_schema(self, request=None, public=False):
- """
- Generate a JSON:API OpenAPI schema.
- Overrides upstream DRF's get_schema.
- """
- # TODO: avoid copying so much of upstream get_schema()
- schema = super().get_schema(request, public)
-
- components_schemas = {}
-
- # Iterate endpoints generating per method path operations.
- paths = {}
- _, view_endpoints = self._get_paths_and_endpoints(None if public else request)
-
- #: `expanded_endpoints` is like view_endpoints with one extra field tacked on:
- #: - 'action' copy of current view.action (list/fetch) as this gets reset for
- # each request.
- expanded_endpoints = []
- for path, method, view in view_endpoints:
- if hasattr(view, "action") and view.action == "retrieve_related":
- expanded_endpoints += self._expand_related(
- path, method, view, view_endpoints
- )
- else:
- expanded_endpoints.append(
- (path, method, view, getattr(view, "action", None))
- )
-
- for path, method, view, action in expanded_endpoints:
- if not self.has_view_permissions(path, method, view):
- continue
- # kludge to preserve view.action as it is 'list' for the parent ViewSet
- # but the related viewset that was expanded may be either 'fetch' (to_one) or 'list'
- # (to_many). This patches the view.action appropriately so that
- # view.schema.get_operation() "does the right thing" for fetch vs. list.
- current_action = None
- if hasattr(view, "action"):
- current_action = view.action
- view.action = action
- operation = view.schema.get_operation(path, method)
- components = view.schema.get_components(path, method)
- for k in components.keys():
- if k not in components_schemas:
- continue
- if components_schemas[k] == components[k]:
- continue
- warnings.warn(
- f'Schema component "{k}" has been overriden with a different value.',
- stacklevel=1,
- )
-
- components_schemas.update(components)
-
- if hasattr(view, "action"):
- view.action = current_action
- # Normalise path for any provided mount url.
- if path.startswith("/"):
- path = path[1:]
- path = urljoin(self.url or "/", path)
-
- paths.setdefault(path, {})
- paths[path][method.lower()] = operation
-
- self.check_duplicate_operation_id(paths)
-
- # Compile final schema, overriding stuff from super class.
- schema["paths"] = paths
- schema["components"] = self.jsonapi_components
- schema["components"]["schemas"].update(components_schemas)
-
- return schema
-
- def _expand_related(self, path, method, view, view_endpoints):
- """
- Expand path containing .../{id}/{related_field} into list of related fields
- and **their** views, making sure toOne relationship's views are a 'fetch' and toMany
- relationship's are a 'list'.
- :param path
- :param method
- :param view
- :param view_endpoints
- :return:list[tuple(path, method, view, action)]
- """
- result = []
- serializer = view.get_serializer()
- # It's not obvious if it's allowed to have both included_ and related_ serializers,
- # so just merge both dicts.
- serializers = {}
- if hasattr(serializer, "included_serializers"):
- serializers = {**serializers, **serializer.included_serializers}
- if hasattr(serializer, "related_serializers"):
- serializers = {**serializers, **serializer.related_serializers}
- related_fields = [fs for fs in serializers.items()]
-
- for field, related_serializer in related_fields:
- related_view = self._find_related_view(
- view_endpoints, related_serializer, view
- )
- if related_view:
- action = self._field_is_one_or_many(field, view)
- result.append(
- (
- path.replace("{related_field}", field),
- method,
- related_view,
- action,
- )
- )
-
- return result
-
- def _find_related_view(self, view_endpoints, related_serializer, parent_view):
- """
- For a given related_serializer, try to find it's "parent" view instance.
-
- :param view_endpoints: list of all view endpoints
- :param related_serializer: the related serializer for a given related field
- :param parent_view: the parent view (used to find toMany vs. toOne).
- TODO: not actually used.
- :return:view
- """
- for _path, _method, view in view_endpoints:
- view_serializer = view.get_serializer()
- if isinstance(view_serializer, related_serializer):
- return view
-
- return None
-
- def _field_is_one_or_many(self, field, view):
- serializer = view.get_serializer()
- if isinstance(serializer.fields[field], ManyRelatedField):
- return "list"
- else:
- return "fetch"
-
-
-class AutoSchema(drf_openapi.AutoSchema):
- """
- Extend DRF's openapi.AutoSchema for JSON:API serialization.
- """
-
- #: ignore all the media types and only generate a JSON:API schema.
- content_types = ["application/vnd.api+json"]
-
- def get_operation(self, path, method):
- """
- JSON:API adds some standard fields to the API response that are not in upstream DRF:
- - some that only apply to GET/HEAD methods.
- - collections
- - special handling for POST, PATCH, DELETE
- """
-
- warnings.warn(
- DeprecationWarning(
- "Built-in support for generating OpenAPI schema is deprecated. "
- "Use drf-spectacular-json-api instead see "
- "https://github.com/jokiefer/drf-spectacular-json-api/"
- ),
- stacklevel=2,
- )
-
- operation = {}
- operation["operationId"] = self.get_operation_id(path, method)
- operation["description"] = self.get_description(path, method)
-
- serializer = self.get_response_serializer(path, method)
-
- parameters = []
- parameters += self.get_path_parameters(path, method)
- # pagination, filters only apply to GET/HEAD of collections and items
- if method in ["GET", "HEAD"]:
- parameters += self._get_include_parameters(path, method, serializer)
- parameters += self._get_fields_parameters(path, method)
- parameters += self.get_pagination_parameters(path, method)
- parameters += self.get_filter_parameters(path, method)
- operation["parameters"] = parameters
- operation["tags"] = self.get_tags(path, method)
-
- # get request and response code schemas
- if method == "GET":
- if is_list_view(path, method, self.view):
- self._add_get_collection_response(operation, path)
- else:
- self._add_get_item_response(operation, path)
- elif method == "POST":
- self._add_post_item_response(operation, path)
- elif method == "PATCH":
- self._add_patch_item_response(operation, path)
- elif method == "DELETE":
- # should only allow deleting a resource, not a collection
- # TODO: implement delete of a relationship in future release.
- self._add_delete_item_response(operation, path)
- return operation
-
- def get_operation_id(self, path, method):
- """
- The upstream DRF version creates non-unique operationIDs, because the same view is
- used for the main path as well as such as related and relationships.
- This concatenates the (mapped) method name and path as the spec allows most any
- """
- method_name = getattr(self.view, "action", method.lower())
- if is_list_view(path, method, self.view):
- action = "List"
- elif method_name not in self.method_mapping:
- action = method_name
- else:
- action = self.method_mapping[method.lower()]
- return action + path
-
- def _get_include_parameters(self, path, method, serializer):
- """
- includes parameter: https://jsonapi.org/format/#fetching-includes
- """
- if getattr(serializer, "included_serializers", {}):
- return [{"$ref": "#/components/parameters/include"}]
- return []
-
- def _get_fields_parameters(self, path, method):
- """
- sparse fieldsets https://jsonapi.org/format/#fetching-sparse-fieldsets
- """
- # TODO: See if able to identify the specific types for fields[type]=... and return this:
- # name: fields
- # in: query
- # description: '[sparse fieldsets](https://jsonapi.org/format/#fetching-sparse-fieldsets)' # noqa: B950
- # required: true
- # style: deepObject
- # schema:
- # type: object
- # properties:
- # hello:
- # type: string # noqa F821
- # world:
- # type: string # noqa F821
- # explode: true
- return [{"$ref": "#/components/parameters/fields"}]
-
- def _add_get_collection_response(self, operation, path):
- """
- Add GET 200 response for a collection to operation
- """
- operation["responses"] = {
- "200": self._get_toplevel_200_response(
- operation, path, "GET", collection=True
- )
- }
- self._add_get_4xx_responses(operation)
-
- def _add_get_item_response(self, operation, path):
- """
- add GET 200 response for an item to operation
- """
- operation["responses"] = {
- "200": self._get_toplevel_200_response(
- operation, path, "GET", collection=False
- )
- }
- self._add_get_4xx_responses(operation)
-
- def _get_toplevel_200_response(self, operation, path, method, collection=True):
- """
- return top-level JSON:API GET 200 response
-
- :param collection: True for collections; False for individual items.
-
- Uses a $ref to the components.schemas. component definition.
- """
- if collection:
- data = {
- "type": "array",
- "items": self.get_reference(self.get_response_serializer(path, method)),
- }
- else:
- data = self.get_reference(self.get_response_serializer(path, method))
-
- return {
- "description": operation["operationId"],
- "content": {
- "application/vnd.api+json": {
- "schema": {
- "type": "object",
- "required": ["data"],
- "properties": {
- "data": data,
- "included": {
- "type": "array",
- "uniqueItems": True,
- "items": {"$ref": "#/components/schemas/include"},
- },
- "links": {
- "description": "Link members related to primary data",
- "allOf": [
- {"$ref": "#/components/schemas/links"},
- {"$ref": "#/components/schemas/pagination"},
- ],
- },
- "jsonapi": {"$ref": "#/components/schemas/jsonapi"},
- },
- }
- }
- },
- }
-
- def _add_post_item_response(self, operation, path):
- """
- add response for POST of an item to operation
- """
- operation["requestBody"] = self.get_request_body(path, "POST")
- operation["responses"] = {
- "201": self._get_toplevel_200_response(
- operation, path, "POST", collection=False
- )
- }
- operation["responses"]["201"]["description"] = (
- "[Created](https://jsonapi.org/format/#crud-creating-responses-201). "
- "Assigned `id` and/or any other changes are in this response."
- )
- self._add_async_response(operation)
- operation["responses"]["204"] = {
- "description": "[Created](https://jsonapi.org/format/#crud-creating-responses-204) "
- "with the supplied `id`. No other changes from what was POSTed."
- }
- self._add_post_4xx_responses(operation)
-
- def _add_patch_item_response(self, operation, path):
- """
- Add PATCH response for an item to operation
- """
- operation["requestBody"] = self.get_request_body(path, "PATCH")
- operation["responses"] = {
- "200": self._get_toplevel_200_response(
- operation, path, "PATCH", collection=False
- )
- }
- self._add_patch_4xx_responses(operation)
-
- def _add_delete_item_response(self, operation, path):
- """
- add DELETE response for item or relationship(s) to operation
- """
- # Only DELETE of relationships has a requestBody
- if isinstance(self.view, views.RelationshipView):
- operation["requestBody"] = self.get_request_body(path, "DELETE")
- self._add_delete_responses(operation)
-
- def get_request_body(self, path, method):
- """
- A request body is required by JSON:API for POST, PATCH, and DELETE methods.
- """
- serializer = self.get_request_serializer(path, method)
- if not isinstance(serializer, (serializers.BaseSerializer,)):
- return {}
- is_relationship = isinstance(self.view, views.RelationshipView)
-
- # DRF uses a $ref to the component schema definition, but this
- # doesn't work for JSON:API due to the different required fields based on
- # the method, so make those changes and inline another copy of the schema.
-
- # TODO: A future improvement could make this DRYer with multiple component schemas:
- # A base schema for each viewset that has no required fields
- # One subclassed from the base that requires some fields (`type` but not `id` for POST)
- # Another subclassed from base with required type/id but no required attributes (PATCH)
-
- if is_relationship:
- item_schema = {"$ref": "#/components/schemas/ResourceIdentifierObject"}
- else:
- item_schema = self.map_serializer(serializer)
- if method == "POST":
- # 'type' and 'id' are both required for:
- # - all relationship operations
- # - regular PATCH or DELETE
- # Only 'type' is required for POST: system may assign the 'id'.
- item_schema["required"] = ["type"]
-
- if "properties" in item_schema and "attributes" in item_schema["properties"]:
- # No required attributes for PATCH
- if (
- method in ["PATCH", "PUT"]
- and "required" in item_schema["properties"]["attributes"]
- ):
- del item_schema["properties"]["attributes"]["required"]
- # No read_only fields for request.
- for name, schema in (
- item_schema["properties"]["attributes"]["properties"].copy().items()
- ): # noqa E501
- if "readOnly" in schema:
- del item_schema["properties"]["attributes"]["properties"][name]
-
- if "properties" in item_schema and "relationships" in item_schema["properties"]:
- # No required relationships for PATCH
- if (
- method in ["PATCH", "PUT"]
- and "required" in item_schema["properties"]["relationships"]
- ):
- del item_schema["properties"]["relationships"]["required"]
-
- return {
- "content": {
- ct: {
- "schema": {
- "required": ["data"],
- "properties": {"data": item_schema},
- }
- }
- for ct in self.content_types
- }
- }
-
- def map_serializer(self, serializer):
- """
- Custom map_serializer that serializes the schema using the JSON:API spec.
-
- Non-attributes like related and identity fields, are moved to 'relationships'
- and 'links'.
- """
- # TODO: remove attributes, etc. for relationshipView??
- if isinstance(
- serializer.parent, (serializers.ListField, serializers.BaseSerializer)
- ):
- # Return plain non-JSON:API serializer schema for serializers nested inside
- # a Serializer or a ListField, as those don't use the full JSON:API
- # serializer schemas.
- return super().map_serializer(serializer)
-
- required = []
- attributes = {}
- relationships_required = []
- relationships = {}
-
- for field in serializer.fields.values():
- if isinstance(field, serializers.HyperlinkedIdentityField):
- # the 'url' is not an attribute but rather a self.link, so don't map it here.
- continue
- if isinstance(field, serializers.HiddenField):
- continue
- if isinstance(
- field,
- (
- serializers.ManyRelatedField,
- ManySerializerMethodResourceRelatedField,
- ),
- ):
- if field.required:
- relationships_required.append(format_field_name(field.field_name))
- relationships[format_field_name(field.field_name)] = {
- "$ref": "#/components/schemas/reltomany"
- }
- continue
- if isinstance(field, serializers.RelatedField):
- if field.required:
- relationships_required.append(format_field_name(field.field_name))
- relationships[format_field_name(field.field_name)] = {
- "$ref": "#/components/schemas/reltoone"
- }
- continue
- if field.field_name == "id":
- # ID is always provided in the root of JSON:API and removed from the
- # attributes in JSONRenderer.
- continue
-
- if field.required:
- required.append(format_field_name(field.field_name))
-
- schema = self.map_field(field)
- if field.read_only:
- schema["readOnly"] = True
- if field.write_only:
- schema["writeOnly"] = True
- if field.allow_null:
- schema["nullable"] = True
- if field.default and field.default != empty and not callable(field.default):
- schema["default"] = field.default
- if field.help_text:
- # Ensure django gettext_lazy is rendered correctly
- schema["description"] = str(field.help_text)
- self.map_field_validators(field, schema)
-
- attributes[format_field_name(field.field_name)] = schema
-
- result = {
- "type": "object",
- "required": ["type", "id"],
- "additionalProperties": False,
- "properties": {
- "type": {"$ref": "#/components/schemas/type"},
- "id": {"$ref": "#/components/schemas/id"},
- "links": {
- "type": "object",
- "properties": {"self": {"$ref": "#/components/schemas/link"}},
- },
- },
- }
- if attributes:
- result["properties"]["attributes"] = {
- "type": "object",
- "properties": attributes,
- }
- if required:
- result["properties"]["attributes"]["required"] = required
-
- if relationships:
- result["properties"]["relationships"] = {
- "type": "object",
- "properties": relationships,
- }
- if relationships_required:
- result["properties"]["relationships"][
- "required"
- ] = relationships_required
- return result
-
- def _add_async_response(self, operation):
- """
- Add async response to operation
- """
- operation["responses"]["202"] = {
- "description": "Accepted for [asynchronous processing]"
- "(https://jsonapi.org/recommendations/#asynchronous-processing)",
- "content": {
- "application/vnd.api+json": {
- "schema": {"$ref": "#/components/schemas/datum"}
- }
- },
- }
-
- def _failure_response(self, reason):
- """
- Return failure response reason as the description
- """
- return {
- "description": reason,
- "content": {
- "application/vnd.api+json": {
- "schema": {"$ref": "#/components/schemas/failure"}
- }
- },
- }
-
- def _add_generic_failure_responses(self, operation):
- """
- Add generic failure response(s) to operation
- """
- for code, reason in [
- ("400", "bad request"),
- ("401", "not authorized"),
- ("429", "too many requests"),
- ]:
- operation["responses"][code] = self._failure_response(reason)
-
- def _add_get_4xx_responses(self, operation):
- """
- Add generic 4xx GET responses to operation
- """
- self._add_generic_failure_responses(operation)
- for code, reason in [("404", "not found")]:
- operation["responses"][code] = self._failure_response(reason)
-
- def _add_post_4xx_responses(self, operation):
- """
- Add POST 4xx error responses to operation
- """
- self._add_generic_failure_responses(operation)
- for code, reason in [
- (
- "403",
- "[Forbidden](https://jsonapi.org/format/#crud-creating-responses-403)",
- ),
- (
- "404",
- "[Related resource does not exist]"
- "(https://jsonapi.org/format/#crud-creating-responses-404)",
- ),
- (
- "409",
- "[Conflict](https://jsonapi.org/format/#crud-creating-responses-409)",
- ),
- ]:
- operation["responses"][code] = self._failure_response(reason)
-
- def _add_patch_4xx_responses(self, operation):
- """
- Add PATCH 4xx error responses to operation
- """
- self._add_generic_failure_responses(operation)
- for code, reason in [
- (
- "403",
- "[Forbidden](https://jsonapi.org/format/#crud-updating-responses-403)",
- ),
- (
- "404",
- "[Related resource does not exist]"
- "(https://jsonapi.org/format/#crud-updating-responses-404)",
- ),
- (
- "409",
- "[Conflict]([Conflict]"
- "(https://jsonapi.org/format/#crud-updating-responses-409)",
- ),
- ]:
- operation["responses"][code] = self._failure_response(reason)
-
- def _add_delete_responses(self, operation):
- """
- Add generic DELETE responses to operation
- """
- # the 2xx statuses:
- operation["responses"] = {
- "200": {
- "description": "[OK](https://jsonapi.org/format/#crud-deleting-responses-200)",
- "content": {
- "application/vnd.api+json": {
- "schema": {"$ref": "#/components/schemas/onlymeta"}
- }
- },
- }
- }
- self._add_async_response(operation)
- operation["responses"]["204"] = {
- "description": "[no content](https://jsonapi.org/format/#crud-deleting-responses-204)", # noqa: B950
- }
- # the 4xx errors:
- self._add_generic_failure_responses(operation)
- for code, reason in [
- (
- "404",
- "[Resource does not exist]"
- "(https://jsonapi.org/format/#crud-deleting-responses-404)",
- ),
- ]:
- operation["responses"][code] = self._failure_response(reason)
diff --git a/setup.cfg b/setup.cfg
index 4230dcbb..92606700 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -63,9 +63,6 @@ DJANGO_SETTINGS_MODULE=example.settings.test
filterwarnings =
error::DeprecationWarning
error::PendingDeprecationWarning
- # Django filter schema generation. Can be removed once we remove
- # schema support
- ignore:Built-in schema generation is deprecated.
testpaths =
example
tests
diff --git a/setup.py b/setup.py
index de61b0d1..0b88f4c9 100755
--- a/setup.py
+++ b/setup.py
@@ -112,7 +112,6 @@ def get_package_data(package):
extras_require={
"django-polymorphic": ["django-polymorphic>=3.0"],
"django-filter": ["django-filter>=2.4"],
- "openapi": ["pyyaml>=5.4", "uritemplate>=3.0.1"],
},
setup_requires=wheel,
python_requires=">=3.9",
diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py
deleted file mode 100644
index 427f18fc..00000000
--- a/tests/schemas/test_openapi.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from rest_framework_json_api.schemas.openapi import AutoSchema
-from tests.serializers import CallableDefaultSerializer
-
-
-class TestAutoSchema:
- def test_schema_callable_default(self):
- inspector = AutoSchema()
- result = inspector.map_serializer(CallableDefaultSerializer())
- assert result["properties"]["attributes"]["properties"]["field"] == {
- "type": "string",
- }