django-rest-framework/tests/test_fields.py

2828 lines
91 KiB
Python
Raw Normal View History

2014-09-22 20:46:02 +04:00
import datetime
import math
2015-03-03 14:34:06 +03:00
import os
import re
import sys
2015-06-25 23:55:51 +03:00
import uuid
import warnings
from decimal import ROUND_DOWN, ROUND_UP, Decimal
from enum import auto
from unittest.mock import patch
2015-06-25 23:55:51 +03:00
2014-09-22 20:46:02 +04:00
import pytest
try:
import pytz
except ImportError:
pytz = None
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db.models import IntegerChoices, TextChoices
from django.http import QueryDict
from django.test import TestCase, override_settings
from django.utils.timezone import activate, deactivate, override
2015-06-25 23:55:51 +03:00
import rest_framework
from rest_framework import exceptions, serializers
from rest_framework.fields import (
AlphabeticFieldValidator, AlphanumericFieldValidator, BuiltinSignatureError, CustomLengthValidator, DjangoImageField, SkipField, empty,
is_simple_callable
)
from tests.models import UUIDForeignKeyTarget
Version 3.5 (#4525) * Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
2016-10-10 15:03:46 +03:00
if sys.version_info >= (3, 9):
from zoneinfo import ZoneInfo
else:
from backports.zoneinfo import ZoneInfo
utc = datetime.timezone.utc
Version 3.5 (#4525) * Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
2016-10-10 15:03:46 +03:00
# Tests for helper functions.
# ---------------------------
2019-05-06 21:35:58 +03:00
Version 3.5 (#4525) * Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
2016-10-10 15:03:46 +03:00
class TestIsSimpleCallable:
def test_method(self):
class Foo:
@classmethod
def classmethod(cls):
pass
def valid(self):
pass
def valid_kwargs(self, param='value'):
pass
def valid_vargs_kwargs(self, *args, **kwargs):
pass
Version 3.5 (#4525) * Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
2016-10-10 15:03:46 +03:00
def invalid(self, param):
pass
assert is_simple_callable(Foo.classmethod)
# unbound methods
assert not is_simple_callable(Foo.valid)
assert not is_simple_callable(Foo.valid_kwargs)
assert not is_simple_callable(Foo.valid_vargs_kwargs)
Version 3.5 (#4525) * Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
2016-10-10 15:03:46 +03:00
assert not is_simple_callable(Foo.invalid)
# bound methods
assert is_simple_callable(Foo().valid)
assert is_simple_callable(Foo().valid_kwargs)
assert is_simple_callable(Foo().valid_vargs_kwargs)
Version 3.5 (#4525) * Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
2016-10-10 15:03:46 +03:00
assert not is_simple_callable(Foo().invalid)
def test_function(self):
def simple():
pass
def valid(param='value', param2='value'):
pass
def valid_vargs_kwargs(*args, **kwargs):
pass
Version 3.5 (#4525) * Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
2016-10-10 15:03:46 +03:00
def invalid(param, param2='value'):
pass
assert is_simple_callable(simple)
assert is_simple_callable(valid)
assert is_simple_callable(valid_vargs_kwargs)
Version 3.5 (#4525) * Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
2016-10-10 15:03:46 +03:00
assert not is_simple_callable(invalid)
@pytest.mark.parametrize('obj', (True, None, "str", b'bytes', 123, 1.23))
def test_not_callable(self, obj):
assert not is_simple_callable(obj)
def test_4602_regression(self):
from django.db import models
class ChoiceModel(models.Model):
choice_field = models.CharField(
max_length=1, default='a',
choices=(('a', 'A'), ('b', 'B')),
)
class Meta:
app_label = 'tests'
assert is_simple_callable(ChoiceModel().get_choice_field_display)
def test_builtin_function(self):
# Built-in function signatures are not easily inspectable, so the
# current expectation is to just raise a helpful error message.
timestamp = datetime.datetime.now()
with pytest.raises(BuiltinSignatureError) as exc_info:
is_simple_callable(timestamp.date)
assert str(exc_info.value) == (
'Built-in function signatures are not inspectable. Wrap the '
'function call in a simple, pure Python function.')
Version 3.5 (#4525) * Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
2016-10-10 15:03:46 +03:00
def test_type_annotation(self):
# The annotation will otherwise raise a syntax error in python < 3.5
locals = {}
exec("def valid(param: str='value'): pass", locals)
valid = locals['valid']
Version 3.5 (#4525) * Start test case * Added 'requests' test client * Address typos * Graceful fallback if requests is not installed. * Add cookie support * Tests for auth and CSRF * Py3 compat * py3 compat * py3 compat * Add get_requests_client * Added SchemaGenerator.should_include_link * add settings for html cutoff on related fields * Router doesn't work if prefix is blank, though project urls.py handles prefix * Fix Django 1.10 to-many deprecation * Add django.core.urlresolvers compatibility * Update django-filter & django-guardian * Check for empty router prefix; adjust URL accordingly It's easiest to fix this issue after we have made the regex. To try to fix it before would require doing something different for List vs Detail, which means we'd have to know which type of url we're constructing before acting accordingly. * Fix misc django deprecations * Use TOC extension instead of header * Fix deprecations for py3k * Add py3k compatibility to is_simple_callable * Add is_simple_callable tests * Drop python 3.2 support (EOL, Dropped by Django) * schema_renderers= should *set* the renderers, not append to them. * API client (#4424) * Fix release notes * Add note about 'User account is disabled.' vs 'Unable to log in' * Clean up schema generation (#4527) * Handle multiple methods on custom action (#4529) * RequestsClient, CoreAPIClient * exclude_from_schema * Added 'get_schema_view()' shortcut * Added schema descriptions * Better descriptions for schemas * Add type annotation to schema generation * Coerce schema 'pk' in path to actual field name * Deprecations move into assertion errors * Use get_schema_view in tests * Updte CoreJSON media type * Handle schema structure correctly when path prefixs exist. Closes #4401 * Add PendingDeprecation to Router schema generation. * Added SCHEMA_COERCE_PATH_PK and SCHEMA_COERCE_METHOD_NAMES * Renamed and documented 'get_schema_fields' interface.
2016-10-10 15:03:46 +03:00
assert is_simple_callable(valid)
2014-09-22 20:46:02 +04:00
2014-09-23 17:15:00 +04:00
# Tests for field keyword arguments and core functionality.
# ---------------------------------------------------------
2014-09-26 13:46:52 +04:00
class TestEmpty:
"""
Tests for `required`, `allow_null`, `allow_blank`, `default`.
"""
2014-09-23 17:15:00 +04:00
def test_required(self):
"""
By default a field must be included in the input.
"""
2014-10-17 16:23:14 +04:00
field = serializers.IntegerField()
with pytest.raises(serializers.ValidationError) as exc_info:
2014-09-23 17:15:00 +04:00
field.run_validation()
assert exc_info.value.detail == ['This field is required.']
2014-09-23 17:15:00 +04:00
def test_not_required(self):
"""
If `required=False` then a field may be omitted from the input.
"""
2014-10-17 16:23:14 +04:00
field = serializers.IntegerField(required=False)
with pytest.raises(serializers.SkipField):
2014-09-23 17:15:00 +04:00
field.run_validation()
def test_disallow_null(self):
"""
By default `None` is not a valid input.
"""
2014-10-17 16:23:14 +04:00
field = serializers.IntegerField()
with pytest.raises(serializers.ValidationError) as exc_info:
2014-09-23 17:15:00 +04:00
field.run_validation(None)
assert exc_info.value.detail == ['This field may not be null.']
2014-09-23 17:15:00 +04:00
def test_allow_null(self):
"""
If `allow_null=True` then `None` is a valid input.
"""
2014-10-17 16:23:14 +04:00
field = serializers.IntegerField(allow_null=True)
2014-09-23 17:15:00 +04:00
output = field.run_validation(None)
assert output is None
def test_disallow_blank(self):
"""
By default '' is not a valid input.
"""
2014-10-17 16:23:14 +04:00
field = serializers.CharField()
with pytest.raises(serializers.ValidationError) as exc_info:
2014-09-23 17:15:00 +04:00
field.run_validation('')
assert exc_info.value.detail == ['This field may not be blank.']
2014-09-23 17:15:00 +04:00
def test_allow_blank(self):
"""
If `allow_blank=True` then '' is a valid input.
"""
2014-10-17 16:23:14 +04:00
field = serializers.CharField(allow_blank=True)
2014-09-23 17:15:00 +04:00
output = field.run_validation('')
assert output == ''
2014-09-23 17:15:00 +04:00
def test_default(self):
"""
If `default` is set, then omitted values get the default input.
"""
2014-10-17 16:23:14 +04:00
field = serializers.IntegerField(default=123)
2014-09-23 17:15:00 +04:00
output = field.run_validation()
assert output == 123
2014-09-23 17:15:00 +04:00
2014-09-26 13:46:52 +04:00
class TestSource:
def test_source(self):
class ExampleSerializer(serializers.Serializer):
example_field = serializers.CharField(source='other')
serializer = ExampleSerializer(data={'example_field': 'abc'})
assert serializer.is_valid()
assert serializer.validated_data == {'other': 'abc'}
2014-09-24 23:53:37 +04:00
def test_redundant_source(self):
class ExampleSerializer(serializers.Serializer):
example_field = serializers.CharField(source='example_field')
with pytest.raises(AssertionError) as exc_info:
ExampleSerializer().fields
2014-09-24 23:53:37 +04:00
assert str(exc_info.value) == (
"It is redundant to specify `source='example_field'` on field "
"'CharField' in serializer 'ExampleSerializer', because it is the "
"same as the field name. Remove the `source` keyword argument."
2014-09-24 23:53:37 +04:00
)
def test_callable_source(self):
class ExampleSerializer(serializers.Serializer):
example_field = serializers.CharField(source='example_callable')
class ExampleInstance:
def example_callable(self):
return 'example callable value'
serializer = ExampleSerializer(ExampleInstance())
assert serializer.data['example_field'] == 'example callable value'
def test_callable_source_raises(self):
class ExampleSerializer(serializers.Serializer):
example_field = serializers.CharField(source='example_callable', read_only=True)
class ExampleInstance:
def example_callable(self):
raise AttributeError('method call failed')
with pytest.raises(ValueError) as exc_info:
serializer = ExampleSerializer(ExampleInstance())
serializer.data.items()
assert 'method call failed' in str(exc_info.value)
def test_builtin_callable_source_raises(self):
class BuiltinSerializer(serializers.Serializer):
date = serializers.ReadOnlyField(source='timestamp.date')
with pytest.raises(BuiltinSignatureError) as exc_info:
BuiltinSerializer({'timestamp': datetime.datetime.now()}).data
assert str(exc_info.value) == (
'Field source for `BuiltinSerializer.date` maps to a built-in '
'function type and is invalid. Define a property or method on '
'the `dict` instance that wraps the call to the built-in function.')
2014-09-23 17:15:00 +04:00
2014-09-26 13:46:52 +04:00
class TestReadOnly:
def setup_method(self):
2014-09-26 13:46:52 +04:00
class TestSerializer(serializers.Serializer):
read_only = serializers.ReadOnlyField(default="789")
2014-10-17 16:23:14 +04:00
writable = serializers.IntegerField()
2014-09-26 13:46:52 +04:00
self.Serializer = TestSerializer
def test_writable_fields(self):
"""
Read-only fields should not be writable, even with default ()
"""
serializer = self.Serializer()
assert len(list(serializer._writable_fields)) == 1
2014-09-26 13:46:52 +04:00
def test_validate_read_only(self):
"""
2014-10-17 16:23:14 +04:00
Read-only serializers.should not be included in validation.
2014-09-26 13:46:52 +04:00
"""
data = {'read_only': 123, 'writable': 456}
serializer = self.Serializer(data=data)
assert serializer.is_valid()
assert serializer.validated_data == {'writable': 456}
def test_serialize_read_only(self):
"""
2014-10-17 16:23:14 +04:00
Read-only serializers.should be serialized.
2014-09-26 13:46:52 +04:00
"""
instance = {'read_only': 123, 'writable': 456}
serializer = self.Serializer(instance)
assert serializer.data == {'read_only': 123, 'writable': 456}
class TestWriteOnly:
def setup_method(self):
2014-09-26 13:46:52 +04:00
class TestSerializer(serializers.Serializer):
2014-10-17 16:23:14 +04:00
write_only = serializers.IntegerField(write_only=True)
readable = serializers.IntegerField()
2014-09-26 13:46:52 +04:00
self.Serializer = TestSerializer
def test_validate_write_only(self):
"""
2014-10-17 16:23:14 +04:00
Write-only serializers.should be included in validation.
2014-09-26 13:46:52 +04:00
"""
data = {'write_only': 123, 'readable': 456}
serializer = self.Serializer(data=data)
assert serializer.is_valid()
assert serializer.validated_data == {'write_only': 123, 'readable': 456}
def test_serialize_write_only(self):
"""
2014-10-17 16:23:14 +04:00
Write-only serializers.should not be serialized.
2014-09-26 13:46:52 +04:00
"""
instance = {'write_only': 123, 'readable': 456}
serializer = self.Serializer(instance)
assert serializer.data == {'readable': 456}
class TestInitial:
def setup_method(self):
2014-09-26 13:46:52 +04:00
class TestSerializer(serializers.Serializer):
2014-10-17 16:23:14 +04:00
initial_field = serializers.IntegerField(initial=123)
blank_field = serializers.IntegerField()
2014-09-26 13:46:52 +04:00
self.serializer = TestSerializer()
def test_initial(self):
"""
Initial values should be included when serializing a new representation.
"""
assert self.serializer.data == {
'initial_field': 123,
'blank_field': None
}
class TestInitialWithCallable:
def setup_method(self):
def initial_value():
return 123
class TestSerializer(serializers.Serializer):
initial_field = serializers.IntegerField(initial=initial_value)
self.serializer = TestSerializer()
def test_initial_should_accept_callable(self):
"""
Follows the default ``Field.initial`` behaviour where they accept a
callable to produce the initial value"""
assert self.serializer.data == {
'initial_field': 123,
}
2014-09-26 13:46:52 +04:00
class TestLabel:
def setup_method(self):
2014-09-26 13:46:52 +04:00
class TestSerializer(serializers.Serializer):
2014-10-17 16:23:14 +04:00
labeled = serializers.IntegerField(label='My label')
2014-09-26 13:46:52 +04:00
self.serializer = TestSerializer()
def test_label(self):
"""
A field's label may be set with the `label` argument.
"""
fields = self.serializer.fields
assert fields['labeled'].label == 'My label'
class TestInvalidErrorKey:
def setup_method(self):
2014-09-26 13:46:52 +04:00
class ExampleField(serializers.Field):
def to_native(self, data):
self.fail('incorrect')
self.field = ExampleField()
def test_invalid_error_key(self):
"""
If a field raises a validation error, but does not have a corresponding
error message, then raise an appropriate assertion error.
"""
with pytest.raises(AssertionError) as exc_info:
self.field.to_native(123)
expected = (
2014-10-17 16:23:14 +04:00
'ValidationError raised by `ExampleField`, but error key '
2014-09-26 13:46:52 +04:00
'`incorrect` does not exist in the `error_messages` dictionary.'
)
assert str(exc_info.value) == expected
class TestBooleanHTMLInput:
def test_empty_html_checkbox(self):
"""
HTML checkboxes do not send any value, but should be treated
as `False` by BooleanField if allow_null=False.
"""
2014-09-26 13:46:52 +04:00
class TestSerializer(serializers.Serializer):
2014-10-17 16:23:14 +04:00
archived = serializers.BooleanField()
2014-09-26 13:46:52 +04:00
serializer = TestSerializer(data=QueryDict(''))
assert serializer.is_valid()
assert serializer.validated_data == {'archived': False}
def test_empty_html_checkbox_not_required(self):
2014-09-26 13:46:52 +04:00
"""
HTML checkboxes do not send any value, but should be treated
as `False` by BooleanField when the field is required=False
and allow_null=False.
2014-09-26 13:46:52 +04:00
"""
class TestSerializer(serializers.Serializer):
archived = serializers.BooleanField(required=False)
serializer = TestSerializer(data=QueryDict(''))
2014-09-26 13:46:52 +04:00
assert serializer.is_valid()
assert serializer.validated_data == {'archived': False}
def test_empty_html_checkbox_allow_null(self):
"""
HTML checkboxes do not send any value and should be treated
as `None` by BooleanField if allow_null is True.
"""
class TestSerializer(serializers.Serializer):
archived = serializers.BooleanField(allow_null=True)
serializer = TestSerializer(data=QueryDict(''))
assert serializer.is_valid()
assert serializer.validated_data == {'archived': None}
def test_empty_html_checkbox_allow_null_with_default(self):
"""
BooleanField should respect default if set and still allow
setting null values.
"""
class TestSerializer(serializers.Serializer):
archived = serializers.BooleanField(allow_null=True, default=True)
serializer = TestSerializer(data=QueryDict(''))
assert serializer.is_valid()
assert serializer.validated_data == {'archived': True}
serializer = TestSerializer(data=QueryDict('archived='))
assert serializer.is_valid()
assert serializer.validated_data == {'archived': None}
2014-09-26 13:46:52 +04:00
class TestHTMLInput:
def test_empty_html_charfield_with_default(self):
class TestSerializer(serializers.Serializer):
message = serializers.CharField(default='happy')
serializer = TestSerializer(data=QueryDict(''))
assert serializer.is_valid()
assert serializer.validated_data == {'message': 'happy'}
def test_empty_html_charfield_without_default(self):
class TestSerializer(serializers.Serializer):
message = serializers.CharField(allow_blank=True)
serializer = TestSerializer(data=QueryDict('message='))
assert serializer.is_valid()
assert serializer.validated_data == {'message': ''}
def test_empty_html_charfield_without_default_not_required(self):
class TestSerializer(serializers.Serializer):
message = serializers.CharField(allow_blank=True, required=False)
serializer = TestSerializer(data=QueryDict('message='))
assert serializer.is_valid()
assert serializer.validated_data == {'message': ''}
def test_empty_html_integerfield(self):
class TestSerializer(serializers.Serializer):
message = serializers.IntegerField(default=123)
serializer = TestSerializer(data=QueryDict('message='))
assert serializer.is_valid()
assert serializer.validated_data == {'message': 123}
def test_empty_html_uuidfield_with_default(self):
class TestSerializer(serializers.Serializer):
message = serializers.UUIDField(default=uuid.uuid4)
serializer = TestSerializer(data=QueryDict('message='))
assert serializer.is_valid()
assert list(serializer.validated_data) == ['message']
def test_empty_html_uuidfield_with_optional(self):
class TestSerializer(serializers.Serializer):
message = serializers.UUIDField(required=False)
serializer = TestSerializer(data=QueryDict('message='))
assert serializer.is_valid()
assert list(serializer.validated_data) == []
def test_empty_html_charfield_allow_null(self):
class TestSerializer(serializers.Serializer):
message = serializers.CharField(allow_null=True)
serializer = TestSerializer(data=QueryDict('message='))
assert serializer.is_valid()
assert serializer.validated_data == {'message': None}
def test_empty_html_datefield_allow_null(self):
class TestSerializer(serializers.Serializer):
expiry = serializers.DateField(allow_null=True)
serializer = TestSerializer(data=QueryDict('expiry='))
assert serializer.is_valid()
assert serializer.validated_data == {'expiry': None}
def test_empty_html_charfield_allow_null_allow_blank(self):
class TestSerializer(serializers.Serializer):
message = serializers.CharField(allow_null=True, allow_blank=True)
serializer = TestSerializer(data=QueryDict('message='))
assert serializer.is_valid()
assert serializer.validated_data == {'message': ''}
def test_empty_html_charfield_required_false(self):
class TestSerializer(serializers.Serializer):
message = serializers.CharField(required=False)
serializer = TestSerializer(data=QueryDict(''))
assert serializer.is_valid()
assert serializer.validated_data == {}
def test_querydict_list_input(self):
class TestSerializer(serializers.Serializer):
scores = serializers.ListField(child=serializers.IntegerField())
serializer = TestSerializer(data=QueryDict('scores=1&scores=3'))
assert serializer.is_valid()
assert serializer.validated_data == {'scores': [1, 3]}
def test_querydict_list_input_only_one_input(self):
class TestSerializer(serializers.Serializer):
scores = serializers.ListField(child=serializers.IntegerField())
serializer = TestSerializer(data=QueryDict('scores=1&'))
assert serializer.is_valid()
assert serializer.validated_data == {'scores': [1]}
def test_querydict_list_input_no_values_uses_default(self):
"""
When there are no values passed in, and default is set
The field should return the default value
"""
class TestSerializer(serializers.Serializer):
a = serializers.IntegerField(required=True)
scores = serializers.ListField(default=lambda: [1, 3])
serializer = TestSerializer(data=QueryDict('a=1&'))
assert serializer.is_valid()
assert serializer.validated_data == {'a': 1, 'scores': [1, 3]}
def test_querydict_list_input_supports_indexed_keys(self):
"""
When data is passed in the format `scores[0]=1&scores[1]=3`
The field should return the correct list, ignoring the default
"""
class TestSerializer(serializers.Serializer):
scores = serializers.ListField(default=lambda: [1, 3])
serializer = TestSerializer(data=QueryDict("scores[0]=5&scores[1]=6"))
assert serializer.is_valid()
assert serializer.validated_data == {'scores': ['5', '6']}
def test_querydict_list_input_no_values_no_default_and_not_required(self):
"""
When there are no keys passed, there is no default, and required=False
The field should be skipped
"""
class TestSerializer(serializers.Serializer):
scores = serializers.ListField(required=False)
serializer = TestSerializer(data=QueryDict(''))
assert serializer.is_valid()
assert serializer.validated_data == {}
def test_querydict_list_input_posts_key_but_no_values(self):
"""
When there are no keys passed, there is no default, and required=False
The field should return an array of 1 item, blank
"""
class TestSerializer(serializers.Serializer):
scores = serializers.ListField(required=False)
serializer = TestSerializer(data=QueryDict('scores=&'))
assert serializer.is_valid()
assert serializer.validated_data == {'scores': ['']}
class TestCreateOnlyDefault:
def setup_method(self):
default = serializers.CreateOnlyDefault('2001-01-01')
class TestSerializer(serializers.Serializer):
published = serializers.HiddenField(default=default)
text = serializers.CharField()
self.Serializer = TestSerializer
def test_create_only_default_is_provided(self):
serializer = self.Serializer(data={'text': 'example'})
assert serializer.is_valid()
assert serializer.validated_data == {
'text': 'example', 'published': '2001-01-01'
}
def test_create_only_default_is_not_provided_on_update(self):
instance = {
'text': 'example', 'published': '2001-01-01'
}
serializer = self.Serializer(instance, data={'text': 'example'})
assert serializer.is_valid()
assert serializer.validated_data == {
'text': 'example',
}
def test_create_only_default_callable_sets_context(self):
2015-03-01 00:06:47 +03:00
"""
CreateOnlyDefault instances with a callable default should set context
2015-03-01 00:06:47 +03:00
on the callable if possible
"""
class TestCallableDefault:
requires_context = True
def __call__(self, field=None):
return "success" if field is not None else "failure"
class TestSerializer(serializers.Serializer):
context_set = serializers.CharField(default=serializers.CreateOnlyDefault(TestCallableDefault()))
serializer = TestSerializer(data={})
assert serializer.is_valid()
assert serializer.validated_data['context_set'] == 'success'
class Test5087Regression:
def test_parent_binding(self):
parent = serializers.Serializer()
field = serializers.CharField()
assert field.root is field
field.bind('name', parent)
assert field.root is parent
class TestTyping(TestCase):
@pytest.mark.skipif(
sys.version_info < (3, 7),
reason="subscriptable classes requires Python 3.7 or higher",
)
def test_field_is_subscriptable(self):
assert serializers.Field is serializers.Field["foo"]
2014-09-23 17:15:00 +04:00
# Tests for field input and output values.
# ----------------------------------------
2014-09-22 20:46:02 +04:00
def get_items(mapping_or_list_of_two_tuples):
# Tests accept either lists of two tuples, or dictionaries.
if isinstance(mapping_or_list_of_two_tuples, dict):
# {value: expected}
return mapping_or_list_of_two_tuples.items()
# [(value, expected), ...]
return mapping_or_list_of_two_tuples
class FieldValues:
"""
Base class for testing valid and invalid input values.
"""
def test_valid_inputs(self, *args):
2014-09-22 20:46:02 +04:00
"""
Ensure that valid values return the expected validated data.
"""
for input_value, expected_output in get_items(self.valid_inputs):
assert self.field.run_validation(input_value) == expected_output, \
'input value: {}'.format(repr(input_value))
2014-09-22 20:46:02 +04:00
def test_invalid_inputs(self, *args):
2014-09-22 20:46:02 +04:00
"""
Ensure that invalid values raise the expected validation error.
"""
for input_value, expected_failure in get_items(self.invalid_inputs):
2014-10-17 16:23:14 +04:00
with pytest.raises(serializers.ValidationError) as exc_info:
2014-09-22 20:46:02 +04:00
self.field.run_validation(input_value)
assert exc_info.value.detail == expected_failure, \
'input value: {}'.format(repr(input_value))
2014-09-22 20:46:02 +04:00
def test_outputs(self, *args):
2014-09-22 20:46:02 +04:00
for output_value, expected_output in get_items(self.outputs):
assert self.field.to_representation(output_value) == expected_output, \
'output value: {}'.format(repr(output_value))
2014-09-22 20:46:02 +04:00
# Boolean types...
class TestBooleanField(FieldValues):
"""
Valid and invalid values for `BooleanField`.
"""
valid_inputs = {
'True': True,
'TRUE': True,
'tRuE': True,
't': True,
'T': True,
2014-09-22 20:46:02 +04:00
'true': True,
'on': True,
'ON': True,
'oN': True,
'False': False,
'FALSE': False,
'fALse': False,
'f': False,
'F': False,
2014-09-22 20:46:02 +04:00
'false': False,
'off': False,
'OFF': False,
'oFf': False,
2014-09-22 20:46:02 +04:00
'1': True,
'0': False,
1: True,
0: False,
True: True,
False: False,
}
invalid_inputs = {
'foo': ['Must be a valid boolean.'],
2014-09-23 17:30:17 +04:00
None: ['This field may not be null.']
2014-09-22 20:46:02 +04:00
}
outputs = {
'True': True,
'TRUE': True,
'tRuE': True,
't': True,
'T': True,
2014-09-22 20:46:02 +04:00
'true': True,
'on': True,
'ON': True,
'oN': True,
'False': False,
'FALSE': False,
'fALse': False,
'f': False,
'F': False,
2014-09-22 20:46:02 +04:00
'false': False,
'off': False,
'OFF': False,
'oFf': False,
2014-09-22 20:46:02 +04:00
'1': True,
'0': False,
1: True,
0: False,
True: True,
False: False,
'other': True
}
2014-10-17 16:23:14 +04:00
field = serializers.BooleanField()
2014-09-22 20:46:02 +04:00
def test_disallow_unhashable_collection_types(self):
inputs = (
[],
{},
)
field = self.field
for input_value in inputs:
with pytest.raises(serializers.ValidationError) as exc_info:
field.run_validation(input_value)
2020-09-05 11:02:27 +03:00
expected = ['Must be a valid boolean.']
assert exc_info.value.detail == expected
2014-09-22 20:46:02 +04:00
class TestNullableBooleanField(TestBooleanField):
2014-09-23 17:30:17 +04:00
"""
Valid and invalid values for `BooleanField` when `allow_null=True`.
2014-09-23 17:30:17 +04:00
"""
valid_inputs = {
'true': True,
'false': False,
'null': None,
True: True,
False: False,
None: None
}
invalid_inputs = {
'foo': ['Must be a valid boolean.'],
2014-09-23 17:30:17 +04:00
}
outputs = {
'true': True,
'false': False,
'null': None,
True: True,
False: False,
None: None,
'other': True
2014-09-23 17:30:17 +04:00
}
field = serializers.BooleanField(allow_null=True)
2014-09-23 17:30:17 +04:00
2014-09-22 20:46:02 +04:00
# String types...
class TestCharField(FieldValues):
"""
Valid and invalid values for `CharField`.
"""
valid_inputs = {
1: '1',
'abc': 'abc'
}
invalid_inputs = {
(): ['Not a valid string.'],
True: ['Not a valid string.'],
2014-09-22 20:46:02 +04:00
'': ['This field may not be blank.']
}
outputs = {
1: '1',
'abc': 'abc'
}
2014-10-17 16:23:14 +04:00
field = serializers.CharField()
2014-09-22 20:46:02 +04:00
def test_trim_whitespace_default(self):
field = serializers.CharField()
2015-02-06 17:43:43 +03:00
assert field.to_internal_value(' abc ') == 'abc'
def test_trim_whitespace_disabled(self):
field = serializers.CharField(trim_whitespace=False)
2015-02-06 17:43:43 +03:00
assert field.to_internal_value(' abc ') == ' abc '
def test_disallow_blank_with_trim_whitespace(self):
field = serializers.CharField(allow_blank=False, trim_whitespace=True)
with pytest.raises(serializers.ValidationError) as exc_info:
field.run_validation(' ')
assert exc_info.value.detail == ['This field may not be blank.']
def test_null_bytes(self):
field = serializers.CharField()
for value in ('\0', 'foo\0', '\0foo', 'foo\0foo'):
with pytest.raises(serializers.ValidationError) as exc_info:
field.run_validation(value)
assert exc_info.value.detail == [
'Null characters are not allowed.'
]
def test_surrogate_characters(self):
field = serializers.CharField()
for code_point, expected_message in (
(0xD800, 'Surrogate characters are not allowed: U+D800.'),
(0xDFFF, 'Surrogate characters are not allowed: U+DFFF.'),
):
with pytest.raises(serializers.ValidationError) as exc_info:
field.run_validation(chr(code_point))
assert exc_info.value.detail[0].code == 'surrogate_characters_not_allowed'
assert str(exc_info.value.detail[0]) == expected_message
for code_point in (0xD800 - 1, 0xDFFF + 1):
field.run_validation(chr(code_point))
def test_iterable_validators(self):
"""
Ensure `validators` parameter is compatible with reasonable iterables.
"""
value = 'example'
for validators in ([], (), set()):
field = serializers.CharField(validators=validators)
field.run_validation(value)
def raise_exception(value):
raise exceptions.ValidationError('Raised error')
for validators in ([raise_exception], (raise_exception,), {raise_exception}):
field = serializers.CharField(validators=validators)
with pytest.raises(serializers.ValidationError) as exc_info:
field.run_validation(value)
assert exc_info.value.detail == ['Raised error']
2014-09-22 20:46:02 +04:00
class TestEmailField(FieldValues):
"""
Valid and invalid values for `EmailField`.
"""
valid_inputs = {
'example@example.com': 'example@example.com',
' example@example.com ': 'example@example.com',
}
invalid_inputs = {
'examplecom': ['Enter a valid email address.']
}
outputs = {}
2014-10-17 16:23:14 +04:00
field = serializers.EmailField()
2014-09-22 20:46:02 +04:00
class TestRegexField(FieldValues):
"""
Valid and invalid values for `RegexField`.
"""
valid_inputs = {
'a9': 'a9',
}
invalid_inputs = {
'A9': ["This value does not match the required pattern."]
}
outputs = {}
2014-10-17 16:23:14 +04:00
field = serializers.RegexField(regex='[a-z][0-9]')
2014-09-22 20:46:02 +04:00
class TestiCompiledRegexField(FieldValues):
"""
Valid and invalid values for `RegexField`.
"""
valid_inputs = {
'a9': 'a9',
}
invalid_inputs = {
'A9': ["This value does not match the required pattern."]
}
outputs = {}
field = serializers.RegexField(regex=re.compile('[a-z][0-9]'))
2014-09-22 20:46:02 +04:00
class TestSlugField(FieldValues):
"""
Valid and invalid values for `SlugField`.
"""
valid_inputs = {
'slug-99': 'slug-99',
}
invalid_inputs = {
'slug 99': ['Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.']
2014-09-22 20:46:02 +04:00
}
outputs = {}
2014-10-17 16:23:14 +04:00
field = serializers.SlugField()
2014-09-22 20:46:02 +04:00
def test_allow_unicode_true(self):
field = serializers.SlugField(allow_unicode=True)
validation_error = False
try:
field.run_validation('slug-99-\u0420')
except serializers.ValidationError:
validation_error = True
assert not validation_error
2014-09-22 20:46:02 +04:00
class TestURLField(FieldValues):
"""
Valid and invalid values for `URLField`.
"""
valid_inputs = {
'http://example.com': 'http://example.com',
}
invalid_inputs = {
'example.com': ['Enter a valid URL.']
}
outputs = {}
2014-10-17 16:23:14 +04:00
field = serializers.URLField()
2014-09-22 20:46:02 +04:00
2015-01-23 18:24:06 +03:00
class TestUUIDField(FieldValues):
"""
Valid and invalid values for `UUIDField`.
"""
valid_inputs = {
'825d7aeb-05a9-45b5-a5b7-05df87923cda': uuid.UUID('825d7aeb-05a9-45b5-a5b7-05df87923cda'),
'825d7aeb05a945b5a5b705df87923cda': uuid.UUID('825d7aeb-05a9-45b5-a5b7-05df87923cda'),
'urn:uuid:213b7d9b-244f-410d-828c-dabce7a2615d': uuid.UUID('213b7d9b-244f-410d-828c-dabce7a2615d'),
284758210125106368185219588917561929842: uuid.UUID('d63a6fb6-88d5-40c7-a91c-9edf73283072')
2015-01-23 18:24:06 +03:00
}
invalid_inputs = {
'825d7aeb-05a9-45b5-a5b7': ['Must be a valid UUID.'],
(1, 2, 3): ['Must be a valid UUID.']
2015-01-23 18:24:06 +03:00
}
outputs = {
uuid.UUID('825d7aeb-05a9-45b5-a5b7-05df87923cda'): '825d7aeb-05a9-45b5-a5b7-05df87923cda'
}
field = serializers.UUIDField()
def _test_format(self, uuid_format, formatted_uuid_0):
field = serializers.UUIDField(format=uuid_format)
assert field.to_representation(uuid.UUID(int=0)) == formatted_uuid_0
assert field.to_internal_value(formatted_uuid_0) == uuid.UUID(int=0)
def test_formats(self):
self._test_format('int', 0)
self._test_format('hex_verbose', '00000000-0000-0000-0000-000000000000')
self._test_format('urn', 'urn:uuid:00000000-0000-0000-0000-000000000000')
self._test_format('hex', '0' * 32)
2015-01-23 18:24:06 +03:00
2015-02-28 10:11:38 +03:00
class TestIPAddressField(FieldValues):
"""
Valid and invalid values for `IPAddressField`
"""
valid_inputs = {
'127.0.0.1': '127.0.0.1',
'192.168.33.255': '192.168.33.255',
'2001:0db8:85a3:0042:1000:8a2e:0370:7334': '2001:db8:85a3:42:1000:8a2e:370:7334',
'2001:cdba:0:0:0:0:3257:9652': '2001:cdba::3257:9652',
'2001:cdba::3257:9652': '2001:cdba::3257:9652'
}
invalid_inputs = {
'127001': ['Enter a valid IPv4 or IPv6 address.'],
'127.122.111.2231': ['Enter a valid IPv4 or IPv6 address.'],
'2001:::9652': ['Enter a valid IPv4 or IPv6 address.'],
'2001:0db8:85a3:0042:1000:8a2e:0370:73341': ['Enter a valid IPv4 or IPv6 address.'],
1000: ['Enter a valid IPv4 or IPv6 address.'],
2015-02-28 10:11:38 +03:00
}
outputs = {}
field = serializers.IPAddressField()
class TestIPv4AddressField(FieldValues):
"""
Valid and invalid values for `IPAddressField`
"""
valid_inputs = {
'127.0.0.1': '127.0.0.1',
'192.168.33.255': '192.168.33.255',
}
invalid_inputs = {
'127001': ['Enter a valid IPv4 address.'],
'127.122.111.2231': ['Enter a valid IPv4 address.'],
}
outputs = {}
field = serializers.IPAddressField(protocol='IPv4')
class TestIPv6AddressField(FieldValues):
"""
Valid and invalid values for `IPAddressField`
"""
valid_inputs = {
'2001:0db8:85a3:0042:1000:8a2e:0370:7334': '2001:db8:85a3:42:1000:8a2e:370:7334',
'2001:cdba:0:0:0:0:3257:9652': '2001:cdba::3257:9652',
'2001:cdba::3257:9652': '2001:cdba::3257:9652'
}
invalid_inputs = {
'2001:::9652': ['Enter a valid IPv4 or IPv6 address.'],
'2001:0db8:85a3:0042:1000:8a2e:0370:73341': ['Enter a valid IPv4 or IPv6 address.'],
}
outputs = {}
field = serializers.IPAddressField(protocol='IPv6')
2015-03-23 02:21:09 +03:00
2015-03-03 14:34:06 +03:00
class TestFilePathField(FieldValues):
"""
Valid and invalid values for `FilePathField`
"""
valid_inputs = {
__file__: __file__,
}
invalid_inputs = {
'wrong_path': ['"wrong_path" is not a valid path choice.']
}
outputs = {
}
field = serializers.FilePathField(
path=os.path.abspath(os.path.dirname(__file__))
)
class TestAlphabeticField:
valid_inputs = {
'John Doe': 'John Doe',
'Alice': 'Alice',
'Bob Marley': 'Bob Marley',
}
invalid_inputs = {
'John123': ['This field must contain only alphabetic characters and spaces.'],
'Alice!': ['This field must contain only alphabetic characters and spaces.'],
'': ['This field must contain only alphabetic characters and spaces.'],
}
non_string_inputs = [
123, # Integer
45.67, # Float
None, # NoneType
[], # Empty list
{}, # Empty dict
set() # Empty set
]
def test_valid_inputs(self):
validator = AlphabeticFieldValidator()
for value in self.valid_inputs.keys():
validator(value)
def test_invalid_inputs(self):
validator = AlphabeticFieldValidator()
for value, expected_errors in self.invalid_inputs.items():
with pytest.raises(ValueError) as excinfo:
validator(value)
assert str(excinfo.value) == expected_errors[0]
def test_non_string_inputs(self):
validator = AlphabeticFieldValidator()
for value in self.non_string_inputs:
with pytest.raises(ValueError) as excinfo:
validator(value)
assert str(excinfo.value) == "This field must be a string."
class TestAlphanumericField:
valid_inputs = {
'John123': 'John123',
'Alice007': 'Alice007',
'Bob1990': 'Bob1990',
}
invalid_inputs = {
'John!': ['This field must contain only alphanumeric characters (letters and numbers).'],
'Alice 007': ['This field must contain only alphanumeric characters (letters and numbers).'],
'': ['This field must contain only alphanumeric characters (letters and numbers).'],
}
non_string_inputs = [
123, # Integer
45.67, # Float
None, # NoneType
[], # Empty list
{}, # Empty dict
set() # Empty set
]
def test_valid_inputs(self):
validator = AlphanumericFieldValidator()
for value in self.valid_inputs.keys():
validator(value)
def test_invalid_inputs(self):
validator = AlphanumericFieldValidator()
for value, expected_errors in self.invalid_inputs.items():
with pytest.raises(ValueError) as excinfo:
validator(value)
assert str(excinfo.value) == expected_errors[0]
def test_non_string_inputs(self):
validator = AlphanumericFieldValidator()
for value in self.non_string_inputs:
with pytest.raises(ValueError) as excinfo:
validator(value)
assert str(excinfo.value) == "This field must be a string."
class TestCustomLengthField:
"""
Valid and invalid values for `CustomLengthValidator`.
"""
valid_inputs = {
'abc': 'abc', # 3 characters
'abcdefghij': 'abcdefghij', # 10 characters
}
invalid_inputs = {
'ab': ['This field must be at least 3 characters long.'], # Too short
'abcdefghijk': ['This field must be no more than 10 characters long.'], # Too long
}
field = str
def test_valid_inputs(self):
validator = CustomLengthValidator(min_length=3, max_length=10)
for value in self.valid_inputs.keys():
validator(value)
def test_invalid_inputs(self):
validator = CustomLengthValidator(min_length=3, max_length=10)
for value, expected_errors in self.invalid_inputs.items():
with pytest.raises(ValueError) as excinfo:
validator(value)
assert str(excinfo.value) == expected_errors[0]
2014-09-22 20:46:02 +04:00
# Number types...
class TestIntegerField(FieldValues):
"""
Valid and invalid values for `IntegerField`.
"""
valid_inputs = {
'1': 1,
'0': 0,
1: 1,
0: 0,
1.0: 1,
0.0: 0,
'1.0': 1
2014-09-22 20:46:02 +04:00
}
invalid_inputs = {
0.5: ['A valid integer is required.'],
'abc': ['A valid integer is required.'],
'0.5': ['A valid integer is required.']
2014-09-22 20:46:02 +04:00
}
outputs = {
'1': 1,
'0': 0,
1: 1,
0: 0,
1.0: 1,
0.0: 0
}
2014-10-17 16:23:14 +04:00
field = serializers.IntegerField()
2014-09-22 20:46:02 +04:00
class TestMinMaxIntegerField(FieldValues):
"""
Valid and invalid values for `IntegerField` with min and max limits.
"""
valid_inputs = {
'1': 1,
'3': 3,
1: 1,
3: 3,
}
invalid_inputs = {
0: ['Ensure this value is greater than or equal to 1.'],
4: ['Ensure this value is less than or equal to 3.'],
'0': ['Ensure this value is greater than or equal to 1.'],
'4': ['Ensure this value is less than or equal to 3.'],
}
outputs = {}
2014-10-17 16:23:14 +04:00
field = serializers.IntegerField(min_value=1, max_value=3)
2014-09-22 20:46:02 +04:00
class TestFloatField(FieldValues):
"""
Valid and invalid values for `FloatField`.
"""
valid_inputs = {
'1': 1.0,
'0': 0.0,
1: 1.0,
0: 0.0,
1.0: 1.0,
0.0: 0.0,
}
invalid_inputs = {
'abc': ["A valid number is required."]
}
outputs = {
'1': 1.0,
'0': 0.0,
1: 1.0,
0: 0.0,
1.0: 1.0,
0.0: 0.0,
}
2014-10-17 16:23:14 +04:00
field = serializers.FloatField()
2014-09-22 20:46:02 +04:00
class TestMinMaxFloatField(FieldValues):
"""
Valid and invalid values for `FloatField` with min and max limits.
"""
valid_inputs = {
'1': 1,
'3': 3,
1: 1,
3: 3,
1.0: 1.0,
3.0: 3.0,
}
invalid_inputs = {
0.9: ['Ensure this value is greater than or equal to 1.'],
3.1: ['Ensure this value is less than or equal to 3.'],
'0.0': ['Ensure this value is greater than or equal to 1.'],
'3.1': ['Ensure this value is less than or equal to 3.'],
}
outputs = {}
2014-10-17 16:23:14 +04:00
field = serializers.FloatField(min_value=1, max_value=3)
2014-09-22 20:46:02 +04:00
class TestFloatFieldOverFlowError(TestCase):
def test_overflow_error_float_field(self):
field = serializers.FloatField()
with pytest.raises(serializers.ValidationError) as exec_info:
field.to_internal_value(data=math.factorial(171))
assert "Integer value too large to convert to float" in str(exec_info.value.detail)
2014-09-22 20:46:02 +04:00
class TestDecimalField(FieldValues):
"""
Valid and invalid values for `DecimalField`.
"""
valid_inputs = {
'12.3': Decimal('12.3'),
'0.1': Decimal('0.1'),
10: Decimal('10'),
0: Decimal('0'),
12.3: Decimal('12.3'),
0.1: Decimal('0.1'),
'2E+1': Decimal('20'),
2014-09-22 20:46:02 +04:00
}
invalid_inputs = (
(None, ["This field may not be null."]),
('', ["A valid number is required."]),
(' ', ["A valid number is required."]),
2014-09-22 20:46:02 +04:00
('abc', ["A valid number is required."]),
(Decimal('Nan'), ["A valid number is required."]),
(Decimal('Snan'), ["A valid number is required."]),
2014-09-22 20:46:02 +04:00
(Decimal('Inf'), ["A valid number is required."]),
('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."]),
2014-09-22 20:46:02 +04:00
('0.01', ["Ensure that there are no more than 1 decimal places."]),
(123, ["Ensure that there are no more than 2 digits before the decimal point."]),
('2E+2', ["Ensure that there are no more than 2 digits before the decimal point."])
2014-09-22 20:46:02 +04:00
)
outputs = {
'1': '1.0',
'0': '0.0',
'1.09': '1.1',
'0.04': '0.0',
1: '1.0',
0: '0.0',
Decimal('1.0'): '1.0',
Decimal('0.0'): '0.0',
Decimal('1.09'): '1.1',
2014-09-26 20:06:20 +04:00
Decimal('0.04'): '0.0'
2014-09-22 20:46:02 +04:00
}
2014-10-17 16:23:14 +04:00
field = serializers.DecimalField(max_digits=3, decimal_places=1)
2014-09-22 20:46:02 +04:00
class TestAllowNullDecimalField(FieldValues):
valid_inputs = {
None: None,
'': None,
' ': None,
}
invalid_inputs = {}
outputs = {
None: '',
}
field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True)
class TestAllowNullNoStringCoercionDecimalField(FieldValues):
valid_inputs = {
None: None,
'': None,
' ': None,
}
invalid_inputs = {}
outputs = {
None: None,
}
field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True, coerce_to_string=False)
2014-09-22 20:46:02 +04:00
class TestMinMaxDecimalField(FieldValues):
"""
Valid and invalid values for `DecimalField` with min and max limits.
"""
valid_inputs = {
'10.0': Decimal('10.0'),
'20.0': Decimal('20.0'),
}
invalid_inputs = {
'9.9': ['Ensure this value is greater than or equal to 10.0.'],
'20.1': ['Ensure this value is less than or equal to 20.0.'],
2014-09-22 20:46:02 +04:00
}
outputs = {}
2014-10-17 16:23:14 +04:00
field = serializers.DecimalField(
2014-09-22 20:46:02 +04:00
max_digits=3, decimal_places=1,
min_value=10.0, max_value=20.0
2014-09-22 20:46:02 +04:00
)
def test_warning_when_not_decimal_types(self, caplog):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
serializers.DecimalField(
max_digits=3, decimal_places=1,
min_value=10.0, max_value=20.0
)
assert len(w) == 2
assert all(issubclass(i.category, UserWarning) for i in w)
assert 'max_value should be an integer or Decimal instance' in str(w[0].message)
assert 'min_value should be an integer or Decimal instance' in str(w[1].message)
2014-09-22 20:46:02 +04:00
class TestAllowEmptyStrDecimalFieldWithValidators(FieldValues):
"""
Check that empty string ('', ' ') is acceptable value for the DecimalField
if allow_null=True and there are max/min validators
"""
valid_inputs = {
None: None,
'': None,
' ': None,
' ': None,
5: Decimal('5'),
'0': Decimal('0'),
'10': Decimal('10'),
}
invalid_inputs = {
-1: ['Ensure this value is greater than or equal to 0.'],
11: ['Ensure this value is less than or equal to 10.'],
}
outputs = {
None: '',
}
field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True, min_value=0, max_value=10)
class TestNoMaxDigitsDecimalField(FieldValues):
field = serializers.DecimalField(
max_value=100, min_value=0,
decimal_places=2, max_digits=None
)
valid_inputs = {
'10': Decimal('10.00')
}
invalid_inputs = {}
outputs = {}
2014-09-22 20:46:02 +04:00
class TestNoStringCoercionDecimalField(FieldValues):
"""
Output values for `DecimalField` with `coerce_to_string=False`.
"""
valid_inputs = {}
invalid_inputs = {}
outputs = {
1.09: Decimal('1.1'),
0.04: Decimal('0.0'),
'1.09': Decimal('1.1'),
'0.04': Decimal('0.0'),
Decimal('1.09'): Decimal('1.1'),
Decimal('0.04'): Decimal('0.0'),
}
2014-10-17 16:23:14 +04:00
field = serializers.DecimalField(
2014-09-22 20:46:02 +04:00
max_digits=3, decimal_places=1,
coerce_to_string=False
)
class TestLocalizedDecimalField(TestCase):
@override_settings(LANGUAGE_CODE='pl')
def test_to_internal_value(self):
field = serializers.DecimalField(max_digits=2, decimal_places=1, localize=True)
assert field.to_internal_value('1,1') == Decimal('1.1')
@override_settings(LANGUAGE_CODE='pl')
def test_to_representation(self):
field = serializers.DecimalField(max_digits=2, decimal_places=1, localize=True)
assert field.to_representation(Decimal('1.1')) == '1,1'
def test_localize_forces_coerce_to_string(self):
field = serializers.DecimalField(max_digits=2, decimal_places=1, coerce_to_string=False, localize=True)
assert isinstance(field.to_representation(Decimal('1.1')), str)
2016-08-01 19:15:41 +03:00
class TestQuantizedValueForDecimal(TestCase):
def test_int_quantized_value_for_decimal(self):
field = serializers.DecimalField(max_digits=4, decimal_places=2)
value = field.to_internal_value(12).as_tuple()
expected_digit_tuple = (0, (1, 2, 0, 0), -2)
assert value == expected_digit_tuple
2016-08-01 19:15:41 +03:00
def test_string_quantized_value_for_decimal(self):
field = serializers.DecimalField(max_digits=4, decimal_places=2)
value = field.to_internal_value('12').as_tuple()
expected_digit_tuple = (0, (1, 2, 0, 0), -2)
assert value == expected_digit_tuple
2016-08-01 19:15:41 +03:00
def test_part_precision_string_quantized_value_for_decimal(self):
field = serializers.DecimalField(max_digits=4, decimal_places=2)
value = field.to_internal_value('12.0').as_tuple()
expected_digit_tuple = (0, (1, 2, 0, 0), -2)
assert value == expected_digit_tuple
2016-08-01 19:15:41 +03:00
class TestNormalizedOutputValueDecimalField(TestCase):
"""
Test that we get the expected behavior of on DecimalField when normalize=True
"""
def test_normalize_output(self):
field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize_output=True)
output = field.to_representation(Decimal('1.000'))
assert output == '1'
def test_non_normalize_output(self):
field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize_output=False)
output = field.to_representation(Decimal('1.000'))
assert output == '1.000'
def test_normalize_coeherce_to_string(self):
field = serializers.DecimalField(max_digits=4, decimal_places=3, normalize_output=True, coerce_to_string=False)
output = field.to_representation(Decimal('1.000'))
assert output == Decimal('1')
class TestNoDecimalPlaces(FieldValues):
valid_inputs = {
'0.12345': Decimal('0.12345'),
}
invalid_inputs = {
'0.1234567': ['Ensure that there are no more than 6 digits in total.']
}
outputs = {
'1.2345': '1.2345',
'0': '0',
'1.1': '1.1',
}
field = serializers.DecimalField(max_digits=6, decimal_places=None)
class TestRoundingDecimalField(TestCase):
def test_valid_rounding(self):
field = serializers.DecimalField(max_digits=4, decimal_places=2, rounding=ROUND_UP)
assert field.to_representation(Decimal('1.234')) == '1.24'
field = serializers.DecimalField(max_digits=4, decimal_places=2, rounding=ROUND_DOWN)
assert field.to_representation(Decimal('1.234')) == '1.23'
def test_invalid_rounding(self):
with pytest.raises(AssertionError) as excinfo:
serializers.DecimalField(max_digits=1, decimal_places=1, rounding='ROUND_UNKNOWN')
assert 'Invalid rounding option' in str(excinfo.value)
2014-09-22 20:46:02 +04:00
# Date & time serializers...
2014-09-22 20:46:02 +04:00
class TestDateField(FieldValues):
"""
Valid and invalid values for `DateField`.
"""
valid_inputs = {
'2001-01-01': datetime.date(2001, 1, 1),
datetime.date(2001, 1, 1): datetime.date(2001, 1, 1),
}
invalid_inputs = {
'abc': ['Date has wrong format. Use one of these formats instead: YYYY-MM-DD.'],
'2001-99-99': ['Date has wrong format. Use one of these formats instead: YYYY-MM-DD.'],
'2001-01': ['Date has wrong format. Use one of these formats instead: YYYY-MM-DD.'],
'2001': ['Date has wrong format. Use one of these formats instead: YYYY-MM-DD.'],
2014-09-22 20:46:02 +04:00
datetime.datetime(2001, 1, 1, 12, 00): ['Expected a date but got a datetime.'],
}
outputs = {
datetime.date(2001, 1, 1): '2001-01-01',
'2001-01-01': '2001-01-01',
str('2016-01-10'): '2016-01-10',
None: None,
'': None,
2014-09-22 20:46:02 +04:00
}
2014-10-17 16:23:14 +04:00
field = serializers.DateField()
2014-09-22 20:46:02 +04:00
class TestCustomInputFormatDateField(FieldValues):
"""
2016-08-08 11:32:22 +03:00
Valid and invalid values for `DateField` with a custom input format.
2014-09-22 20:46:02 +04:00
"""
valid_inputs = {
'1 Jan 2001': datetime.date(2001, 1, 1),
}
invalid_inputs = {
'2001-01-01': ['Date has wrong format. Use one of these formats instead: DD [Jan-Dec] YYYY.']
2014-09-22 20:46:02 +04:00
}
outputs = {}
2014-10-17 16:23:14 +04:00
field = serializers.DateField(input_formats=['%d %b %Y'])
2014-09-22 20:46:02 +04:00
class TestCustomOutputFormatDateField(FieldValues):
"""
Values for `DateField` with a custom output format.
"""
valid_inputs = {}
invalid_inputs = {}
outputs = {
datetime.date(2001, 1, 1): '01 Jan 2001'
}
2014-10-17 16:23:14 +04:00
field = serializers.DateField(format='%d %b %Y')
2014-09-22 20:46:02 +04:00
class TestNoOutputFormatDateField(FieldValues):
"""
Values for `DateField` with no output format.
"""
valid_inputs = {}
invalid_inputs = {}
outputs = {
datetime.date(2001, 1, 1): datetime.date(2001, 1, 1)
}
2014-10-17 16:23:14 +04:00
field = serializers.DateField(format=None)
2014-09-22 20:46:02 +04:00
class TestDateTimeField(FieldValues):
"""
Valid and invalid values for `DateTimeField`.
"""
valid_inputs = {
2017-01-18 17:01:18 +03:00
'2001-01-01 13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
'2001-01-01T13:00': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
'2001-01-01T13:00Z': datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
2017-01-18 17:01:18 +03:00
datetime.datetime(2001, 1, 1, 13, 00): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc),
2014-09-22 20:46:02 +04:00
}
invalid_inputs = {
'abc': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
'2001-99-99T99:00': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
'2018-08-16 22:00-24:00': ['Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'],
2014-09-22 20:46:02 +04:00
datetime.date(2001, 1, 1): ['Expected a datetime but got a date.'],
'9999-12-31T21:59:59.99990-03:00': ['Datetime value out of range.'],
2014-09-22 20:46:02 +04:00
}
outputs = {
datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00Z',
2017-01-18 17:01:18 +03:00
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): '2001-01-01T13:00:00Z',
'2001-01-01T00:00:00': '2001-01-01T00:00:00',
str('2016-01-10T00:00:00'): '2016-01-10T00:00:00',
None: None,
'': None,
2014-09-22 20:46:02 +04:00
}
2017-01-18 17:01:18 +03:00
field = serializers.DateTimeField(default_timezone=utc)
2014-09-22 20:46:02 +04:00
class TestCustomInputFormatDateTimeField(FieldValues):
"""
2016-08-08 11:32:22 +03:00
Valid and invalid values for `DateTimeField` with a custom input format.
2014-09-22 20:46:02 +04:00
"""
valid_inputs = {
2017-01-18 17:01:18 +03:00
'1:35pm, 1 Jan 2001': datetime.datetime(2001, 1, 1, 13, 35, tzinfo=utc),
2014-09-22 20:46:02 +04:00
}
invalid_inputs = {
'2001-01-01T20:50': ['Datetime has wrong format. Use one of these formats instead: hh:mm[AM|PM], DD [Jan-Dec] YYYY.']
2014-09-22 20:46:02 +04:00
}
outputs = {}
2017-01-18 17:01:18 +03:00
field = serializers.DateTimeField(default_timezone=utc, input_formats=['%I:%M%p, %d %b %Y'])
2014-09-22 20:46:02 +04:00
class TestCustomOutputFormatDateTimeField(FieldValues):
"""
Values for `DateTimeField` with a custom output format.
"""
valid_inputs = {}
invalid_inputs = {}
outputs = {
datetime.datetime(2001, 1, 1, 13, 00): '01:00PM, 01 Jan 2001',
}
2014-10-17 16:23:14 +04:00
field = serializers.DateTimeField(format='%I:%M%p, %d %b %Y')
2014-09-22 20:46:02 +04:00
class TestNoOutputFormatDateTimeField(FieldValues):
"""
Values for `DateTimeField` with no output format.
"""
valid_inputs = {}
invalid_inputs = {}
outputs = {
datetime.datetime(2001, 1, 1, 13, 00): datetime.datetime(2001, 1, 1, 13, 00),
}
2014-10-17 16:23:14 +04:00
field = serializers.DateTimeField(format=None)
2014-09-22 20:46:02 +04:00
@override_settings(TIME_ZONE='UTC', USE_TZ=False)
class TestNaiveDateTimeField(FieldValues, TestCase):
2014-09-22 20:46:02 +04:00
"""
Valid and invalid values for `DateTimeField` with naive datetimes.
"""
valid_inputs = {
2017-01-18 17:01:18 +03:00
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): datetime.datetime(2001, 1, 1, 13, 00),
2014-09-22 20:46:02 +04:00
'2001-01-01 13:00': datetime.datetime(2001, 1, 1, 13, 00),
}
invalid_inputs = {}
outputs = {
datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00',
datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): '2001-01-01T13:00:00',
}
2014-10-17 16:23:14 +04:00
field = serializers.DateTimeField(default_timezone=None)
2014-09-22 20:46:02 +04:00
class TestTZWithDateTimeField(FieldValues):
"""
Valid and invalid values for `DateTimeField` when not using UTC as the timezone.
"""
@classmethod
def setup_class(cls):
# use class setup method, as class-level attribute will still be evaluated even if test is skipped
kolkata = ZoneInfo('Asia/Kolkata')
cls.valid_inputs = {
'2016-12-19T10:00:00': datetime.datetime(2016, 12, 19, 10, tzinfo=kolkata),
'2016-12-19T10:00:00+05:30': datetime.datetime(2016, 12, 19, 10, tzinfo=kolkata),
datetime.datetime(2016, 12, 19, 10): datetime.datetime(2016, 12, 19, 10, tzinfo=kolkata),
}
cls.invalid_inputs = {}
cls.outputs = {
datetime.datetime(2016, 12, 19, 10): '2016-12-19T10:00:00+05:30',
datetime.datetime(2016, 12, 19, 4, 30, tzinfo=utc): '2016-12-19T10:00:00+05:30',
}
cls.field = serializers.DateTimeField(default_timezone=kolkata)
@override_settings(TIME_ZONE='UTC', USE_TZ=True)
class TestDefaultTZDateTimeField(TestCase):
"""
Test the current/default timezone handling in `DateTimeField`.
"""
@classmethod
def setup_class(cls):
cls.field = serializers.DateTimeField()
cls.kolkata = ZoneInfo('Asia/Kolkata')
def assertUTC(self, tzinfo):
"""
Check UTC for datetime.timezone, ZoneInfo, and pytz tzinfo instances.
"""
assert (
tzinfo is utc or
(getattr(tzinfo, "key", None) or getattr(tzinfo, "zone", None)) == "UTC"
)
def test_default_timezone(self):
self.assertUTC(self.field.default_timezone())
def test_current_timezone(self):
self.assertUTC(self.field.default_timezone())
activate(self.kolkata)
assert self.field.default_timezone() == self.kolkata
deactivate()
self.assertUTC(self.field.default_timezone())
@override_settings(TIME_ZONE='UTC', USE_TZ=True)
class TestCustomTimezoneForDateTimeField(TestCase):
@classmethod
def setup_class(cls):
cls.kolkata = ZoneInfo('Asia/Kolkata')
cls.date_format = '%d/%m/%Y %H:%M'
def test_should_render_date_time_in_default_timezone(self):
field = serializers.DateTimeField(default_timezone=self.kolkata, format=self.date_format)
dt = datetime.datetime(2018, 2, 8, 14, 15, 16, tzinfo=ZoneInfo("UTC"))
with override(self.kolkata):
rendered_date = field.to_representation(dt)
rendered_date_in_timezone = dt.astimezone(self.kolkata).strftime(self.date_format)
assert rendered_date == rendered_date_in_timezone
2024-04-26 17:50:40 +03:00
@pytest.mark.skipif(pytz is None, reason="Django 5.0 has removed pytz; this test should eventually be able to get removed.")
class TestPytzNaiveDayLightSavingTimeTimeZoneDateTimeField(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-21 01:47:25 +03:00
'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 = {}
if pytz:
class MockTimezone(pytz.BaseTzInfo):
@staticmethod
def localize(value, is_dst):
raise pytz.InvalidTimeError()
def __str__(self):
return 'America/New_York'
field = serializers.DateTimeField(default_timezone=MockTimezone())
@patch('rest_framework.utils.timezone.datetime_ambiguous', return_value=True)
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 MockZoneInfoTimezone(datetime.tzinfo):
def __str__(self):
return 'America/New_York'
field = serializers.DateTimeField(default_timezone=MockZoneInfoTimezone())
2014-09-22 20:46:02 +04:00
class TestTimeField(FieldValues):
"""
Valid and invalid values for `TimeField`.
"""
valid_inputs = {
'13:00': datetime.time(13, 00),
datetime.time(13, 00): datetime.time(13, 00),
}
invalid_inputs = {
'abc': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].'],
'99:99': ['Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]].'],
2014-09-22 20:46:02 +04:00
}
outputs = {
datetime.time(13, 0): '13:00:00',
datetime.time(0, 0): '00:00:00',
'00:00:00': '00:00:00',
2015-12-14 02:09:56 +03:00
None: None,
'': None,
2014-09-22 20:46:02 +04:00
}
2014-10-17 16:23:14 +04:00
field = serializers.TimeField()
2014-09-22 20:46:02 +04:00
class TestCustomInputFormatTimeField(FieldValues):
"""
Valid and invalid values for `TimeField` with a custom input format.
"""
valid_inputs = {
'1:00pm': datetime.time(13, 00),
}
invalid_inputs = {
'13:00': ['Time has wrong format. Use one of these formats instead: hh:mm[AM|PM].'],
2014-09-22 20:46:02 +04:00
}
outputs = {}
2014-10-17 16:23:14 +04:00
field = serializers.TimeField(input_formats=['%I:%M%p'])
2014-09-22 20:46:02 +04:00
class TestCustomOutputFormatTimeField(FieldValues):
"""
Values for `TimeField` with a custom output format.
"""
valid_inputs = {}
invalid_inputs = {}
outputs = {
datetime.time(13, 00): '01:00PM'
}
2014-10-17 16:23:14 +04:00
field = serializers.TimeField(format='%I:%M%p')
2014-09-22 20:46:02 +04:00
class TestNoOutputFormatTimeField(FieldValues):
"""
Values for `TimeField` with a no output format.
"""
valid_inputs = {}
invalid_inputs = {}
outputs = {
datetime.time(13, 00): datetime.time(13, 00)
}
2014-10-17 16:23:14 +04:00
field = serializers.TimeField(format=None)
2014-09-22 20:46:02 +04:00
class TestMinMaxDurationField(FieldValues):
"""
Valid and invalid values for `DurationField` with min and max limits.
"""
valid_inputs = {
'3 08:32:01.000123': datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123),
86401: datetime.timedelta(days=1, seconds=1),
}
invalid_inputs = {
3600: ['Ensure this value is greater than or equal to 1 day, 0:00:00.'],
'4 08:32:01.000123': ['Ensure this value is less than or equal to 4 days, 0:00:00.'],
'3600': ['Ensure this value is greater than or equal to 1 day, 0:00:00.'],
}
outputs = {}
field = serializers.DurationField(min_value=datetime.timedelta(days=1), max_value=datetime.timedelta(days=4))
2015-06-01 19:20:53 +03:00
class TestDurationField(FieldValues):
"""
Valid and invalid values for `DurationField`.
"""
valid_inputs = {
'13': datetime.timedelta(seconds=13),
'3 08:32:01.000123': datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123),
'08:01': datetime.timedelta(minutes=8, seconds=1),
datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123): datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123),
2015-09-03 02:47:50 +03:00
3600: datetime.timedelta(hours=1),
'-999999999 00': datetime.timedelta(days=-999999999),
'999999999 00': datetime.timedelta(days=999999999),
2015-06-01 19:20:53 +03:00
}
invalid_inputs = {
'abc': ['Duration has wrong format. Use one of these formats instead: [DD] [HH:[MM:]]ss[.uuuuuu].'],
'3 08:32 01.123': ['Duration has wrong format. Use one of these formats instead: [DD] [HH:[MM:]]ss[.uuuuuu].'],
'-1000000000 00': ['The number of days must be between -999999999 and 999999999.'],
'1000000000 00': ['The number of days must be between -999999999 and 999999999.'],
2015-06-01 19:20:53 +03:00
}
outputs = {
datetime.timedelta(days=3, hours=8, minutes=32, seconds=1, microseconds=123): '3 08:32:01.000123',
}
field = serializers.DurationField()
2015-06-01 19:20:53 +03:00
2014-09-22 20:46:02 +04:00
# Choice types...
class TestChoiceField(FieldValues):
"""
Valid and invalid values for `ChoiceField`.
"""
valid_inputs = {
'poor': 'poor',
'medium': 'medium',
'good': 'good',
}
invalid_inputs = {
'amazing': ['"amazing" is not a valid choice.']
2014-09-22 20:46:02 +04:00
}
outputs = {
'good': 'good',
'': '',
'amazing': 'amazing',
2014-09-22 20:46:02 +04:00
}
2014-10-17 16:23:14 +04:00
field = serializers.ChoiceField(
2014-09-22 20:46:02 +04:00
choices=[
('poor', 'Poor quality'),
('medium', 'Medium quality'),
('good', 'Good quality'),
]
)
def test_allow_blank(self):
"""
If `allow_blank=True` then '' is a valid input.
"""
field = serializers.ChoiceField(
allow_blank=True,
choices=[
('poor', 'Poor quality'),
('medium', 'Medium quality'),
('good', 'Good quality'),
]
)
output = field.run_validation('')
assert output == ''
def test_allow_null(self):
"""
If `allow_null=True` then '' on HTML forms is treated as None.
"""
field = serializers.ChoiceField(
allow_null=True,
choices=[
1, 2, 3
]
)
field.field_name = 'example'
value = field.get_value(QueryDict('example='))
assert value is None
output = field.run_validation(None)
assert output is None
2015-08-06 14:18:09 +03:00
def test_iter_options(self):
"""
iter_options() should return a list of options and option groups.
"""
field = serializers.ChoiceField(
choices=[
('Numbers', ['integer', 'float']),
('Strings', ['text', 'email', 'url']),
'boolean'
2015-08-06 14:18:09 +03:00
]
)
items = list(field.iter_options())
assert items[0].start_option_group
assert items[0].label == 'Numbers'
assert items[1].value == 'integer'
assert items[2].value == 'float'
assert items[3].end_option_group
assert items[4].start_option_group
assert items[4].label == 'Strings'
assert items[5].value == 'text'
assert items[6].value == 'email'
assert items[7].value == 'url'
assert items[8].end_option_group
assert items[9].value == 'boolean'
def test_edit_choices(self):
field = serializers.ChoiceField(
allow_null=True,
choices=[
1, 2,
]
)
field.choices = [1]
assert field.run_validation(1) == 1
with pytest.raises(serializers.ValidationError) as exc_info:
field.run_validation(2)
assert exc_info.value.detail == ['"2" is not a valid choice.']
def test_enum_integer_choices(self):
from enum import IntEnum
class ChoiceCase(IntEnum):
first = auto()
second = auto()
# Enum validate
choices = [
(ChoiceCase.first, "1"),
(ChoiceCase.second, "2")
]
field = serializers.ChoiceField(choices=choices)
assert field.run_validation(1) == 1
assert field.run_validation(ChoiceCase.first) == 1
assert field.run_validation("1") == 1
# Enum.value validate
choices = [
(ChoiceCase.first.value, "1"),
(ChoiceCase.second.value, "2")
]
field = serializers.ChoiceField(choices=choices)
assert field.run_validation(1) == 1
assert field.run_validation(ChoiceCase.first) == 1
assert field.run_validation("1") == 1
def test_integer_choices(self):
class ChoiceCase(IntegerChoices):
first = auto()
second = auto()
# Enum validate
choices = [
(ChoiceCase.first, "1"),
(ChoiceCase.second, "2")
]
field = serializers.ChoiceField(choices=choices)
assert field.run_validation(1) == 1
assert field.run_validation(ChoiceCase.first) == 1
assert field.run_validation("1") == 1
choices = [
(ChoiceCase.first.value, "1"),
(ChoiceCase.second.value, "2")
]
field = serializers.ChoiceField(choices=choices)
assert field.run_validation(1) == 1
assert field.run_validation(ChoiceCase.first) == 1
assert field.run_validation("1") == 1
def test_text_choices(self):
class ChoiceCase(TextChoices):
first = auto()
second = auto()
# Enum validate
choices = [
(ChoiceCase.first, "first"),
(ChoiceCase.second, "second")
]
field = serializers.ChoiceField(choices=choices)
assert field.run_validation(ChoiceCase.first) == "first"
assert field.run_validation("first") == "first"
choices = [
(ChoiceCase.first.value, "first"),
(ChoiceCase.second.value, "second")
]
field = serializers.ChoiceField(choices=choices)
assert field.run_validation(ChoiceCase.first) == "first"
assert field.run_validation("first") == "first"
2014-09-22 20:46:02 +04:00
class TestChoiceFieldWithType(FieldValues):
"""
Valid and invalid values for a `Choice` field that uses an integer type,
instead of a char type.
"""
valid_inputs = {
'1': 1,
3: 3,
}
invalid_inputs = {
5: ['"5" is not a valid choice.'],
'abc': ['"abc" is not a valid choice.']
2014-09-22 20:46:02 +04:00
}
outputs = {
'1': 1,
1: 1
}
2014-10-17 16:23:14 +04:00
field = serializers.ChoiceField(
2014-09-22 20:46:02 +04:00
choices=[
(1, 'Poor quality'),
(2, 'Medium quality'),
(3, 'Good quality'),
]
)
class TestChoiceFieldWithListChoices(FieldValues):
"""
Valid and invalid values for a `Choice` field that uses a flat list for the
choices, rather than a list of pairs of (`value`, `description`).
"""
valid_inputs = {
'poor': 'poor',
'medium': 'medium',
'good': 'good',
}
invalid_inputs = {
'awful': ['"awful" is not a valid choice.']
2014-09-22 20:46:02 +04:00
}
outputs = {
'good': 'good'
}
2014-10-17 16:23:14 +04:00
field = serializers.ChoiceField(choices=('poor', 'medium', 'good'))
2014-09-22 20:46:02 +04:00
class TestChoiceFieldWithGroupedChoices(FieldValues):
"""
Valid and invalid values for a `Choice` field that uses a grouped list for the
choices, rather than a list of pairs of (`value`, `description`).
"""
valid_inputs = {
'poor': 'poor',
'medium': 'medium',
'good': 'good',
}
invalid_inputs = {
'awful': ['"awful" is not a valid choice.']
}
outputs = {
'good': 'good'
}
field = serializers.ChoiceField(
choices=[
(
'Category',
(
('poor', 'Poor quality'),
('medium', 'Medium quality'),
),
),
('good', 'Good quality'),
]
)
class TestChoiceFieldWithMixedChoices(FieldValues):
"""
Valid and invalid values for a `Choice` field that uses a single paired or
grouped.
"""
valid_inputs = {
'poor': 'poor',
'medium': 'medium',
'good': 'good',
}
invalid_inputs = {
'awful': ['"awful" is not a valid choice.']
}
outputs = {
'good': 'good'
}
field = serializers.ChoiceField(
choices=[
(
'Category',
(
('poor', 'Poor quality'),
),
),
'medium',
('good', 'Good quality'),
]
)
2014-09-22 20:46:02 +04:00
class TestMultipleChoiceField(FieldValues):
"""
Valid and invalid values for `MultipleChoiceField`.
"""
valid_inputs = {
(): set(),
('aircon',): {'aircon'},
('aircon', 'manual'): {'aircon', 'manual'},
2014-09-22 20:46:02 +04:00
}
invalid_inputs = {
'abc': ['Expected a list of items but got type "str".'],
('aircon', 'incorrect'): ['"incorrect" is not a valid choice.']
2014-09-22 20:46:02 +04:00
}
outputs = [
(['aircon', 'manual', 'incorrect'], {'aircon', 'manual', 'incorrect'})
2014-09-22 20:46:02 +04:00
]
2014-10-17 16:23:14 +04:00
field = serializers.MultipleChoiceField(
2014-09-22 20:46:02 +04:00
choices=[
('aircon', 'AirCon'),
('manual', 'Manual drive'),
('diesel', 'Diesel'),
]
)
2015-06-01 18:13:12 +03:00
def test_against_partial_and_full_updates(self):
2015-06-01 18:04:05 +03:00
field = serializers.MultipleChoiceField(choices=(('a', 'a'), ('b', 'b')))
2015-06-01 18:13:12 +03:00
field.partial = False
assert field.get_value(QueryDict('')) == []
2015-06-01 18:13:12 +03:00
field.partial = True
assert field.get_value(QueryDict('')) == rest_framework.fields.empty
2015-06-01 18:04:05 +03:00
class TestEmptyMultipleChoiceField(FieldValues):
"""
Invalid values for `MultipleChoiceField(allow_empty=False)`.
"""
valid_inputs = {
}
invalid_inputs = (
([], ['This selection may not be empty.']),
)
outputs = [
]
field = serializers.MultipleChoiceField(
choices=[
('consistency', 'Consistency'),
('availability', 'Availability'),
('partition', 'Partition tolerance'),
],
allow_empty=False
)
2014-10-17 16:23:14 +04:00
# File serializers...
2014-09-26 20:06:20 +04:00
class MockFile:
def __init__(self, name='', size=0, url=''):
self.name = name
self.size = size
self.url = url
def __eq__(self, other):
return (
isinstance(other, MockFile) and
self.name == other.name and
self.size == other.size and
self.url == other.url
)
class TestFileField(FieldValues):
"""
Values for `FileField`.
"""
valid_inputs = [
(MockFile(name='example', size=10), MockFile(name='example', size=10))
]
invalid_inputs = [
('invalid', ['The submitted data was not a file. Check the encoding type on the form.']),
(MockFile(name='example.txt', size=0), ['The submitted file is empty.']),
(MockFile(name='', size=10), ['No filename could be determined.']),
(MockFile(name='x' * 100, size=10), ['Ensure this filename has at most 10 characters (it has 100).'])
]
outputs = [
2014-10-08 19:59:52 +04:00
(MockFile(name='example.txt', url='/example.txt'), '/example.txt'),
('', None)
2014-09-26 20:06:20 +04:00
]
2014-10-17 16:23:14 +04:00
field = serializers.FileField(max_length=10)
2014-09-26 20:06:20 +04:00
class TestFieldFieldWithName(FieldValues):
"""
Values for `FileField` with a filename output instead of URLs.
"""
valid_inputs = {}
invalid_inputs = {}
outputs = [
(MockFile(name='example.txt', url='/example.txt'), 'example.txt')
]
2014-10-17 16:23:14 +04:00
field = serializers.FileField(use_url=False)
2014-09-26 20:06:20 +04:00
def ext_validator(value):
if not value.name.endswith('.png'):
raise serializers.ValidationError('File extension is not allowed. Allowed extensions is png.')
2014-09-26 20:06:20 +04:00
# Stub out mock Django `forms.ImageField` class so we don't *actually*
# call into it's regular validation, or require PIL for testing.
class PassImageValidation(DjangoImageField):
default_validators = [ext_validator]
2014-09-26 20:06:20 +04:00
def to_python(self, value):
return value
2014-09-26 20:06:20 +04:00
class FailImageValidation(PassImageValidation):
2014-09-26 20:06:20 +04:00
def to_python(self, value):
if value.name == 'badimage.png':
raise serializers.ValidationError(self.error_messages['invalid_image'])
2014-09-26 20:06:20 +04:00
return value
class TestInvalidImageField(FieldValues):
"""
Values for an invalid `ImageField`.
"""
valid_inputs = {}
invalid_inputs = [
(MockFile(name='badimage.png', size=10), ['Upload a valid image. The file you uploaded was either not an image or a corrupted image.']),
(MockFile(name='goodimage.html', size=10), ['File extension is not allowed. Allowed extensions is png.'])
2014-09-26 20:06:20 +04:00
]
outputs = {}
2014-10-17 16:23:14 +04:00
field = serializers.ImageField(_DjangoImageField=FailImageValidation)
2014-09-26 20:06:20 +04:00
class TestValidImageField(FieldValues):
"""
Values for an valid `ImageField`.
"""
valid_inputs = [
(MockFile(name='example.png', size=10), MockFile(name='example.png', size=10))
2014-09-26 20:06:20 +04:00
]
invalid_inputs = {}
outputs = {}
2014-10-17 16:23:14 +04:00
field = serializers.ImageField(_DjangoImageField=PassImageValidation)
2014-09-26 20:06:20 +04:00
2014-10-17 16:23:14 +04:00
# Composite serializers...
2014-09-26 20:06:20 +04:00
2014-09-26 16:08:20 +04:00
class TestListField(FieldValues):
"""
Values for `ListField` with IntegerField as child.
2014-09-26 16:08:20 +04:00
"""
valid_inputs = [
([1, 2, 3], [1, 2, 3]),
(['1', '2', '3'], [1, 2, 3]),
([], [])
2014-09-26 16:08:20 +04:00
]
invalid_inputs = [
('not a list', ['Expected a list of items but got type "str".']),
([1, 2, 'error', 'error'], {2: ['A valid integer is required.'], 3: ['A valid integer is required.']}),
({'one': 'two'}, ['Expected a list of items but got type "dict".'])
2014-09-26 16:08:20 +04:00
]
outputs = [
([1, 2, 3], [1, 2, 3]),
(['1', '2', '3'], [1, 2, 3])
]
2014-10-17 16:23:14 +04:00
field = serializers.ListField(child=serializers.IntegerField())
2014-09-26 16:08:20 +04:00
def test_no_source_on_child(self):
with pytest.raises(AssertionError) as exc_info:
serializers.ListField(child=serializers.IntegerField(source='other'))
assert str(exc_info.value) == (
"The `source` argument is not meaningful when applied to a `child=` field. "
"Remove `source=` from the field declaration."
)
def test_collection_types_are_invalid_input(self):
field = serializers.ListField(child=serializers.CharField())
input_value = ({'one': 'two'})
with pytest.raises(serializers.ValidationError) as exc_info:
field.to_internal_value(input_value)
2015-11-13 18:41:57 +03:00
assert exc_info.value.detail == ['Expected a list of items but got type "dict".']
def test_constructor_misuse_raises(self):
# Test that `ListField` can only be instantiated with keyword arguments
with pytest.raises(TypeError):
serializers.ListField(serializers.CharField())
2014-09-26 16:08:20 +04:00
class TestNestedListField(FieldValues):
"""
Values for nested `ListField` with IntegerField as child.
"""
valid_inputs = [
([[1, 2], [3]], [[1, 2], [3]]),
([[]], [[]])
]
invalid_inputs = [
(['not a list'], {0: ['Expected a list of items but got type "str".']}),
([[1, 2, 'error'], ['error']], {0: {2: ['A valid integer is required.']}, 1: {0: ['A valid integer is required.']}}),
([{'one': 'two'}], {0: ['Expected a list of items but got type "dict".']})
]
outputs = [
([[1, 2], [3]], [[1, 2], [3]]),
]
field = serializers.ListField(child=serializers.ListField(child=serializers.IntegerField()))
class TestListFieldWithDjangoValidationErrors(FieldValues, TestCase):
"""
Values for `ListField` with UUIDField as child
(since UUIDField can throw ValidationErrors from Django).
The idea is to test that Django's ValidationErrors raised
from Django internals are caught and serializers in a way
that is structurally consistent with DRF's ValidationErrors.
"""
valid_inputs = []
invalid_inputs = [
(
['not-a-valid-uuid', 'd7364368-d1b3-4455-aaa3-56439b460ca2', 'some-other-invalid-uuid'],
{
0: [exceptions.ErrorDetail(string='“not-a-valid-uuid” is not a valid UUID.', code='invalid')],
1: [
exceptions.ErrorDetail(
string='Invalid pk "d7364368-d1b3-4455-aaa3-56439b460ca2" - object does not exist.',
code='does_not_exist',
)
],
2: [exceptions.ErrorDetail(string='“some-other-invalid-uuid” is not a valid UUID.', code='invalid')],
},
),
]
outputs = {}
field = serializers.ListField(child=serializers.PrimaryKeyRelatedField(queryset=UUIDForeignKeyTarget.objects.all()))
class TestEmptyListField(FieldValues):
"""
Values for `ListField` with allow_empty=False flag.
"""
valid_inputs = {}
invalid_inputs = [
([], ['This list may not be empty.'])
]
outputs = {}
field = serializers.ListField(child=serializers.IntegerField(), allow_empty=False)
class TestListFieldLengthLimit(FieldValues):
valid_inputs = ()
invalid_inputs = [
((0, 1), ['Ensure this field has at least 3 elements.']),
((0, 1, 2, 3, 4, 5), ['Ensure this field has no more than 4 elements.']),
]
outputs = ()
field = serializers.ListField(child=serializers.IntegerField(), min_length=3, max_length=4)
class TestUnvalidatedListField(FieldValues):
"""
Values for `ListField` with no `child` argument.
"""
valid_inputs = [
([1, '2', True, [4, 5, 6]], [1, '2', True, [4, 5, 6]]),
]
invalid_inputs = [
2015-01-30 17:00:25 +03:00
('not a list', ['Expected a list of items but got type "str".']),
]
outputs = [
([1, '2', True, [4, 5, 6]], [1, '2', True, [4, 5, 6]]),
]
field = serializers.ListField()
class TestDictField(FieldValues):
"""
Values for `DictField` with CharField as child.
"""
valid_inputs = [
({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
({}, {}),
]
invalid_inputs = [
({'a': 1, 'b': None, 'c': None}, {'b': ['This field may not be null.'], 'c': ['This field may not be null.']}),
2015-01-30 17:00:25 +03:00
('not a dict', ['Expected a dictionary of items but got type "str".']),
]
outputs = [
({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
]
field = serializers.DictField(child=serializers.CharField())
def test_no_source_on_child(self):
with pytest.raises(AssertionError) as exc_info:
serializers.DictField(child=serializers.CharField(source='other'))
assert str(exc_info.value) == (
"The `source` argument is not meaningful when applied to a `child=` field. "
"Remove `source=` from the field declaration."
)
def test_allow_null(self):
"""
If `allow_null=True` then `None` is a valid input.
"""
field = serializers.DictField(allow_null=True)
output = field.run_validation(None)
assert output is None
def test_allow_empty_disallowed(self):
"""
If allow_empty is False then an empty dict is not a valid input.
"""
field = serializers.DictField(allow_empty=False)
with pytest.raises(serializers.ValidationError) as exc_info:
field.run_validation({})
assert exc_info.value.detail == ['This dictionary may not be empty.']
class TestNestedDictField(FieldValues):
"""
Values for nested `DictField` with CharField as child.
"""
valid_inputs = [
({0: {'a': 1, 'b': '2'}, 1: {3: 3}}, {'0': {'a': '1', 'b': '2'}, '1': {'3': '3'}}),
]
invalid_inputs = [
({0: {'a': 1, 'b': None}, 1: {'c': None}}, {'0': {'b': ['This field may not be null.']}, '1': {'c': ['This field may not be null.']}}),
({0: 'not a dict'}, {'0': ['Expected a dictionary of items but got type "str".']}),
]
outputs = [
({0: {'a': 1, 'b': '2'}, 1: {3: 3}}, {'0': {'a': '1', 'b': '2'}, '1': {'3': '3'}}),
]
field = serializers.DictField(child=serializers.DictField(child=serializers.CharField()))
class TestDictFieldWithNullChild(FieldValues):
"""
Values for `DictField` with allow_null CharField as child.
"""
valid_inputs = [
({'a': None, 'b': '2', 3: 3}, {'a': None, 'b': '2', '3': '3'}),
]
invalid_inputs = [
]
outputs = [
({'a': None, 'b': '2', 3: 3}, {'a': None, 'b': '2', '3': '3'}),
]
field = serializers.DictField(child=serializers.CharField(allow_null=True))
class TestUnvalidatedDictField(FieldValues):
"""
Values for `DictField` with no `child` argument.
"""
valid_inputs = [
({'a': 1, 'b': [4, 5, 6], 1: 123}, {'a': 1, 'b': [4, 5, 6], '1': 123}),
]
invalid_inputs = [
2015-01-30 17:00:25 +03:00
('not a dict', ['Expected a dictionary of items but got type "str".']),
]
outputs = [
({'a': 1, 'b': [4, 5, 6]}, {'a': 1, 'b': [4, 5, 6]}),
]
field = serializers.DictField()
class TestHStoreField(FieldValues):
"""
Values for `ListField` with CharField as child.
"""
valid_inputs = [
({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
({'a': 1, 'b': None}, {'a': '1', 'b': None}),
]
invalid_inputs = [
('not a dict', ['Expected a dictionary of items but got type "str".']),
]
outputs = [
({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
]
field = serializers.HStoreField()
def test_child_is_charfield(self):
with pytest.raises(AssertionError) as exc_info:
serializers.HStoreField(child=serializers.IntegerField())
assert str(exc_info.value) == (
"The `child` argument must be an instance of `CharField`, "
"as the hstore extension stores values as strings."
)
def test_no_source_on_child(self):
with pytest.raises(AssertionError) as exc_info:
serializers.HStoreField(child=serializers.CharField(source='other'))
assert str(exc_info.value) == (
"The `source` argument is not meaningful when applied to a `child=` field. "
"Remove `source=` from the field declaration."
)
def test_allow_null(self):
"""
If `allow_null=True` then `None` is a valid input.
"""
field = serializers.HStoreField(allow_null=True)
output = field.run_validation(None)
assert output is None
2015-09-28 19:25:52 +03:00
class TestJSONField(FieldValues):
"""
Values for `JSONField`.
"""
valid_inputs = [
({
'a': 1,
'b': ['some', 'list', True, 1.23],
'3': None
}, {
'a': 1,
'b': ['some', 'list', True, 1.23],
'3': None
}),
]
invalid_inputs = [
({'a': set()}, ['Value must be valid JSON.']),
({'a': float('inf')}, ['Value must be valid JSON.']),
2015-09-28 19:25:52 +03:00
]
outputs = [
({
'a': 1,
'b': ['some', 'list', True, 1.23],
'3': 3
}, {
'a': 1,
'b': ['some', 'list', True, 1.23],
'3': 3
}),
]
field = serializers.JSONField()
def test_html_input_as_json_string(self):
"""
HTML inputs should be treated as a serialized JSON string.
"""
class TestSerializer(serializers.Serializer):
config = serializers.JSONField()
data = QueryDict(mutable=True)
data.update({'config': '{"a":1}'})
serializer = TestSerializer(data=data)
assert serializer.is_valid()
assert serializer.validated_data == {'config': {"a": 1}}
2015-09-28 19:25:52 +03:00
class TestBinaryJSONField(FieldValues):
"""
Values for `JSONField` with binary=True.
"""
valid_inputs = [
(b'{"a": 1, "3": null, "b": ["some", "list", true, 1.23]}', {
2015-09-28 19:25:52 +03:00
'a': 1,
'b': ['some', 'list', True, 1.23],
'3': None
}),
]
invalid_inputs = [
('{"a": "unterminated string}', ['Value must be valid JSON.']),
]
outputs = [
(['some', 'list', True, 1.23], b'["some", "list", true, 1.23]'),
2015-09-28 19:25:52 +03:00
]
field = serializers.JSONField(binary=True)
# Tests for FileField.
# --------------------
2014-09-29 17:12:26 +04:00
class MockRequest:
def build_absolute_uri(self, value):
return 'http://example.com' + value
class TestFileFieldContext:
def test_fully_qualified_when_request_in_context(self):
2014-10-17 16:23:14 +04:00
field = serializers.FileField(max_length=10)
2014-09-29 17:12:26 +04:00
field._context = {'request': MockRequest()}
obj = MockFile(name='example.txt', url='/example.txt')
value = field.to_representation(obj)
assert value == 'http://example.com/example.txt'
# Tests for FilePathField.
# --------------------
class TestFilePathFieldRequired:
def test_required_passed_to_both_django_file_path_field_and_base(self):
field = serializers.FilePathField(
path=os.path.abspath(os.path.dirname(__file__)),
required=False,
)
assert "" in field.choices # Django adds empty choice if not required
assert field.required is False
with pytest.raises(SkipField):
field.run_validation(empty)
# Tests for SerializerMethodField.
# --------------------------------
class TestSerializerMethodField:
def test_serializer_method_field(self):
class ExampleSerializer(serializers.Serializer):
example_field = serializers.SerializerMethodField()
def get_example_field(self, obj):
return 'ran get_example_field(%d)' % obj['example_field']
serializer = ExampleSerializer({'example_field': 123})
assert serializer.data == {
'example_field': 'ran get_example_field(123)'
}
def test_redundant_method_name(self):
# Prior to v3.10, redundant method names were not allowed.
# This restriction has since been removed.
class ExampleSerializer(serializers.Serializer):
example_field = serializers.SerializerMethodField('get_example_field')
field = ExampleSerializer().fields['example_field']
assert field.method_name == 'get_example_field'
# Tests for ModelField.
# ---------------------
class TestModelField:
def test_max_length_init(self):
field = serializers.ModelField(None)
assert len(field.validators) == 0
field = serializers.ModelField(None, max_length=10)
assert len(field.validators) == 1
# Tests for validation errors
# ---------------------------
class TestValidationErrorCode:
@pytest.mark.parametrize('use_list', (False, True))
def test_validationerror_code_with_msg(self, use_list):
class ExampleSerializer(serializers.Serializer):
password = serializers.CharField()
def validate_password(self, obj):
err = DjangoValidationError(
'exc_msg %s', code='exc_code', params=('exc_param',),
)
if use_list:
err = DjangoValidationError([err])
raise err
serializer = ExampleSerializer(data={'password': 123})
serializer.is_valid()
assert serializer.errors == {'password': ['exc_msg exc_param']}
assert serializer.errors['password'][0].code == 'exc_code'
@pytest.mark.parametrize('use_list', (False, True))
def test_validationerror_code_with_msg_including_percent(self, use_list):
class ExampleSerializer(serializers.Serializer):
password = serializers.CharField()
def validate_password(self, obj):
err = DjangoValidationError('exc_msg with %', code='exc_code')
if use_list:
err = DjangoValidationError([err])
raise err
serializer = ExampleSerializer(data={'password': 123})
serializer.is_valid()
assert serializer.errors == {'password': ['exc_msg with %']}
assert serializer.errors['password'][0].code == 'exc_code'
@pytest.mark.parametrize('code', (None, 'exc_code',))
@pytest.mark.parametrize('use_list', (False, True))
def test_validationerror_code_with_dict(self, use_list, code):
class ExampleSerializer(serializers.Serializer):
def validate(self, obj):
if code is None:
err = DjangoValidationError({
'email': 'email error',
})
else:
err = DjangoValidationError({
'email': DjangoValidationError(
'email error',
code=code),
})
if use_list:
err = DjangoValidationError([err])
raise err
serializer = ExampleSerializer(data={})
serializer.is_valid()
expected_code = code if code else 'invalid'
if use_list:
assert serializer.errors == {
'non_field_errors': [
exceptions.ErrorDetail(
string='email error',
code=expected_code
)
]
}
else:
assert serializer.errors == {
'email': ['email error'],
}
assert serializer.errors['email'][0].code == expected_code
@pytest.mark.parametrize('code', (None, 'exc_code',))
def test_validationerror_code_with_dict_list_same_code(self, code):
class ExampleSerializer(serializers.Serializer):
def validate(self, obj):
if code is None:
raise DjangoValidationError({'email': ['email error 1',
'email error 2']})
raise DjangoValidationError({'email': [
DjangoValidationError('email error 1', code=code),
DjangoValidationError('email error 2', code=code),
]})
serializer = ExampleSerializer(data={})
serializer.is_valid()
expected_code = code if code else 'invalid'
assert serializer.errors == {
'email': [
exceptions.ErrorDetail(
string='email error 1',
code=expected_code
),
exceptions.ErrorDetail(
string='email error 2',
code=expected_code
),
]
}