This commit is contained in:
Asif Saif Uddin (Auvi) 2019-10-24 10:06:48 +06:00
commit fc418fe051
36 changed files with 157 additions and 66 deletions

View File

@ -21,11 +21,13 @@ matrix:
- { python: "3.7", env: DJANGO=2.2 } - { python: "3.7", env: DJANGO=2.2 }
- { python: "3.7", env: DJANGO=master } - { python: "3.7", env: DJANGO=master }
- { python: "3.7", env: TOXENV=base } - { python: "3.8", env: DJANGO=master }
- { python: "3.7", env: TOXENV=lint }
- { python: "3.7", env: TOXENV=docs }
- python: "3.7" - { python: "3.8", env: TOXENV=base }
- { python: "3.8", env: TOXENV=lint }
- { python: "3.8", env: TOXENV=docs }
- python: "3.8"
env: TOXENV=dist env: TOXENV=dist
script: script:
- python setup.py bdist_wheel - python setup.py bdist_wheel

View File

@ -26,8 +26,9 @@ The initial aim is to provide a single full-time position on REST framework.
[![][kloudless-img]][kloudless-url] [![][kloudless-img]][kloudless-url]
[![][esg-img]][esg-url] [![][esg-img]][esg-url]
[![][lightson-img]][lightson-url] [![][lightson-img]][lightson-url]
[![][retool-img]][retool-url]
Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [Cadre][cadre-url], [Kloudless][kloudless-url], [ESG][esg-url], and [Lights On Software][lightson-url]. Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [Cadre][cadre-url], [Kloudless][kloudless-url], [ESG][esg-url], [Lights On Software][lightson-url], and [Retool][retool-url].
--- ---
@ -199,6 +200,7 @@ Please see the [security policy][security-policy].
[kloudless-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/kloudless-readme.png [kloudless-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/kloudless-readme.png
[esg-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/esg-readme.png [esg-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/esg-readme.png
[lightson-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/lightson-readme.png [lightson-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/lightson-readme.png
[retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/retool-readme.png
[sentry-url]: https://getsentry.com/welcome/ [sentry-url]: https://getsentry.com/welcome/
[stream-url]: https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf [stream-url]: https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf
@ -207,6 +209,7 @@ Please see the [security policy][security-policy].
[kloudless-url]: https://hubs.ly/H0f30Lf0 [kloudless-url]: https://hubs.ly/H0f30Lf0
[esg-url]: https://software.esg-usa.com/ [esg-url]: https://software.esg-usa.com/
[lightson-url]: https://lightsonsoftware.com [lightson-url]: https://lightsonsoftware.com
[retool-url]: https://retool.com/?utm_source=djangorest&utm_medium=sponsorship
[oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth [oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
[oauth2-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit [oauth2-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit

View File

@ -52,7 +52,7 @@ The `default` is not applied during partial update operations. In the partial up
May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `set_context` method, that will be called each time before getting the value with the field instance as only argument. This works the same way as for [validators](validators.md#using-set_context). May be set to a function or other callable, in which case the value will be evaluated each time it is used. When called, it will receive no arguments. If the callable has a `set_context` method, that will be called each time before getting the value with the field instance as only argument. This works the same way as for [validators](validators.md#using-set_context).
When serializing the instance, default will be used if the the object attribute or dictionary key is not present in the instance. When serializing the instance, default will be used if the object attribute or dictionary key is not present in the instance.
Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error. Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error.

View File

@ -1,6 +1,6 @@
--- ---
source: source:
- schemas.py - schemas
--- ---
# Schema # Schema
@ -90,6 +90,7 @@ The `get_schema_view()` helper takes the following keyword arguments:
url='https://www.example.org/api/', url='https://www.example.org/api/',
urlconf='myproject.urls' urlconf='myproject.urls'
) )
* `patterns`: List of url patterns to limit the schema introspection to. If you * `patterns`: List of url patterns to limit the schema introspection to. If you
only want the `myproject.api` urls to be exposed in the schema: only want the `myproject.api` urls to be exposed in the schema:

View File

@ -887,10 +887,10 @@ To implement a read-only serializer using the `BaseSerializer` class, we just ne
It's simple to create a read-only serializer for converting `HighScore` instances into primitive data types. It's simple to create a read-only serializer for converting `HighScore` instances into primitive data types.
class HighScoreSerializer(serializers.BaseSerializer): class HighScoreSerializer(serializers.BaseSerializer):
def to_representation(self, obj): def to_representation(self, instance):
return { return {
'score': obj.score, 'score': instance.score,
'player_name': obj.player_name 'player_name': instance.player_name
} }
We can now use this class to serialize single `HighScore` instances: We can now use this class to serialize single `HighScore` instances:
@ -945,10 +945,10 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd
'player_name': player_name 'player_name': player_name
} }
def to_representation(self, obj): def to_representation(self, instance):
return { return {
'score': obj.score, 'score': instance.score,
'player_name': obj.player_name 'player_name': instance.player_name
} }
def create(self, validated_data): def create(self, validated_data):
@ -965,10 +965,10 @@ The following class is an example of a generic serializer that can handle coerci
A read-only serializer that coerces arbitrary complex objects A read-only serializer that coerces arbitrary complex objects
into primitive representations. into primitive representations.
""" """
def to_representation(self, obj): def to_representation(self, instance):
output = {} output = {}
for attribute_name in dir(obj): for attribute_name in dir(instance):
attribute = getattr(obj, attribute_name) attribute = getattr(instance, attribute_name)
if attribute_name.startswith('_'): if attribute_name.startswith('_'):
# Ignore private attributes. # Ignore private attributes.
pass pass
@ -1010,11 +1010,11 @@ Some reasons this might be useful include...
The signatures for these methods are as follows: The signatures for these methods are as follows:
#### `.to_representation(self, obj)` #### `.to_representation(self, instance)`
Takes the object instance that requires serialization, and should return a primitive representation. Typically this means returning a structure of built-in Python datatypes. The exact types that can be handled will depend on the render classes you have configured for your API. Takes the object instance that requires serialization, and should return a primitive representation. Typically this means returning a structure of built-in Python datatypes. The exact types that can be handled will depend on the render classes you have configured for your API.
May be overridden in order modify the representation style. For example: May be overridden in order to modify the representation style. For example:
def to_representation(self, instance): def to_representation(self, instance):
"""Convert `username` to lowercase.""" """Convert `username` to lowercase."""

View File

@ -218,7 +218,7 @@ in the `.validate()` method, or else in the view.
For example: For example:
class BillingRecordSerializer(serializers.ModelSerializer): class BillingRecordSerializer(serializers.ModelSerializer):
def validate(self, data): def validate(self, attrs):
# Apply custom validation either here, or in the view. # Apply custom validation either here, or in the view.
class Meta: class Meta:

View File

@ -84,7 +84,7 @@ urlpatterns = [
### Customization ### Customization
For customizations that you want to apply across the the entire API, you can subclass `rest_framework.schemas.openapi.SchemaGenerator` and provide it as an argument For customizations that you want to apply across the entire API, you can subclass `rest_framework.schemas.openapi.SchemaGenerator` and provide it as an argument
to the `generateschema` command or `get_schema_view()` helper function. to the `generateschema` command or `get_schema_view()` helper function.
For specific per-view customizations, you can subclass `AutoSchema`, For specific per-view customizations, you can subclass `AutoSchema`,

View File

@ -195,7 +195,6 @@ If `@tomchristie` ceases to participate in the project then `@j4mie` has respons
The following issues still need to be addressed: The following issues still need to be addressed:
* [Consider moving the repo into a proper GitHub organization][github-org].
* Ensure `@jamie` has back-up access to the `django-rest-framework.org` domain setup and admin. * Ensure `@jamie` has back-up access to the `django-rest-framework.org` domain setup and admin.
* Document ownership of the [live example][sandbox] API. * Document ownership of the [live example][sandbox] API.
* Document ownership of the [mailing list][mailing-list] and IRC channel. * Document ownership of the [mailing list][mailing-list] and IRC channel.
@ -206,6 +205,5 @@ The following issues still need to be addressed:
[transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/ [transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/
[transifex-client]: https://pypi.org/project/transifex-client/ [transifex-client]: https://pypi.org/project/transifex-client/
[translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations [translation-memory]: http://docs.transifex.com/guides/tm#let-tm-automatically-populate-translations
[github-org]: https://github.com/encode/django-rest-framework/issues/2162
[sandbox]: https://restframework.herokuapp.com/ [sandbox]: https://restframework.herokuapp.com/
[mailing-list]: https://groups.google.com/forum/#!forum/django-rest-framework [mailing-list]: https://groups.google.com/forum/#!forum/django-rest-framework

View File

@ -211,6 +211,8 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [djangorestframework-queryfields][djangorestframework-queryfields] - Serializer mixin allowing clients to control which fields will be sent in the API response. * [djangorestframework-queryfields][djangorestframework-queryfields] - Serializer mixin allowing clients to control which fields will be sent in the API response.
* [drf-flex-fields][drf-flex-fields] - Serializer providing dynamic field expansion and sparse field sets via URL parameters. * [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. * [drf-action-serializer][drf-action-serializer] - Serializer providing per-action fields config for use with ViewSets to prevent having to write multiple serializers.
* [djangorestframework-dataclasses][djangorestframework-dataclasses] - Serializer providing automatic field generation for Python dataclasses, like the built-in ModelSerializer does for models.
* [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, uses GraphQL like syntax, supports read and write on both flat and nested fields).
### Serializer fields ### Serializer fields
@ -348,5 +350,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[drf-access-policy]: https://github.com/rsinger86/drf-access-policy [drf-access-policy]: https://github.com/rsinger86/drf-access-policy
[drf-flex-fields]: https://github.com/rsinger86/drf-flex-fields [drf-flex-fields]: https://github.com/rsinger86/drf-flex-fields
[drf-action-serializer]: https://github.com/gregschmit/drf-action-serializer [drf-action-serializer]: https://github.com/gregschmit/drf-action-serializer
[djangorestframework-dataclasses]: https://github.com/oxan/djangorestframework-dataclasses
[django-restql]: https://github.com/yezyilomo/django-restql
[djangorestframework-mvt]: https://github.com/corteva/djangorestframework-mvt [djangorestframework-mvt]: https://github.com/corteva/djangorestframework-mvt
[django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian [django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -73,10 +73,11 @@ continued development by **[signing up for a paid plan][funding]**.
<li><a href="https://cadre.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/cadre.png)">Cadre</a></li> <li><a href="https://cadre.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/cadre.png)">Cadre</a></li>
<li><a href="https://hubs.ly/H0f30Lf0" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/kloudless-plus-text.png)">Kloudless</a></li> <li><a href="https://hubs.ly/H0f30Lf0" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/kloudless-plus-text.png)">Kloudless</a></li>
<li><a href="https://lightsonsoftware.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/lightson-dark.png)">Lights On Software</a></li> <li><a href="https://lightsonsoftware.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/lightson-dark.png)">Lights On Software</a></li>
<li><a href="https://retool.com/?utm_source=djangorest&utm_medium=sponsorship" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/retool-sidebar.png)">Retool</a></li>
</ul> </ul>
<div style="clear: both; padding-bottom: 20px;"></div> <div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), and [Lights On Software](https://lightsonsoftware.com).* *Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), and [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship).*
--- ---

View File

@ -74,7 +74,7 @@ See the [Swagger UI documentation][swagger-ui] for advanced usage.
### A minimal example with ReDoc. ### A minimal example with ReDoc.
Assuming you've followed the example from the schemas documentation for routing Assuming you've followed the example from the schemas documentation for routing
a dynamic `SchemaView`, a minimal Django template for using Swagger UI might be a dynamic `SchemaView`, a minimal Django template for using ReDoc might be
this: this:
```html ```html
@ -221,7 +221,7 @@ If the python `Markdown` library is installed, then [markdown syntax][markdown]
[ref]: http://example.com/activating-accounts [ref]: http://example.com/activating-accounts
""" """
Note that when using viewsets the basic docstring is used for all generated views. To provide descriptions for each view, such as for the the list and retrieve views, use docstring sections as described in [Schemas as documentation: Examples][schemas-examples]. Note that when using viewsets the basic docstring is used for all generated views. To provide descriptions for each view, such as for the list and retrieve views, use docstring sections as described in [Schemas as documentation: Examples][schemas-examples].
#### The `OPTIONS` method #### The `OPTIONS` method

View File

@ -1,7 +1,7 @@
# PEP8 code linting, which we run on all commits. # PEP8 code linting, which we run on all commits.
flake8==3.5.0 flake8==3.7.8
flake8-tidy-imports==1.1.0 flake8-tidy-imports==3.0.0
pycodestyle==2.3.1 pycodestyle==2.5.0
# Sort and lint imports # Sort and lint imports
isort==4.3.3 isort==4.3.21

View File

@ -1062,9 +1062,7 @@ class DecimalField(Field):
except decimal.DecimalException: except decimal.DecimalException:
self.fail('invalid') self.fail('invalid')
# Check for NaN. It is the only value that isn't equal to itself, if value.is_nan():
# so we can use this to identify NaN values.
if value != value:
self.fail('invalid') self.fail('invalid')
# Check for infinity and negative infinity. # Check for infinity and negative infinity.
@ -1764,8 +1762,8 @@ class JSONField(Field):
# When HTML form input is used, mark up the input # When HTML form input is used, mark up the input
# as being a JSON string, rather than a JSON primitive. # as being a JSON string, rather than a JSON primitive.
class JSONString(str): class JSONString(str):
def __new__(self, value): def __new__(cls, value):
ret = str.__new__(self, value) ret = str.__new__(cls, value)
ret.is_json_string = True ret.is_json_string = True
return ret return ret
return JSONString(dictionary[self.field_name]) return JSONString(dictionary[self.field_name])

View File

@ -46,8 +46,8 @@ class Hyperlink(str):
We use this for hyperlinked URLs that may render as a named link We use this for hyperlinked URLs that may render as a named link
in some contexts, or render as a plain URL in others. in some contexts, or render as a plain URL in others.
""" """
def __new__(self, url, obj): def __new__(cls, url, obj):
ret = str.__new__(self, url) ret = str.__new__(cls, url)
ret.obj = obj ret.obj = obj
return ret return ret

View File

@ -38,9 +38,7 @@ def escape_curly_brackets(url_path):
""" """
Double brackets in regex of url_path for escape string formatting Double brackets in regex of url_path for escape string formatting
""" """
if ('{' and '}') in url_path: return url_path.replace('{', '{{').replace('}', '}}')
url_path = url_path.replace('{', '{{').replace('}', '}}')
return url_path
def flatten(list_of_lists): def flatten(list_of_lists):

View File

@ -23,8 +23,8 @@ Other access should target the submodules directly
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from . import coreapi, openapi from . import coreapi, openapi
from .inspectors import DefaultSchema # noqa
from .coreapi import AutoSchema, ManualSchema, SchemaGenerator # noqa from .coreapi import AutoSchema, ManualSchema, SchemaGenerator # noqa
from .inspectors import DefaultSchema # noqa
def get_schema_view( def get_schema_view(

View File

@ -387,7 +387,7 @@ class AutoSchema(ViewInspector):
result = { result = {
'properties': properties 'properties': properties
} }
if len(required) > 0: if required:
result['required'] = required result['required'] = required
return result return result
@ -463,7 +463,7 @@ class AutoSchema(ViewInspector):
content = self._map_serializer(serializer) content = self._map_serializer(serializer)
# No required fields for PATCH # No required fields for PATCH
if method == 'PATCH': if method == 'PATCH':
del content['required'] content.pop('required', None)
# No read_only fields for request. # No read_only fields for request.
for name, schema in content['properties'].copy().items(): for name, schema in content['properties'].copy().items():
if 'readOnly' in schema: if 'readOnly' in schema:

View File

@ -16,12 +16,11 @@ import traceback
from collections import OrderedDict from collections import OrderedDict
from collections.abc import Mapping from collections.abc import Mapping
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import models from django.db import models
from django.db.models import DurationField as ModelDurationField from django.db.models import DurationField as ModelDurationField
from django.db.models.fields import Field as DjangoModelField from django.db.models.fields import Field as DjangoModelField
from django.db.models.fields import FieldDoesNotExist
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -791,6 +790,8 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data):
* Silently ignore the nested part of the update. * Silently ignore the nested part of the update.
* Automatically create a profile instance. * Automatically create a profile instance.
""" """
ModelClass = serializer.Meta.model
model_field_info = model_meta.get_field_info(ModelClass)
# Ensure we don't have a writable nested field. For example: # Ensure we don't have a writable nested field. For example:
# #
@ -800,6 +801,7 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data):
assert not any( assert not any(
isinstance(field, BaseSerializer) and isinstance(field, BaseSerializer) and
(field.source in validated_data) and (field.source in validated_data) and
(field.source in model_field_info.relations) and
isinstance(validated_data[field.source], (list, dict)) isinstance(validated_data[field.source], (list, dict))
for field in serializer._writable_fields for field in serializer._writable_fields
), ( ), (
@ -818,9 +820,19 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data):
# class UserSerializer(ModelSerializer): # class UserSerializer(ModelSerializer):
# ... # ...
# address = serializer.CharField('profile.address') # address = serializer.CharField('profile.address')
#
# Though, non-relational fields (e.g., JSONField) are acceptable. For example:
#
# class NonRelationalPersonModel(models.Model):
# profile = JSONField()
#
# class UserSerializer(ModelSerializer):
# ...
# address = serializer.CharField('profile.address')
assert not any( assert not any(
len(field.source_attrs) > 1 and len(field.source_attrs) > 1 and
(field.source_attrs[0] in validated_data) and (field.source_attrs[0] in validated_data) and
(field.source_attrs[0] in model_field_info.relations) and
isinstance(validated_data[field.source_attrs[0]], (list, dict)) isinstance(validated_data[field.source_attrs[0]], (list, dict))
for field in serializer._writable_fields for field in serializer._writable_fields
), ( ), (

View File

@ -6,7 +6,7 @@ addopts=--tb=short --strict -ra
testspath = tests testspath = tests
[flake8] [flake8]
ignore = E501 ignore = E501,W504
banned-modules = json = use from rest_framework.utils import json! banned-modules = json = use from rest_framework.utils import json!
[isort] [isort]

View File

@ -101,6 +101,7 @@ setup(
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3 :: Only',
'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP',
], ],

View File

@ -24,8 +24,8 @@ from rest_framework.utils import formatting
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from . import views
from ..models import BasicModel, ForeignKeySource, ManyToManySource from ..models import BasicModel, ForeignKeySource, ManyToManySource
from . import views
factory = APIRequestFactory() factory = APIRequestFactory()

View File

@ -169,6 +169,31 @@ class TestOperationIntrospection(TestCase):
for response in inspector._get_responses(path, method).values(): for response in inspector._get_responses(path, method).values():
assert 'required' not in response['content']['application/json']['schema'] assert 'required' not in response['content']['application/json']['schema']
def test_empty_required_with_patch_method(self):
path = '/'
method = 'PATCH'
class Serializer(serializers.Serializer):
read_only = serializers.CharField(read_only=True)
write_only = serializers.CharField(write_only=True, required=False)
class View(generics.GenericAPIView):
serializer_class = Serializer
view = create_view(
View,
method,
create_request(path)
)
inspector = AutoSchema()
inspector.view = view
request_body = inspector._get_request_body(path, method)
# there should be no empty 'required' property, see #6834
assert 'required' not in request_body['content']['application/json']['schema']
for response in inspector._get_responses(path, method).values():
assert 'required' not in response['content']['application/json']['schema']
def test_response_body_generation(self): def test_response_body_generation(self):
path = '/' path = '/'
method = 'POST' method = 'POST'

View File

@ -1080,6 +1080,7 @@ class TestDecimalField(FieldValues):
invalid_inputs = ( invalid_inputs = (
('abc', ["A valid number is required."]), ('abc', ["A valid number is required."]),
(Decimal('Nan'), ["A valid number is required."]), (Decimal('Nan'), ["A valid number is required."]),
(Decimal('Snan'), ["A valid number is required."]),
(Decimal('Inf'), ["A valid number is required."]), (Decimal('Inf'), ["A valid number is required."]),
('12.345', ["Ensure that there are no more than 3 digits in total."]), ('12.345', ["Ensure that there are no more than 3 digits in total."]),
(200000000000.0, ["Ensure that there are no more than 3 digits in total."]), (200000000000.0, ["Ensure that there are no more than 3 digits in total."]),

View File

@ -494,28 +494,28 @@ class CustomPermissionsTests(TestCase):
self.custom_message = 'Custom: You cannot access this resource' self.custom_message = 'Custom: You cannot access this resource'
def test_permission_denied(self): def test_permission_denied(self):
response = denied_view(self.request, pk=1) response = denied_view(self.request, pk=1)
detail = response.data.get('detail') detail = response.data.get('detail')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertNotEqual(detail, self.custom_message) self.assertNotEqual(detail, self.custom_message)
def test_permission_denied_with_custom_detail(self): def test_permission_denied_with_custom_detail(self):
response = denied_view_with_detail(self.request, pk=1) response = denied_view_with_detail(self.request, pk=1)
detail = response.data.get('detail') detail = response.data.get('detail')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(detail, self.custom_message) self.assertEqual(detail, self.custom_message)
def test_permission_denied_for_object(self): def test_permission_denied_for_object(self):
response = denied_object_view(self.request, pk=1) response = denied_object_view(self.request, pk=1)
detail = response.data.get('detail') detail = response.data.get('detail')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertNotEqual(detail, self.custom_message) self.assertNotEqual(detail, self.custom_message)
def test_permission_denied_for_object_with_custom_detail(self): def test_permission_denied_for_object_with_custom_detail(self):
response = denied_object_view_with_detail(self.request, pk=1) response = denied_object_view_with_detail(self.request, pk=1)
detail = response.data.get('detail') detail = response.data.get('detail')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(detail, self.custom_message) self.assertEqual(detail, self.custom_message)
class PermissionsCompositionTests(TestCase): class PermissionsCompositionTests(TestCase):

View File

@ -251,7 +251,7 @@ class TestHyperlinkedIdentityField(APISimpleTestCase):
def test_improperly_configured(self): def test_improperly_configured(self):
""" """
If a matching view cannot be reversed with the given instance, If a matching view cannot be reversed with the given instance,
the the user has misconfigured something, as the URL conf and the the user has misconfigured something, as the URL conf and the
hyperlinked field do not match. hyperlinked field do not match.
""" """
self.field.reverse = fail_reverse self.field.reverse = fail_reverse

View File

@ -4,6 +4,8 @@ from django.http import QueryDict
from django.test import TestCase from django.test import TestCase
from rest_framework import serializers from rest_framework import serializers
from rest_framework.compat import postgres_fields
from rest_framework.serializers import raise_errors_on_nested_writes
class TestNestedSerializer: class TestNestedSerializer:
@ -302,3 +304,50 @@ class TestNestedWriteErrors(TestCase):
'serializer `tests.test_serializer_nested.DottedAddressSerializer`, ' 'serializer `tests.test_serializer_nested.DottedAddressSerializer`, '
'or set `read_only=True` on dotted-source serializer fields.' 'or set `read_only=True` on dotted-source serializer fields.'
) )
if postgres_fields:
class NonRelationalPersonModel(models.Model):
"""Model declaring a postgres JSONField"""
data = postgres_fields.JSONField()
@pytest.mark.skipif(not postgres_fields, reason='psycopg2 is not installed')
class TestNestedNonRelationalFieldWrite:
"""
Test that raise_errors_on_nested_writes does not raise `AssertionError` when the
model field is not a relation.
"""
def test_nested_serializer_create_and_update(self):
class NonRelationalPersonDataSerializer(serializers.Serializer):
occupation = serializers.CharField()
class NonRelationalPersonSerializer(serializers.ModelSerializer):
data = NonRelationalPersonDataSerializer()
class Meta:
model = NonRelationalPersonModel
fields = ['data']
serializer = NonRelationalPersonSerializer(data={'data': {'occupation': 'developer'}})
assert serializer.is_valid()
assert serializer.validated_data == {'data': {'occupation': 'developer'}}
raise_errors_on_nested_writes('create', serializer, serializer.validated_data)
raise_errors_on_nested_writes('update', serializer, serializer.validated_data)
def test_dotted_source_field_create_and_update(self):
class DottedNonRelationalPersonSerializer(serializers.ModelSerializer):
occupation = serializers.CharField(source='data.occupation')
class Meta:
model = NonRelationalPersonModel
fields = ['occupation']
serializer = DottedNonRelationalPersonSerializer(data={'occupation': 'developer'})
assert serializer.is_valid()
assert serializer.validated_data == {'data': {'occupation': 'developer'}}
raise_errors_on_nested_writes('create', serializer, serializer.validated_data)
raise_errors_on_nested_writes('update', serializer, serializer.validated_data)

View File

@ -4,7 +4,7 @@ envlist =
{py35,py36,py37}-django20, {py35,py36,py37}-django20,
{py35,py36,py37}-django21 {py35,py36,py37}-django21
{py35,py36,py37}-django22 {py35,py36,py37}-django22
{py36,py37}-djangomaster, {py36,py37,py38}-djangomaster,
base,dist,lint,docs, base,dist,lint,docs,
[travis:env] [travis:env]
@ -44,14 +44,12 @@ deps =
-rrequirements/requirements-optionals.txt -rrequirements/requirements-optionals.txt
[testenv:lint] [testenv:lint]
basepython = python3.7
commands = ./runtests.py --lintonly commands = ./runtests.py --lintonly
deps = deps =
-rrequirements/requirements-codestyle.txt -rrequirements/requirements-codestyle.txt
-rrequirements/requirements-testing.txt -rrequirements/requirements-testing.txt
[testenv:docs] [testenv:docs]
basepython = python3.7
skip_install = true skip_install = true
commands = mkdocs build commands = mkdocs build
deps = deps =