Merge branch 'master' into localizedfloatfield

This commit is contained in:
kgeorgy 2017-05-02 15:25:23 +02:00 committed by GitHub
commit 97051799e7
24 changed files with 386 additions and 36 deletions

View File

@ -53,7 +53,7 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements
* Python (2.7, 3.2, 3.3, 3.4, 3.5)
* Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6)
* Django (1.8, 1.9, 1.10, 1.11)
# Installation

View File

@ -117,7 +117,7 @@ The simplest way to include a schema in your project is to use the
Once the view has been added, you'll be able to make API requests to retrieve
the auto-generated schema definition.
$ http http://127.0.0.1:8000/ Accept:application/vnd.coreapi+json
$ http http://127.0.0.1:8000/ Accept:application/coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/vnd.coreapi+json
@ -170,6 +170,22 @@ May be used to pass the set of renderer classes that can be used to render the A
renderer_classes=[CoreJSONRenderer, APIBlueprintRenderer]
)
#### `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:
schema_url_patterns = [
url(r'^api/', include('myproject.api.urls')),
]
schema_view = get_schema_view(
title='Server Monitoring API',
url='https://www.example.org/api/',
patterns=schema_url_patterns,
)
## Using an explicit schema view
If you need a little more control than the `get_schema_view()` shortcut gives you,

View File

@ -162,7 +162,7 @@ The `credentials` method is appropriate for testing APIs that require authentica
#### .force_authenticate(user=None, token=None)
Sometimes you may want to bypass authentication, and simple force all requests by the test client to be automatically treated as authenticated.
Sometimes you may want to bypass authentication entirely and force all requests by the test client to be automatically treated as authenticated.
This can be a useful shortcut if you're testing the API but don't want to have to construct valid authentication credentials in order to make test requests.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@ -87,7 +87,7 @@ continued development by **[signing up for a paid plan][funding]**.
REST framework requires the following:
* Python (2.7, 3.2, 3.3, 3.4, 3.5)
* Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6)
* Django (1.8, 1.9, 1.10, 1.11)
The following packages are optional:

View File

@ -146,8 +146,6 @@ An alternative, but more complex option would be to replace the input with an au
There are [a variety of packages for autocomplete widgets][autocomplete-packages], such as [django-autocomplete-light][django-autocomplete-light], that you may want to refer to. Note that you will not be able to simply include these components as standard widgets, but will need to write the HTML template explicitly. This is because REST framework 3.0 no longer supports the `widget` keyword argument since it now uses templated HTML generation.
Better support for autocomplete inputs is planned in future versions.
---
[cite]: http://en.wikiquote.org/wiki/Alfred_North_Whitehead

View File

