mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-28 00:49:49 +03:00
Merge branch 'master' of https://github.com/encode/django-rest-framework
This commit is contained in:
commit
b887b14b60
|
@ -17,11 +17,16 @@ other cache decorators such as [`cache_page`][page] and
|
||||||
[`vary_on_cookie`][cookie].
|
[`vary_on_cookie`][cookie].
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views.decorators.cache import cache_page
|
||||||
|
from django.views.decorators.vary import vary_on_cookie
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
|
||||||
class UserViewSet(viewsets.Viewset):
|
|
||||||
|
class UserViewSet(viewsets.ViewSet):
|
||||||
|
|
||||||
# Cache requested url for each user for 2 hours
|
# Cache requested url for each user for 2 hours
|
||||||
@method_decorator(cache_page(60*60*2))
|
@method_decorator(cache_page(60*60*2))
|
||||||
|
@ -32,6 +37,7 @@ class UserViewSet(viewsets.Viewset):
|
||||||
}
|
}
|
||||||
return Response(content)
|
return Response(content)
|
||||||
|
|
||||||
|
|
||||||
class PostView(APIView):
|
class PostView(APIView):
|
||||||
|
|
||||||
# Cache page for the requested url
|
# Cache page for the requested url
|
||||||
|
|
|
@ -713,7 +713,7 @@ the coordinate pair:
|
||||||
fields = ['label', 'coordinates']
|
fields = ['label', 'coordinates']
|
||||||
|
|
||||||
Note that this example doesn't handle validation. Partly for that reason, in a
|
Note that this example doesn't handle validation. Partly for that reason, in a
|
||||||
real project, the coordinate nesting might be better handled with a nested serialiser
|
real project, the coordinate nesting might be better handled with a nested serializer
|
||||||
using `source='*'`, with two `IntegerField` instances, each with their own `source`
|
using `source='*'`, with two `IntegerField` instances, each with their own `source`
|
||||||
pointing to the relevant field.
|
pointing to the relevant field.
|
||||||
|
|
||||||
|
@ -746,7 +746,7 @@ suitable for updating our target object. With `source='*'`, the return from
|
||||||
('y_coordinate', 4),
|
('y_coordinate', 4),
|
||||||
('x_coordinate', 3)])
|
('x_coordinate', 3)])
|
||||||
|
|
||||||
For completeness lets do the same thing again but with the nested serialiser
|
For completeness lets do the same thing again but with the nested serializer
|
||||||
approach suggested above:
|
approach suggested above:
|
||||||
|
|
||||||
class NestedCoordinateSerializer(serializers.Serializer):
|
class NestedCoordinateSerializer(serializers.Serializer):
|
||||||
|
@ -768,14 +768,14 @@ declarations. It's our `NestedCoordinateSerializer` that takes `source='*'`.
|
||||||
Our new `DataPointSerializer` exhibits the same behaviour as the custom field
|
Our new `DataPointSerializer` exhibits the same behaviour as the custom field
|
||||||
approach.
|
approach.
|
||||||
|
|
||||||
Serialising:
|
Serializing:
|
||||||
|
|
||||||
>>> out_serializer = DataPointSerializer(instance)
|
>>> out_serializer = DataPointSerializer(instance)
|
||||||
>>> out_serializer.data
|
>>> out_serializer.data
|
||||||
ReturnDict([('label', 'testing'),
|
ReturnDict([('label', 'testing'),
|
||||||
('coordinates', OrderedDict([('x', 1), ('y', 2)]))])
|
('coordinates', OrderedDict([('x', 1), ('y', 2)]))])
|
||||||
|
|
||||||
Deserialising:
|
Deserializing:
|
||||||
|
|
||||||
>>> in_serializer = DataPointSerializer(data=data)
|
>>> in_serializer = DataPointSerializer(data=data)
|
||||||
>>> in_serializer.is_valid()
|
>>> in_serializer.is_valid()
|
||||||
|
@ -802,8 +802,8 @@ But we also get the built-in validation for free:
|
||||||
{'x': ['A valid integer is required.'],
|
{'x': ['A valid integer is required.'],
|
||||||
'y': ['A valid integer is required.']})])
|
'y': ['A valid integer is required.']})])
|
||||||
|
|
||||||
For this reason, the nested serialiser approach would be the first to try. You
|
For this reason, the nested serializer approach would be the first to try. You
|
||||||
would use the custom field approach when the nested serialiser becomes infeasible
|
would use the custom field approach when the nested serializer becomes infeasible
|
||||||
or overly complex.
|
or overly complex.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -378,10 +378,6 @@ If you need to generic PUT-as-create behavior you may want to include something
|
||||||
|
|
||||||
The following third party packages provide additional generic view implementations.
|
The following third party packages provide additional generic view implementations.
|
||||||
|
|
||||||
## Django REST Framework bulk
|
|
||||||
|
|
||||||
The [django-rest-framework-bulk package][django-rest-framework-bulk] implements generic view mixins as well as some common concrete generic views to allow to apply bulk operations via API requests.
|
|
||||||
|
|
||||||
## Django Rest Multiple Models
|
## Django Rest Multiple Models
|
||||||
|
|
||||||
[Django Rest Multiple Models][django-rest-multiple-models] provides a generic view (and mixin) for sending multiple serialized models and/or querysets via a single API request.
|
[Django Rest Multiple Models][django-rest-multiple-models] provides a generic view (and mixin) for sending multiple serialized models and/or querysets via a single API request.
|
||||||
|
@ -394,5 +390,4 @@ The [django-rest-framework-bulk package][django-rest-framework-bulk] implements
|
||||||
[RetrieveModelMixin]: #retrievemodelmixin
|
[RetrieveModelMixin]: #retrievemodelmixin
|
||||||
[UpdateModelMixin]: #updatemodelmixin
|
[UpdateModelMixin]: #updatemodelmixin
|
||||||
[DestroyModelMixin]: #destroymodelmixin
|
[DestroyModelMixin]: #destroymodelmixin
|
||||||
[django-rest-framework-bulk]: https://github.com/miki725/django-rest-framework-bulk
|
|
||||||
[django-rest-multiple-models]: https://github.com/MattBroach/DjangoRestMultipleModels
|
[django-rest-multiple-models]: https://github.com/MattBroach/DjangoRestMultipleModels
|
||||||
|
|
|
@ -49,7 +49,7 @@ If a client sends a request with a content-type that cannot be parsed then a `Un
|
||||||
|
|
||||||
# Content negotiation
|
# Content negotiation
|
||||||
|
|
||||||
The request exposes some properties that allow you to determine the result of the content negotiation stage. This allows you to implement behaviour such as selecting a different serialisation schemes for different media types.
|
The request exposes some properties that allow you to determine the result of the content negotiation stage. This allows you to implement behaviour such as selecting a different serialization schemes for different media types.
|
||||||
|
|
||||||
## .accepted_renderer
|
## .accepted_renderer
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,6 @@ The `get_schema_view()` helper takes the following keyword arguments:
|
||||||
* `renderer_classes`: May be used to pass the set of renderer classes that can
|
* `renderer_classes`: May be used to pass the set of renderer classes that can
|
||||||
be used to render the API root endpoint.
|
be used to render the API root endpoint.
|
||||||
|
|
||||||
|
|
||||||
## Customizing Schema Generation
|
## Customizing Schema Generation
|
||||||
|
|
||||||
You may customize schema generation at the level of the schema as a whole, or
|
You may customize schema generation at the level of the schema as a whole, or
|
||||||
|
@ -155,7 +154,7 @@ Returns a dictionary that represents the OpenAPI schema:
|
||||||
The `request` argument is optional, and may be used if you want to apply
|
The `request` argument is optional, and may be used if you want to apply
|
||||||
per-user permissions to the resulting schema generation.
|
per-user permissions to the resulting schema generation.
|
||||||
|
|
||||||
This is a good point to override if you want to customise the generated
|
This is a good point to override if you want to customize the generated
|
||||||
dictionary, for example to add custom
|
dictionary, for example to add custom
|
||||||
[specification extensions][openapi-specification-extensions].
|
[specification extensions][openapi-specification-extensions].
|
||||||
|
|
||||||
|
@ -177,21 +176,20 @@ for each view, allowed method, and path.
|
||||||
**Note**: For basic `APIView` subclasses, default introspection is essentially
|
**Note**: For basic `APIView` subclasses, default introspection is essentially
|
||||||
limited to the URL kwarg path parameters. For `GenericAPIView`
|
limited to the URL kwarg path parameters. For `GenericAPIView`
|
||||||
subclasses, which includes all the provided class based views, `AutoSchema` will
|
subclasses, which includes all the provided class based views, `AutoSchema` will
|
||||||
attempt to introspect serialiser, pagination and filter fields, as well as
|
attempt to introspect serializer, pagination and filter fields, as well as
|
||||||
provide richer path field descriptions. (The key hooks here are the relevant
|
provide richer path field descriptions. (The key hooks here are the relevant
|
||||||
`GenericAPIView` attributes and methods: `get_serializer`, `pagination_class`,
|
`GenericAPIView` attributes and methods: `get_serializer`, `pagination_class`,
|
||||||
`filter_backends` and so on.)
|
`filter_backends` and so on.)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
In order to customise the operation generation, you should provide an `AutoSchema` subclass, overriding `get_operation()` as you need:
|
In order to customize the operation generation, you should provide an `AutoSchema` subclass, overriding `get_operation()` as you need:
|
||||||
|
|
||||||
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.schemas.openapi import AutoSchema
|
from rest_framework.schemas.openapi import AutoSchema
|
||||||
|
|
||||||
class CustomSchema(AutoSchema):
|
class CustomSchema(AutoSchema):
|
||||||
def get_link(...):
|
def get_operation(...):
|
||||||
# Implement custom introspection here (or in other sub-methods)
|
# Implement custom introspection here (or in other sub-methods)
|
||||||
|
|
||||||
class CustomView(APIView):
|
class CustomView(APIView):
|
||||||
|
@ -219,4 +217,4 @@ project you may adjust `settings.DEFAULT_SCHEMA_CLASS` appropriately.
|
||||||
|
|
||||||
[openapi]: https://github.com/OAI/OpenAPI-Specification
|
[openapi]: https://github.com/OAI/OpenAPI-Specification
|
||||||
[openapi-specification-extensions]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification-extensions
|
[openapi-specification-extensions]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification-extensions
|
||||||
[openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject
|
[openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject
|
||||||
|
|
|
@ -222,11 +222,11 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10.
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
serializer.save(owner=self.request.user)
|
serializer.save(owner=self.request.user)
|
||||||
|
|
||||||
Alternatively you may override `save()` or `create()` or `update()` on the serialiser as appropriate.
|
Alternatively you may override `save()` or `create()` or `update()` on the serializer as appropriate.
|
||||||
|
|
||||||
* Correct allow_null behaviour when required=False [#5888][gh5888]
|
* Correct allow_null behaviour when required=False [#5888][gh5888]
|
||||||
|
|
||||||
Without an explicit `default`, `allow_null` implies a default of `null` for outgoing serialisation. Previously such
|
Without an explicit `default`, `allow_null` implies a default of `null` for outgoing serialization. Previously such
|
||||||
fields were being skipped when read-only or otherwise not required.
|
fields were being skipped when read-only or otherwise not required.
|
||||||
|
|
||||||
**Possible backwards compatibility break** if you were relying on such fields being excluded from the outgoing
|
**Possible backwards compatibility break** if you were relying on such fields being excluded from the outgoing
|
||||||
|
@ -464,7 +464,7 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10.
|
||||||
* Deprecated `exclude_from_schema` on `APIView` and `api_view` decorator. Set `schema = None` or `@schema(None)` as appropriate. [#5422][gh5422]
|
* Deprecated `exclude_from_schema` on `APIView` and `api_view` decorator. Set `schema = None` or `@schema(None)` as appropriate. [#5422][gh5422]
|
||||||
* Timezone-aware `DateTimeField`s now respect active or default `timezone` during serialization, instead of always using UTC. [#5435][gh5435]
|
* Timezone-aware `DateTimeField`s now respect active or default `timezone` during serialization, instead of always using UTC. [#5435][gh5435]
|
||||||
|
|
||||||
Resolves inconsistency whereby instances were serialised with supplied datetime for `create` but UTC for `retrieve`. [#3732][gh3732]
|
Resolves inconsistency whereby instances were serialized with supplied datetime for `create` but UTC for `retrieve`. [#3732][gh3732]
|
||||||
|
|
||||||
**Possible backwards compatibility break** if you were relying on datetime strings being UTC. Have client interpret datetimes or [set default or active timezone (docs)][djangodocs-set-timezone] to UTC if needed.
|
**Possible backwards compatibility break** if you were relying on datetime strings being UTC. Have client interpret datetimes or [set default or active timezone (docs)][djangodocs-set-timezone] to UTC if needed.
|
||||||
|
|
||||||
|
|
|
@ -222,7 +222,6 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
|
|
||||||
### Views
|
### Views
|
||||||
|
|
||||||
* [djangorestframework-bulk][djangorestframework-bulk] - Implements generic view mixins as well as some common concrete generic views to allow to apply bulk operations via API requests.
|
|
||||||
* [django-rest-multiple-models][django-rest-multiple-models] - Provides a generic view (and mixin) for sending multiple serialized models and/or querysets via a single API request.
|
* [django-rest-multiple-models][django-rest-multiple-models] - Provides a generic view (and mixin) for sending multiple serialized models and/or querysets via a single API request.
|
||||||
|
|
||||||
### Routers
|
### Routers
|
||||||
|
@ -254,7 +253,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
### Misc
|
### Misc
|
||||||
|
|
||||||
* [cookiecutter-django-rest][cookiecutter-django-rest] - A cookiecutter template that takes care of the setup and configuration so you can focus on making your REST apis awesome.
|
* [cookiecutter-django-rest][cookiecutter-django-rest] - A cookiecutter template that takes care of the setup and configuration so you can focus on making your REST apis awesome.
|
||||||
* [djangorestrelationalhyperlink][djangorestrelationalhyperlink] - A hyperlinked serialiser that can can be used to alter relationships via hyperlinks, but otherwise like a hyperlink model serializer.
|
* [djangorestrelationalhyperlink][djangorestrelationalhyperlink] - A hyperlinked serializer that can can be used to alter relationships via hyperlinks, but otherwise like a hyperlink model serializer.
|
||||||
* [django-rest-swagger][django-rest-swagger] - An API documentation generator for Swagger UI.
|
* [django-rest-swagger][django-rest-swagger] - An API documentation generator for Swagger UI.
|
||||||
* [django-rest-framework-proxy][django-rest-framework-proxy] - Proxy to redirect incoming request to another API server.
|
* [django-rest-framework-proxy][django-rest-framework-proxy] - Proxy to redirect incoming request to another API server.
|
||||||
* [gaiarestframework][gaiarestframework] - Utils for django-rest-framework
|
* [gaiarestframework][gaiarestframework] - Utils for django-rest-framework
|
||||||
|
@ -305,7 +304,6 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
|
||||||
[djangorestframework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore
|
[djangorestframework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore
|
||||||
[drf-compound-fields]: https://github.com/estebistec/drf-compound-fields
|
[drf-compound-fields]: https://github.com/estebistec/drf-compound-fields
|
||||||
[django-extra-fields]: https://github.com/Hipo/drf-extra-fields
|
[django-extra-fields]: https://github.com/Hipo/drf-extra-fields
|
||||||
[djangorestframework-bulk]: https://github.com/miki725/django-rest-framework-bulk
|
|
||||||
[django-rest-multiple-models]: https://github.com/MattBroach/DjangoRestMultipleModels
|
[django-rest-multiple-models]: https://github.com/MattBroach/DjangoRestMultipleModels
|
||||||
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
|
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
|
||||||
[wq.db.rest]: https://wq.io/docs/about-rest
|
[wq.db.rest]: https://wq.io/docs/about-rest
|
||||||
|
|
|
@ -191,7 +191,7 @@ each view, allowed method and path.)
|
||||||
**Note**: For basic `APIView` subclasses, default introspection is essentially
|
**Note**: For basic `APIView` subclasses, default introspection is essentially
|
||||||
limited to the URL kwarg path parameters. For `GenericAPIView`
|
limited to the URL kwarg path parameters. For `GenericAPIView`
|
||||||
subclasses, which includes all the provided class based views, `AutoSchema` will
|
subclasses, which includes all the provided class based views, `AutoSchema` will
|
||||||
attempt to introspect serialiser, pagination and filter fields, as well as
|
attempt to introspect serializer, pagination and filter fields, as well as
|
||||||
provide richer path field descriptions. (The key hooks here are the relevant
|
provide richer path field descriptions. (The key hooks here are the relevant
|
||||||
`GenericAPIView` attributes and methods: `get_serializer`, `pagination_class`,
|
`GenericAPIView` attributes and methods: `get_serializer`, `pagination_class`,
|
||||||
`filter_backends` and so on.)
|
`filter_backends` and so on.)
|
||||||
|
|
|
@ -10,7 +10,7 @@ ______ _____ _____ _____ __
|
||||||
__title__ = 'Django REST framework'
|
__title__ = 'Django REST framework'
|
||||||
__version__ = '3.10.3'
|
__version__ = '3.10.3'
|
||||||
__author__ = 'Tom Christie'
|
__author__ = 'Tom Christie'
|
||||||
__license__ = 'BSD 2-Clause'
|
__license__ = 'BSD 3-Clause'
|
||||||
__copyright__ = 'Copyright 2011-2019 Encode OSS Ltd'
|
__copyright__ = 'Copyright 2011-2019 Encode OSS Ltd'
|
||||||
|
|
||||||
# Version synonym
|
# Version synonym
|
||||||
|
|
|
@ -209,7 +209,7 @@ class AutoSchema(ViewInspector):
|
||||||
if not is_list_view(path, method, view):
|
if not is_list_view(path, method, view):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
paginator = self._get_pagninator()
|
paginator = self._get_paginator()
|
||||||
if not paginator:
|
if not paginator:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -344,6 +344,7 @@ class AutoSchema(ViewInspector):
|
||||||
serializers.BooleanField: 'boolean',
|
serializers.BooleanField: 'boolean',
|
||||||
serializers.JSONField: 'object',
|
serializers.JSONField: 'object',
|
||||||
serializers.DictField: 'object',
|
serializers.DictField: 'object',
|
||||||
|
serializers.HStoreField: 'object',
|
||||||
}
|
}
|
||||||
return {'type': FIELD_CLASS_SCHEMA_TYPE.get(field.__class__, 'string')}
|
return {'type': FIELD_CLASS_SCHEMA_TYPE.get(field.__class__, 'string')}
|
||||||
|
|
||||||
|
@ -429,7 +430,7 @@ class AutoSchema(ViewInspector):
|
||||||
schema['maximum'] = int(digits * '9') + 1
|
schema['maximum'] = int(digits * '9') + 1
|
||||||
schema['minimum'] = -schema['maximum']
|
schema['minimum'] = -schema['maximum']
|
||||||
|
|
||||||
def _get_pagninator(self):
|
def _get_paginator(self):
|
||||||
pagination_class = getattr(self.view, 'pagination_class', None)
|
pagination_class = getattr(self.view, 'pagination_class', None)
|
||||||
if pagination_class:
|
if pagination_class:
|
||||||
return pagination_class()
|
return pagination_class()
|
||||||
|
@ -502,7 +503,7 @@ class AutoSchema(ViewInspector):
|
||||||
'type': 'array',
|
'type': 'array',
|
||||||
'items': item_schema,
|
'items': item_schema,
|
||||||
}
|
}
|
||||||
paginator = self._get_pagninator()
|
paginator = self._get_paginator()
|
||||||
if paginator:
|
if paginator:
|
||||||
response_schema = paginator.get_paginated_response_schema(response_schema)
|
response_schema = paginator.get_paginated_response_schema(response_schema)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -462,6 +462,22 @@ class TestOperationIntrospection(TestCase):
|
||||||
assert properties['date']['format'] == 'date'
|
assert properties['date']['format'] == 'date'
|
||||||
assert properties['datetime']['format'] == 'date-time'
|
assert properties['datetime']['format'] == 'date-time'
|
||||||
|
|
||||||
|
def test_serializer_hstorefield(self):
|
||||||
|
path = '/'
|
||||||
|
method = 'GET'
|
||||||
|
view = create_view(
|
||||||
|
views.ExampleGenericAPIView,
|
||||||
|
method,
|
||||||
|
create_request(path),
|
||||||
|
)
|
||||||
|
inspector = AutoSchema()
|
||||||
|
inspector.view = view
|
||||||
|
|
||||||
|
responses = inspector._get_responses(path, method)
|
||||||
|
response_schema = responses['200']['content']['application/json']['schema']
|
||||||
|
properties = response_schema['items']['properties']
|
||||||
|
assert properties['hstore']['type'] == 'object'
|
||||||
|
|
||||||
def test_serializer_validators(self):
|
def test_serializer_validators(self):
|
||||||
path = '/'
|
path = '/'
|
||||||
method = 'GET'
|
method = 'GET'
|
||||||
|
|
|
@ -33,6 +33,7 @@ class ExampleDetailView(APIView):
|
||||||
class ExampleSerializer(serializers.Serializer):
|
class ExampleSerializer(serializers.Serializer):
|
||||||
date = serializers.DateField()
|
date = serializers.DateField()
|
||||||
datetime = serializers.DateTimeField()
|
datetime = serializers.DateTimeField()
|
||||||
|
hstore = serializers.HStoreField()
|
||||||
|
|
||||||
|
|
||||||
class ExampleGenericAPIView(generics.GenericAPIView):
|
class ExampleGenericAPIView(generics.GenericAPIView):
|
||||||
|
|
|
@ -555,7 +555,7 @@ class TestDefaultOutput:
|
||||||
bar = serializers.CharField(source='foo.bar', allow_null=True)
|
bar = serializers.CharField(source='foo.bar', allow_null=True)
|
||||||
optional = serializers.CharField(required=False, allow_null=True)
|
optional = serializers.CharField(required=False, allow_null=True)
|
||||||
|
|
||||||
# allow_null=True should imply default=None when serialising:
|
# allow_null=True should imply default=None when serializing:
|
||||||
assert Serializer({'foo': None}).data == {'foo': None, 'bar': None, 'optional': None, }
|
assert Serializer({'foo': None}).data == {'foo': None, 'bar': None, 'optional': None, }
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user