mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-29 01:20:02 +03:00
Merge branch 'master' into add_django_restql_to_third_party_packages
This commit is contained in:
commit
7cd283bb6d
|
@ -60,7 +60,8 @@ urlpatterns = [
|
|||
# * Provide view name for use with `reverse()`.
|
||||
path('openapi', get_schema_view(
|
||||
title="Your Project",
|
||||
description="API for all things …"
|
||||
description="API for all things …",
|
||||
version="1.0.0"
|
||||
), name='openapi-schema'),
|
||||
# ...
|
||||
]
|
||||
|
@ -72,6 +73,7 @@ The `get_schema_view()` helper takes the following keyword arguments:
|
|||
|
||||
* `title`: May be used to provide a descriptive title for the schema definition.
|
||||
* `description`: Longer descriptive text.
|
||||
* `version`: The version of the API. Defaults to `0.1.0`.
|
||||
* `url`: May be used to pass a canonical base URL for the schema.
|
||||
|
||||
schema_view = get_schema_view(
|
||||
|
@ -137,6 +139,7 @@ Arguments:
|
|||
|
||||
* `title` **required**: The name of the API.
|
||||
* `description`: Longer descriptive text.
|
||||
* `version`: The version of the API. Defaults to `0.1.0`.
|
||||
* `url`: The root URL of the API schema. This option is not required unless the schema is included under path prefix.
|
||||
* `patterns`: A list of URLs to inspect when generating the schema. Defaults to the project's URL conf.
|
||||
* `urlconf`: A URL conf module name to use when generating the schema. Defaults to `settings.ROOT_URLCONF`.
|
||||
|
|
|
@ -218,7 +218,7 @@ in the `.validate()` method, or else in the view.
|
|||
For example:
|
||||
|
||||
class BillingRecordSerializer(serializers.ModelSerializer):
|
||||
def validate(self, data):
|
||||
def validate(self, attrs):
|
||||
# Apply custom validation either here, or in the view.
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -40,6 +40,35 @@ You can determine your currently installed version using `pip show`:
|
|||
|
||||
## 3.10.x series
|
||||
|
||||
### 3.10.3
|
||||
|
||||
* Include API version in OpenAPI schema generation, defaulting to empty string.
|
||||
* Add pagination properties to OpenAPI response schemas.
|
||||
* Add missing "description" property to OpenAPI response schemas.
|
||||
* Only include "required" for non-empty cases in OpenAPI schemas.
|
||||
* Fix response schemas for "DELETE" case in OpenAPI schemas.
|
||||
* Use an array type for list view response schemas.
|
||||
* Use consistent `lowerInitialCamelCase` style in OpenAPI operation IDs.
|
||||
* Fix `minLength`/`maxLength`/`minItems`/`maxItems` properties in OpenAPI schemas.
|
||||
* Only call `FileField.url` once in serialization, for improved performance.
|
||||
* Fix an edge case where throttling calcualtions could error after a configuration change.
|
||||
|
||||
* TODO
|
||||
|
||||
### 3.10.2
|
||||
|
||||
**Date**: 29th July 2019
|
||||
|
||||
* Various `OpenAPI` schema fixes.
|
||||
* Ability to specify urlconf in include_docs_urls.
|
||||
|
||||
### 3.10.1
|
||||
|
||||
**Date**: 17th July 2019
|
||||
|
||||
* Don't include autocomplete fields on TokenAuth admin, since it forces constraints on custom user models & admin.
|
||||
* Require `uritemplate` for OpenAPI schema generation, but not `coreapi`.
|
||||
|
||||
### 3.10.0
|
||||
|
||||
**Date**: [15th July 2019][3.10.0-milestone]
|
||||
|
|
|
@ -212,6 +212,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
* [drf-flex-fields][drf-flex-fields] - Serializer providing dynamic field expansion and sparse field sets via URL parameters.
|
||||
* [drf-action-serializer][drf-action-serializer] - Serializer providing per-action fields config for use with ViewSets to prevent having to write multiple serializers.
|
||||
* [django-restql][django-restql] - Turn your REST API into a GraphQL like API(It allows clients to control which fields will be sent in a response, supports both flat and nested fields, uses GraphQL like syntax).
|
||||
* [djangorestframework-dataclasses][djangorestframework-dataclasses] - Serializer providing automatic field generation for Python dataclasses, like the built-in ModelSerializer does for models.
|
||||
|
||||
### Serializer fields
|
||||
|
||||
|
@ -350,5 +351,6 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
|||
[django-restql]: https://github.com/yezyilomo/django-restql
|
||||
[drf-flex-fields]: https://github.com/rsinger86/drf-flex-fields
|
||||
[drf-action-serializer]: https://github.com/gregschmit/drf-action-serializer
|
||||
[djangorestframework-dataclasses]: https://github.com/oxan/djangorestframework-dataclasses
|
||||
[djangorestframework-mvt]: https://github.com/corteva/djangorestframework-mvt
|
||||
[django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian
|
||||
|
|
|
@ -85,11 +85,11 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
|
|||
|
||||
|
||||
[beginners-guide-to-the-django-rest-framework]: https://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786
|
||||
[getting-started-with-django-rest-framework-and-angularjs]: https://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html
|
||||
[getting-started-with-django-rest-framework-and-angularjs]: https://blog.kevinastone.com/django-rest-framework-and-angular-js
|
||||
[end-to-end-web-app-with-django-rest-framework-angularjs]: https://mourafiq.com/2013/07/01/end-to-end-web-app-with-django-angular-1.html
|
||||
[start-your-api-django-rest-framework-part-1]: https://godjango.com/41-start-your-api-django-rest-framework-part-1/
|
||||
[permissions-authentication-django-rest-framework-part-2]: https://godjango.com/43-permissions-authentication-django-rest-framework-part-2/
|
||||
[viewsets-and-routers-django-rest-framework-part-3]: https://godjango.com/45-viewsets-and-routers-django-rest-framework-part-3/
|
||||
[start-your-api-django-rest-framework-part-1]: https://www.youtube.com/watch?v=hqo2kk91WpE
|
||||
[permissions-authentication-django-rest-framework-part-2]: https://www.youtube.com/watch?v=R3xvUDUZxGU
|
||||
[viewsets-and-routers-django-rest-framework-part-3]: https://www.youtube.com/watch?v=2d6w4DGQ4OU
|
||||
[django-rest-framework-user-endpoint]: https://richardtier.com/2014/02/25/django-rest-framework-user-endpoint/
|
||||
[check-credentials-using-django-rest-framework]: https://richardtier.com/2014/03/06/110/
|
||||
[ember-and-django-part 1-video]: http://www.neckbeardrepublic.com/screencasts/ember-and-django-part-1
|
||||
|
|
|
@ -8,7 +8,7 @@ ______ _____ _____ _____ __
|
|||
"""
|
||||
|
||||
__title__ = 'Django REST framework'
|
||||
__version__ = '3.10.1'
|
||||
__version__ = '3.10.3'
|
||||
__author__ = 'Tom Christie'
|
||||
__license__ = 'BSD 2-Clause'
|
||||
__copyright__ = 'Copyright 2011-2019 Encode OSS Ltd'
|
||||
|
|
|
@ -138,6 +138,9 @@ class BasePagination:
|
|||
def get_paginated_response(self, data): # pragma: no cover
|
||||
raise NotImplementedError('get_paginated_response() must be implemented.')
|
||||
|
||||
def get_paginated_response_schema(self, schema):
|
||||
return schema
|
||||
|
||||
def to_html(self): # pragma: no cover
|
||||
raise NotImplementedError('to_html() must be implemented to display page controls.')
|
||||
|
||||
|
@ -222,6 +225,26 @@ class PageNumberPagination(BasePagination):
|
|||
('results', data)
|
||||
]))
|
||||
|
||||
def get_paginated_response_schema(self, schema):
|
||||
return {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'count': {
|
||||
'type': 'integer',
|
||||
'example': 123,
|
||||
},
|
||||
'next': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'previous': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'results': schema,
|
||||
},
|
||||
}
|
||||
|
||||
def get_page_size(self, request):
|
||||
if self.page_size_query_param:
|
||||
try:
|
||||
|
@ -369,6 +392,26 @@ class LimitOffsetPagination(BasePagination):
|
|||
('results', data)
|
||||
]))
|
||||
|
||||
def get_paginated_response_schema(self, schema):
|
||||
return {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'count': {
|
||||
'type': 'integer',
|
||||
'example': 123,
|
||||
},
|
||||
'next': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'previous': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'results': schema,
|
||||
},
|
||||
}
|
||||
|
||||
def get_limit(self, request):
|
||||
if self.limit_query_param:
|
||||
try:
|
||||
|
@ -840,6 +883,22 @@ class CursorPagination(BasePagination):
|
|||
('results', data)
|
||||
]))
|
||||
|
||||
def get_paginated_response_schema(self, schema):
|
||||
return {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'next': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'previous': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'results': schema,
|
||||
},
|
||||
}
|
||||
|
||||
def get_html_context(self):
|
||||
return {
|
||||
'previous_url': self.get_previous_link(),
|
||||
|
|
|
@ -31,7 +31,8 @@ def get_schema_view(
|
|||
title=None, url=None, description=None, urlconf=None, renderer_classes=None,
|
||||
public=False, patterns=None, generator_class=None,
|
||||
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
|
||||
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
|
||||
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES,
|
||||
version=None):
|
||||
"""
|
||||
Return a schema view.
|
||||
"""
|
||||
|
@ -43,7 +44,7 @@ def get_schema_view(
|
|||
|
||||
generator = generator_class(
|
||||
title=title, url=url, description=description,
|
||||
urlconf=urlconf, patterns=patterns,
|
||||
urlconf=urlconf, patterns=patterns, version=version
|
||||
)
|
||||
|
||||
# Avoid import cycle on APIView
|
||||
|
|
|
@ -124,7 +124,7 @@ class SchemaGenerator(BaseSchemaGenerator):
|
|||
# Set by 'SCHEMA_COERCE_METHOD_NAMES'.
|
||||
coerce_method_names = None
|
||||
|
||||
def __init__(self, title=None, url=None, description=None, patterns=None, urlconf=None):
|
||||
def __init__(self, title=None, url=None, description=None, patterns=None, urlconf=None, version=None):
|
||||
assert coreapi, '`coreapi` must be installed for schema support.'
|
||||
assert coreschema, '`coreschema` must be installed for schema support.'
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ class BaseSchemaGenerator(object):
|
|||
# Set by 'SCHEMA_COERCE_PATH_PK'.
|
||||
coerce_path_pk = None
|
||||
|
||||
def __init__(self, title=None, url=None, description=None, patterns=None, urlconf=None):
|
||||
def __init__(self, title=None, url=None, description=None, patterns=None, urlconf=None, version=''):
|
||||
if url and not url.endswith('/'):
|
||||
url += '/'
|
||||
|
||||
|
@ -161,6 +161,7 @@ class BaseSchemaGenerator(object):
|
|||
self.urlconf = urlconf
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.version = version
|
||||
self.url = url
|
||||
self.endpoints = None
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class SchemaGenerator(BaseSchemaGenerator):
|
|||
def get_info(self):
|
||||
info = {
|
||||
'title': self.title,
|
||||
'version': 'TODO',
|
||||
'version': self.version,
|
||||
}
|
||||
|
||||
if self.description is not None:
|
||||
|
@ -111,7 +111,7 @@ class AutoSchema(ViewInspector):
|
|||
"""
|
||||
method_name = getattr(self.view, 'action', method.lower())
|
||||
if is_list_view(path, method, self.view):
|
||||
action = 'List'
|
||||
action = 'list'
|
||||
elif method_name not in self.method_mapping:
|
||||
action = method_name
|
||||
else:
|
||||
|
@ -135,10 +135,13 @@ class AutoSchema(ViewInspector):
|
|||
name = name[:-7]
|
||||
elif name.endswith('View'):
|
||||
name = name[:-4]
|
||||
if name.endswith(action): # ListView, UpdateAPIView, ThingDelete ...
|
||||
|
||||
# Due to camel-casing of classes and `action` being lowercase, apply title in order to find if action truly
|
||||
# comes at the end of the name
|
||||
if name.endswith(action.title()): # ListView, UpdateAPIView, ThingDelete ...
|
||||
name = name[:-len(action)]
|
||||
|
||||
if action == 'List' and not name.endswith('s'): # ListThings instead of ListThing
|
||||
if action == 'list' and not name.endswith('s'): # listThings instead of listThing
|
||||
name += 's'
|
||||
|
||||
return action + name
|
||||
|
@ -206,11 +209,10 @@ class AutoSchema(ViewInspector):
|
|||
if not is_list_view(path, method, view):
|
||||
return []
|
||||
|
||||
pagination = getattr(view, 'pagination_class', None)
|
||||
if not pagination:
|
||||
paginator = self._get_pagninator()
|
||||
if not paginator:
|
||||
return []
|
||||
|
||||
paginator = view.pagination_class()
|
||||
return paginator.get_schema_operation_parameters(view)
|
||||
|
||||
def _map_field(self, field):
|
||||
|
@ -378,7 +380,7 @@ class AutoSchema(ViewInspector):
|
|||
schema['default'] = field.default
|
||||
if field.help_text:
|
||||
schema['description'] = str(field.help_text)
|
||||
self._map_field_validators(field.validators, schema)
|
||||
self._map_field_validators(field, schema)
|
||||
|
||||
properties[field.field_name] = schema
|
||||
|
||||
|
@ -390,13 +392,11 @@ class AutoSchema(ViewInspector):
|
|||
|
||||
return result
|
||||
|
||||
def _map_field_validators(self, validators, schema):
|
||||
def _map_field_validators(self, field, schema):
|
||||
"""
|
||||
map field validators
|
||||
:param list:validators: list of field validators
|
||||
:param dict:schema: schema that the validators get added to
|
||||
"""
|
||||
for v in validators:
|
||||
for v in field.validators:
|
||||
# "Formats such as "email", "uuid", and so on, MAY be used even though undefined by this specification."
|
||||
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#data-types
|
||||
if isinstance(v, EmailValidator):
|
||||
|
@ -406,9 +406,15 @@ class AutoSchema(ViewInspector):
|
|||
if isinstance(v, RegexValidator):
|
||||
schema['pattern'] = v.regex.pattern
|
||||
elif isinstance(v, MaxLengthValidator):
|
||||
schema['maxLength'] = v.limit_value
|
||||
attr_name = 'maxLength'
|
||||
if isinstance(field, serializers.ListField):
|
||||
attr_name = 'maxItems'
|
||||
schema[attr_name] = v.limit_value
|
||||
elif isinstance(v, MinLengthValidator):
|
||||
schema['minLength'] = v.limit_value
|
||||
attr_name = 'minLength'
|
||||
if isinstance(field, serializers.ListField):
|
||||
attr_name = 'minItems'
|
||||
schema[attr_name] = v.limit_value
|
||||
elif isinstance(v, MaxValueValidator):
|
||||
schema['maximum'] = v.limit_value
|
||||
elif isinstance(v, MinValueValidator):
|
||||
|
@ -423,6 +429,13 @@ class AutoSchema(ViewInspector):
|
|||
schema['maximum'] = int(digits * '9') + 1
|
||||
schema['minimum'] = -schema['maximum']
|
||||
|
||||
def _get_pagninator(self):
|
||||
pagination_class = getattr(self.view, 'pagination_class', None)
|
||||
if pagination_class:
|
||||
return pagination_class()
|
||||
|
||||
return None
|
||||
|
||||
def _get_serializer(self, method, path):
|
||||
view = self.view
|
||||
|
||||
|
@ -489,6 +502,9 @@ class AutoSchema(ViewInspector):
|
|||
'type': 'array',
|
||||
'items': item_schema,
|
||||
}
|
||||
paginator = self._get_pagninator()
|
||||
if paginator:
|
||||
response_schema = paginator.get_paginated_response_schema(response_schema)
|
||||
else:
|
||||
response_schema = item_schema
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ class TestOperationIntrospection(TestCase):
|
|||
|
||||
operation = inspector.get_operation(path, method)
|
||||
assert operation == {
|
||||
'operationId': 'ListExamples',
|
||||
'operationId': 'listExamples',
|
||||
'parameters': [],
|
||||
'responses': {
|
||||
'200': {
|
||||
|
@ -264,6 +264,58 @@ class TestOperationIntrospection(TestCase):
|
|||
},
|
||||
}
|
||||
|
||||
def test_paginated_list_response_body_generation(self):
|
||||
"""Test that pagination properties are added for a paginated list view."""
|
||||
path = '/'
|
||||
method = 'GET'
|
||||
|
||||
class Pagination(pagination.BasePagination):
|
||||
def get_paginated_response_schema(self, schema):
|
||||
return {
|
||||
'type': 'object',
|
||||
'item': schema,
|
||||
}
|
||||
|
||||
class ItemSerializer(serializers.Serializer):
|
||||
text = serializers.CharField()
|
||||
|
||||
class View(generics.GenericAPIView):
|
||||
serializer_class = ItemSerializer
|
||||
pagination_class = Pagination
|
||||
|
||||
view = create_view(
|
||||
View,
|
||||
method,
|
||||
create_request(path),
|
||||
)
|
||||
inspector = AutoSchema()
|
||||
inspector.view = view
|
||||
|
||||
responses = inspector._get_responses(path, method)
|
||||
assert responses == {
|
||||
'200': {
|
||||
'description': '',
|
||||
'content': {
|
||||
'application/json': {
|
||||
'schema': {
|
||||
'type': 'object',
|
||||
'item': {
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'properties': {
|
||||
'text': {
|
||||
'type': 'string',
|
||||
},
|
||||
},
|
||||
'required': ['text'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def test_delete_response_body_generation(self):
|
||||
"""Test that a view's delete method generates a proper response body schema."""
|
||||
path = '/{id}/'
|
||||
|
@ -288,15 +340,27 @@ class TestOperationIntrospection(TestCase):
|
|||
}
|
||||
|
||||
def test_retrieve_response_body_generation(self):
|
||||
"""Test that a list of properties is returned for retrieve item views."""
|
||||
"""
|
||||
Test that a list of properties is returned for retrieve item views.
|
||||
|
||||
Pagination properties should not be added as the view represents a single item.
|
||||
"""
|
||||
path = '/{id}/'
|
||||
method = 'GET'
|
||||
|
||||
class Pagination(pagination.BasePagination):
|
||||
def get_paginated_response_schema(self, schema):
|
||||
return {
|
||||
'type': 'object',
|
||||
'item': schema,
|
||||
}
|
||||
|
||||
class ItemSerializer(serializers.Serializer):
|
||||
text = serializers.CharField()
|
||||
|
||||
class View(generics.GenericAPIView):
|
||||
serializer_class = ItemSerializer
|
||||
pagination_class = Pagination
|
||||
|
||||
view = create_view(
|
||||
View,
|
||||
|
@ -338,7 +402,7 @@ class TestOperationIntrospection(TestCase):
|
|||
inspector.view = view
|
||||
|
||||
operationId = inspector._get_operation_id(path, method)
|
||||
assert operationId == 'ListExamples'
|
||||
assert operationId == 'listExamples'
|
||||
|
||||
def test_repeat_operation_ids(self):
|
||||
router = routers.SimpleRouter()
|
||||
|
@ -395,6 +459,9 @@ class TestOperationIntrospection(TestCase):
|
|||
assert properties['string']['minLength'] == 2
|
||||
assert properties['string']['maxLength'] == 10
|
||||
|
||||
assert properties['lst']['minItems'] == 2
|
||||
assert properties['lst']['maxItems'] == 10
|
||||
|
||||
assert properties['regex']['pattern'] == r'[ABC]12{3}'
|
||||
assert properties['regex']['description'] == 'must have an A, B, or C followed by 1222'
|
||||
|
||||
|
@ -489,3 +556,17 @@ class TestGenerator(TestCase):
|
|||
|
||||
assert 'openapi' in schema
|
||||
assert 'paths' in schema
|
||||
|
||||
def test_schema_information(self):
|
||||
"""Construction of the top level dictionary."""
|
||||
patterns = [
|
||||
url(r'^example/?$', views.ExampleListView.as_view()),
|
||||
]
|
||||
generator = SchemaGenerator(patterns=patterns, title='My title', version='1.2.3', description='My description')
|
||||
|
||||
request = create_request('/')
|
||||
schema = generator.get_schema(request=request)
|
||||
|
||||
assert schema['info']['title'] == 'My title'
|
||||
assert schema['info']['version'] == '1.2.3'
|
||||
assert schema['info']['description'] == 'My description'
|
||||
|
|
|
@ -85,6 +85,12 @@ class ExampleValidatedSerializer(serializers.Serializer):
|
|||
),
|
||||
help_text='must have an A, B, or C followed by 1222'
|
||||
)
|
||||
lst = serializers.ListField(
|
||||
validators=(
|
||||
MaxLengthValidator(limit_value=10),
|
||||
MinLengthValidator(limit_value=2),
|
||||
)
|
||||
)
|
||||
decimal1 = serializers.DecimalField(max_digits=6, decimal_places=2)
|
||||
decimal2 = serializers.DecimalField(max_digits=5, decimal_places=0,
|
||||
validators=(DecimalValidator(max_digits=17, decimal_places=4),))
|
||||
|
|
|
@ -259,6 +259,37 @@ class TestPageNumberPagination:
|
|||
with pytest.raises(exceptions.NotFound):
|
||||
self.paginate_queryset(request)
|
||||
|
||||
def test_get_paginated_response_schema(self):
|
||||
unpaginated_schema = {
|
||||
'type': 'object',
|
||||
'item': {
|
||||
'properties': {
|
||||
'test-property': {
|
||||
'type': 'integer',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert self.pagination.get_paginated_response_schema(unpaginated_schema) == {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'count': {
|
||||
'type': 'integer',
|
||||
'example': 123,
|
||||
},
|
||||
'next': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'previous': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'results': unpaginated_schema,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class TestPageNumberPaginationOverride:
|
||||
"""
|
||||
|
@ -535,6 +566,37 @@ class TestLimitOffset:
|
|||
assert content.get('next') == next_url
|
||||
assert content.get('previous') == prev_url
|
||||
|
||||
def test_get_paginated_response_schema(self):
|
||||
unpaginated_schema = {
|
||||
'type': 'object',
|
||||
'item': {
|
||||
'properties': {
|
||||
'test-property': {
|
||||
'type': 'integer',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert self.pagination.get_paginated_response_schema(unpaginated_schema) == {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'count': {
|
||||
'type': 'integer',
|
||||
'example': 123,
|
||||
},
|
||||
'next': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'previous': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'results': unpaginated_schema,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class CursorPaginationTestsMixin:
|
||||
|
||||
|
@ -834,6 +896,33 @@ class CursorPaginationTestsMixin:
|
|||
assert current == [1, 1, 1, 1, 1]
|
||||
assert next == [1, 2, 3, 4, 4]
|
||||
|
||||
def test_get_paginated_response_schema(self):
|
||||
unpaginated_schema = {
|
||||
'type': 'object',
|
||||
'item': {
|
||||
'properties': {
|
||||
'test-property': {
|
||||
'type': 'integer',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert self.pagination.get_paginated_response_schema(unpaginated_schema) == {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'next': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'previous': {
|
||||
'type': 'string',
|
||||
'nullable': True,
|
||||
},
|
||||
'results': unpaginated_schema,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class TestCursorPagination(CursorPaginationTestsMixin):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue
Block a user