@ -2,6 +2,17 @@
There are a wide range of resources available for learning and using Django REST framework. We try to keep a comprehensive list available here.
## Books
<div class="book-covers">
<a class="book-cover" href="https://hellowebapp.com/order/">
<img src="../../img/books/hwa-cover.png"/>
</a>
<a class="book-cover" href="https://www.twoscoopspress.com/products/two-scoops-of-django-1-11">
<img src="../../img/books/tsd-cover.png"/>
</a>
</div>
## Tutorials
* [Beginner's Guide to the Django REST Framework][beginners-guide-to-the-django-rest-framework]
@ -56,10 +67,6 @@ There are a wide range of resources available for learning and using Django REST
* [New Django Admin with DRF and EmberJS... What are the News?][new-django-admin-with-drf-and-emberjs]
* [Blog posts about Django REST Framework][medium-django-rest-framework]
## Books
* [Hello Web App: Intermediate Concepts, Chapter 10][hello-web-app-intermediate]
### Documentations
* [Classy Django REST Framework][cdrf.co]
* [DRF-schema-adapter][drf-schema]
@ -95,7 +102,6 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
[drf-schema]: http://drf-schema-adapter.readthedocs.io/en/latest/
[creating-a-production-ready-api-with-python-and-drf-part1]: https://www.andreagrandi.it/2016/09/28/creating-production-ready-api-python-django-rest-framework-part-1/
[creating-a-production-ready-api-with-python-and-drf-part2]: https://www.andreagrandi.it/2016/10/01/creating-a-production-ready-api-with-python-and-django-rest-framework-part-2/
[hello-web-app-intermediate]: https://hellowebapp.com/order/
[django-rest-api-so-easy]: https://www.youtube.com/watch?v=cqP758k1BaQ
[full-fledged-rest-api-with-django-oauth-tookit]: https://www.youtube.com/watch?v=M6Ud3qC2tTk
[drf-in-your-pjs]: https://www.youtube.com/watch?v=xMtHsWa72Ww

View File

@ -310,7 +310,7 @@ Quit out of the shell...
Validating models...
0 errors found
Django version 1.8.3, using settings 'tutorial.settings'
Django version 1.11, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

View File

@ -36,6 +36,7 @@ API schema.
We can now include a schema for our API, by including an autogenerated schema
view in our URL configuration.
```
from rest_framework.schemas import get_schema_view
schema_view = get_schema_view(title='Pastebin API')
@ -44,6 +45,7 @@ view in our URL configuration.
   url(r'^schema/$', schema_view),
...
]
```
If you visit the API root endpoint in a browser you should now see `corejson`
representation become available as an option.

View File

@ -417,3 +417,8 @@ ul.sponsor {
.toclink {
color: #333;
}
.book-cover img {
margin: 0 !important;
display: inline-block !important;
}

View File

@ -275,6 +275,14 @@ except ImportError:
def pygments_css(style):
return None
try:
import pytz
from pytz.exceptions import InvalidTimeError
except ImportError:
InvalidTimeError = Exception
# `separators` argument to `json.dumps()` differs between 2.x and 3.x
# See: http://bugs.python.org/issue22767
if six.PY3:
@ -339,6 +347,7 @@ def set_many(instance, field, value):
field = getattr(instance, field)
field.set(value)
def include(module, namespace=None, app_name=None):
from django.conf.urls import include
if django.VERSION < (1,9):

View File

@ -6,7 +6,9 @@ from rest_framework.renderers import (
from rest_framework.schemas import SchemaGenerator, get_schema_view
def get_docs_view(title=None, description=None, schema_url=None, public=True, generator_class=SchemaGenerator):
def get_docs_view(
title=None, description=None, schema_url=None, public=True,
patterns=None, generator_class=SchemaGenerator):
renderer_classes = [DocumentationRenderer, CoreJSONRenderer]
return get_schema_view(
@ -15,11 +17,14 @@ def get_docs_view(title=None, description=None, schema_url=None, public=True, ge
description=description,
renderer_classes=renderer_classes,
public=public,
patterns=patterns,
generator_class=generator_class,
)
def get_schemajs_view(title=None, description=None, schema_url=None, public=True, generator_class=SchemaGenerator):
def get_schemajs_view(
title=None, description=None, schema_url=None, public=True,
patterns=None, generator_class=SchemaGenerator):
renderer_classes = [SchemaJSRenderer]
return get_schema_view(
@ -28,16 +33,20 @@ def get_schemajs_view(title=None, description=None, schema_url=None, public=True
description=description,
renderer_classes=renderer_classes,
public=public,
patterns=patterns,
generator_class=generator_class,
)
def include_docs_urls(title=None, description=None, schema_url=None, public=True, generator_class=SchemaGenerator):
def include_docs_urls(
title=None, description=None, schema_url=None, public=True,
patterns=None, generator_class=SchemaGenerator):
docs_view = get_docs_view(
title=title,
description=description,
schema_url=schema_url,
public=public,
patterns=patterns,
generator_class=generator_class,
)
schema_js_view = get_schemajs_view(
@ -45,6 +54,7 @@ def include_docs_urls(title=None, description=None, schema_url=None, public=True
description=description,
schema_url=schema_url,
public=public,
patterns=patterns,
generator_class=generator_class,
)
urls = [

View File

@ -35,7 +35,8 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import ISO_8601
from rest_framework.compat import (
get_remote_field, unicode_repr, unicode_to_repr, value_from_object
InvalidTimeError, get_remote_field, unicode_repr, unicode_to_repr,
value_from_object
)
from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.settings import api_settings
@ -1102,6 +1103,7 @@ class DateTimeField(Field):
default_error_messages = {
'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'),
'date': _('Expected a datetime but got a date.'),
'make_aware': _('Invalid datetime for the timezone "{timezone}".')
}
datetime_parser = datetime.datetime.strptime
@ -1122,7 +1124,10 @@ class DateTimeField(Field):
field_timezone = getattr(self, 'timezone', self.default_timezone())
if (field_timezone is not None) and not timezone.is_aware(value):
return timezone.make_aware(value, field_timezone)
try:
return timezone.make_aware(value, field_timezone)
except InvalidTimeError:
self.fail('make_aware', timezone=field_timezone)
elif (field_timezone is None) and timezone.is_aware(value):
return timezone.make_naive(value, utc)
return value

View File

@ -332,12 +332,12 @@ class LimitOffsetPagination(BasePagination):
template = 'rest_framework/pagination/numbers.html'
def paginate_queryset(self, queryset, request, view=None):
self.count = _get_count(queryset)
self.limit = self.get_limit(request)
if self.limit is None:
return None
self.offset = self.get_offset(request)
self.count = _get_count(queryset)
self.request = request
if self.count > self.limit and self.template is not None:
self.display_page_controls = True

View File

@ -7,7 +7,9 @@ from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.db.models import Manager
from django.db.models.query import QuerySet
from django.utils import six
from django.utils.encoding import python_2_unicode_compatible, smart_text
from django.utils.encoding import (
python_2_unicode_compatible, smart_text, uri_to_iri
)
from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _
@ -324,6 +326,8 @@ class HyperlinkedRelatedField(RelatedField):
if data.startswith(prefix):
data = '/' + data[len(prefix):]
data = uri_to_iri(data)
try:
match = resolve(data)
except Resolver404:

View File

@ -604,7 +604,7 @@ class SchemaGenerator(object):
return []
pagination = getattr(view, 'pagination_class', None)
if not pagination or not pagination.page_size:
if not pagination or not getattr(pagination, 'page_size', None):
return []
paginator = view.pagination_class()
@ -695,18 +695,15 @@ class SchemaView(APIView):
def get_schema_view(
title=None,
url=None,
description=None,
urlconf=None,
renderer_classes=None,
public=False,
generator_class=SchemaGenerator,
):
title=None, url=None, description=None, urlconf=None, renderer_classes=None,
public=False, patterns=None, generator_class=SchemaGenerator):
"""
Return a schema view.
"""
generator = generator_class(title=title, url=url, description=description, urlconf=urlconf)
generator = generator_class(
title=title, url=url, description=description,
urlconf=urlconf, patterns=patterns,
)
return SchemaView.as_view(
renderer_classes=renderer_classes,
schema_generator=generator,

View File

@ -38,7 +38,8 @@ from rest_framework.utils.field_mapping import (
get_relation_kwargs, get_url_kwargs
)
from rest_framework.utils.serializer_helpers import (
BindingDict, BoundField, NestedBoundField, ReturnDict, ReturnList
BindingDict, BoundField, JSONBoundField, NestedBoundField, ReturnDict,
ReturnList
)
from rest_framework.validators import (
UniqueForDateValidator, UniqueForMonthValidator, UniqueForYearValidator,
@ -521,6 +522,8 @@ class Serializer(BaseSerializer):
error = self.errors.get(key) if hasattr(self, '_errors') else None
if isinstance(field, Serializer):
return NestedBoundField(field, value, error)
if isinstance(field, JSONField):
return JSONBoundField(field, value, error)
return BoundField(field, value, error)
# Include a backlink to the serializer class on return objects.
@ -562,6 +565,10 @@ class ListSerializer(BaseSerializer):
super(ListSerializer, self).__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
def bind(self, field_name, parent):
super(ListSerializer, self).bind(field_name, parent)
self.partial = self.parent.partial
def get_initial(self):
if hasattr(self, 'initial_data'):
return self.to_representation(self.initial_data)
@ -613,6 +620,9 @@ class ListSerializer(BaseSerializer):
}, code='not_a_list')
if not self.allow_empty and len(data) == 0:
if self.parent and self.partial:
raise SkipField()
message = self.error_messages['empty']
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]

View File

@ -8,7 +8,7 @@ from django.core import validators
from django.db import models
from django.utils.text import capfirst
from rest_framework.compat import DecimalValidator
from rest_framework.compat import DecimalValidator, JSONField
from rest_framework.validators import UniqueValidator
NUMERIC_FIELD_TYPES = (
@ -88,7 +88,7 @@ def get_field_kwargs(field_name, model_field):
if decimal_places is not None:
kwargs['decimal_places'] = decimal_places
if isinstance(model_field, models.TextField):
if isinstance(model_field, models.TextField) or (JSONField and isinstance(model_field, JSONField)):
kwargs['style'] = {'base_template': 'textarea.html'}
if isinstance(model_field, models.AutoField) or not model_field.editable:

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals
import collections
import json
from collections import OrderedDict
from django.utils.encoding import force_text
@ -82,6 +83,16 @@ class BoundField(object):
return self.__class__(self._field, value, self.errors, self._prefix)
class JSONBoundField(BoundField):
def as_form_field(self):
value = self.value
try:
value = json.dumps(self.value, sort_keys=True, indent=4)
except TypeError:
pass
return self.__class__(self._field, value, self.errors, self._prefix)
class NestedBoundField(BoundField):
"""
This `BoundField` additionally implements __iter__ and __getitem__
@ -101,7 +112,7 @@ class NestedBoundField(BoundField):
def __getitem__(self, key):
field = self.fields[key]
value = self.value.get(key) if self.value else None
error = self.errors.get(key) if self.errors else None
error = self.errors.get(key) if isinstance(self.errors, dict) else None
if hasattr(field, 'fields'):
return NestedBoundField(field, value, error, prefix=self.name + '.')
return BoundField(field, value, error, prefix=self.name + '.')

View File

@ -12,7 +12,7 @@ from django.utils import six
from django.utils.timezone import utc
import rest_framework
from rest_framework import serializers
from rest_framework import compat, serializers
from rest_framework.fields import is_simple_callable
try:
@ -1235,6 +1235,30 @@ class TestNaiveDateTimeField(FieldValues):
field = serializers.DateTimeField(default_timezone=None)
class TestNaiveDayLightSavingTimeTimeZoneDateTimeField(FieldValues):
"""
Invalid values for `DateTimeField` with datetime in DST shift (non-existing or ambiguous) and timezone with DST.
Timezone America/New_York has DST shift from 2017-03-12T02:00:00 to 2017-03-12T03:00:00 and
from 2017-11-05T02:00:00 to 2017-11-05T01:00:00 in 2017.
"""
valid_inputs = {}
invalid_inputs = {
'2017-03-12T02:30:00': ['Invalid datetime for the timezone "America/New_York".'],
'2017-11-05T01:30:00': ['Invalid datetime for the timezone "America/New_York".']
}
outputs = {}
class MockTimezone:
@staticmethod
def localize(value, is_dst):
raise compat.InvalidTimeError()
def __str__(self):
return 'America/New_York'
field = serializers.DateTimeField(default_timezone=MockTimezone())
class TestTimeField(FieldValues):
"""
Valid and invalid values for `TimeField`.

View File

@ -1,7 +1,9 @@
import uuid
import pytest
from django.conf.urls import url
from django.core.exceptions import ImproperlyConfigured
from django.test import override_settings
from django.utils.datastructures import MultiValueDict
from rest_framework import serializers
@ -87,10 +89,21 @@ class TestProxiedPrimaryKeyRelatedField(APISimpleTestCase):
assert representation == self.instance.pk.int
@override_settings(ROOT_URLCONF=[
url(r'^example/(?P<name>.+)/$', lambda: None, name='example'),
])
class TestHyperlinkedRelatedField(APISimpleTestCase):
def setUp(self):
self.queryset = MockQueryset([
MockObject(pk=1, name='foobar'),
MockObject(pk=2, name='baz qux'),
])
self.field = serializers.HyperlinkedRelatedField(
view_name='example', read_only=True)
view_name='example',
lookup_field='name',
lookup_url_kwarg='name',
queryset=self.queryset,
)
self.field.reverse = mock_reverse
self.field._context = {'request': True}
@ -98,6 +111,20 @@ class TestHyperlinkedRelatedField(APISimpleTestCase):
representation = self.field.to_representation(MockObject(pk=''))
assert representation is None
def test_hyperlinked_related_lookup_exists(self):
instance = self.field.to_internal_value('http://example.org/example/foobar/')
assert instance is self.queryset.items[0]
def test_hyperlinked_related_lookup_url_encoded_exists(self):
instance = self.field.to_internal_value('http://example.org/example/baz%20qux/')
assert instance is self.queryset.items[1]
def test_hyperlinked_related_lookup_does_not_exist(self):
with pytest.raises(serializers.ValidationError) as excinfo:
self.field.to_internal_value('http://example.org/example/doesnotexist/')
msg = excinfo.value.detail[0]
assert msg == 'Invalid hyperlink - Object does not exist.'
class TestHyperlinkedIdentityField(APISimpleTestCase):
def setUp(self):

View File

@ -156,6 +156,7 @@ class TestCustomLookupFields(TestCase):
"""
def setUp(self):
RouterTestModel.objects.create(uuid='123', text='foo bar')
RouterTestModel.objects.create(uuid='a b', text='baz qux')
def test_custom_lookup_field_route(self):
detail_route = notes_router.urls[-1]
@ -164,12 +165,19 @@ class TestCustomLookupFields(TestCase):
def test_retrieve_lookup_field_list_view(self):
response = self.client.get('/example/notes/')
assert response.data == [{"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}]
assert response.data == [
{"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"},
{"url": "http://testserver/example/notes/a%20b/", "uuid": "a b", "text": "baz qux"},
]
def test_retrieve_lookup_field_detail_view(self):
response = self.client.get('/example/notes/123/')
assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}
def test_retrieve_lookup_field_url_encoded_detail_view_(self):
response = self.client.get('/example/notes/a%20b/')
assert response.data == {"url": "http://testserver/example/notes/a%20b/", "uuid": "a b", "text": "baz qux"}
class TestLookupValueRegex(TestCase):
"""
@ -211,6 +219,10 @@ class TestLookupUrlKwargs(TestCase):
response = self.client.get('/example2/notes/fo/')
assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}
def test_retrieve_lookup_url_encoded_kwarg_detail_view(self):
response = self.client.get('/example2/notes/foo%20bar/')
assert response.data == {"url": "http://testserver/example/notes/123/", "uuid": "123", "text": "foo bar"}
class TestTrailingSlashIncluded(TestCase):
def setUp(self):

View File

@ -318,3 +318,217 @@ class TestSerializerPartialUsage:
assert serializer.is_valid()
assert serializer.validated_data == {}
assert serializer.errors == {}
def test_allow_empty_true(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
instance = [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
serializer = ListSerializer(instance, data=[], partial=True, many=True)
assert serializer.is_valid()
assert serializer.validated_data == []
assert serializer.errors == []
def test_update_allow_empty_true(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
instance = [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
input_data = [{'update_field': 31}, {'update_field': 41}]
updated_data_list = [
{'update_field': 31, 'store_field': 12},
{'update_field': 41, 'store_field': 22},
]
serializer = ListSerializer(
instance, data=input_data, partial=True, many=True)
assert serializer.is_valid()
for index, data in enumerate(serializer.validated_data):
for key, value in data.items():
assert value == updated_data_list[index][key]
assert serializer.errors == []
def test_allow_empty_false(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
instance = [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
serializer = ListSerializer(
instance, data=[], allow_empty=False, partial=True, many=True)
assert not serializer.is_valid()
assert serializer.validated_data == []
assert len(serializer.errors) == 1
assert serializer.errors['non_field_errors'][0] == 'This list may not be empty.'
def test_update_allow_empty_false(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
instance = [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
input_data = [{'update_field': 31}, {'update_field': 41}]
updated_data_list = [
{'update_field': 31, 'store_field': 12},
{'update_field': 41, 'store_field': 22},
]
serializer = ListSerializer(
instance, data=input_data, allow_empty=False, partial=True, many=True)
assert serializer.is_valid()
for index, data in enumerate(serializer.validated_data):
for key, value in data.items():
assert value == updated_data_list[index][key]
assert serializer.errors == []
def test_as_field_allow_empty_true(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
class Serializer(serializers.Serializer):
extra_field = serializers.IntegerField()
list_field = ListSerializer(many=True)
instance = {
'extra_field': 1,
'list_field': [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
}
serializer = Serializer(instance, data={}, partial=True)
assert serializer.is_valid()
assert serializer.validated_data == {}
assert serializer.errors == {}
def test_udate_as_field_allow_empty_true(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
class Serializer(serializers.Serializer):
extra_field = serializers.IntegerField()
list_field = ListSerializer(many=True)
instance = {
'extra_field': 1,
'list_field': [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
}
input_data_1 = {'extra_field': 2}
input_data_2 = {
'list_field': [
{'update_field': 31},
{'update_field': 41},
]
}
# data_1
serializer = Serializer(instance, data=input_data_1, partial=True)
assert serializer.is_valid()
assert len(serializer.validated_data) == 1
assert serializer.validated_data['extra_field'] == 2
assert serializer.errors == {}
# data_2
serializer = Serializer(instance, data=input_data_2, partial=True)
assert serializer.is_valid()
updated_data_list = [
{'update_field': 31, 'store_field': 12},
{'update_field': 41, 'store_field': 22},
]
for index, data in enumerate(serializer.validated_data['list_field']):
for key, value in data.items():
assert value == updated_data_list[index][key]
assert serializer.errors == {}
def test_as_field_allow_empty_false(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
class Serializer(serializers.Serializer):
extra_field = serializers.IntegerField()
list_field = ListSerializer(many=True, allow_empty=False)
instance = {
'extra_field': 1,
'list_field': [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
}
serializer = Serializer(instance, data={}, partial=True)
assert serializer.is_valid()
assert serializer.validated_data == {}
assert serializer.errors == {}
def test_update_as_field_allow_empty_false(self):
class ListSerializer(serializers.Serializer):
update_field = serializers.IntegerField()
store_field = serializers.IntegerField()
class Serializer(serializers.Serializer):
extra_field = serializers.IntegerField()
list_field = ListSerializer(many=True, allow_empty=False)
instance = {
'extra_field': 1,
'list_field': [
{'update_field': 11, 'store_field': 12},
{'update_field': 21, 'store_field': 22},
]
}
input_data_1 = {'extra_field': 2}
input_data_2 = {
'list_field': [
{'update_field': 31},
{'update_field': 41},
]
}
updated_data_list = [
{'update_field': 31, 'store_field': 12},
{'update_field': 41, 'store_field': 22},
]
# data_1
serializer = Serializer(instance, data=input_data_1, partial=True)
assert serializer.is_valid()
assert serializer.errors == {}
# data_2
serializer = Serializer(instance, data=input_data_2, partial=True)
assert serializer.is_valid()
for index, data in enumerate(serializer.validated_data['list_field']):
for key, value in data.items():
assert value == updated_data_list[index][key]
assert serializer.errors == {}