mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-08 06:14:47 +03:00
Merge 6bbb9165a5
into 9b56dda918
This commit is contained in:
commit
069a980210
|
@ -24,7 +24,7 @@ from django.utils.dateparse import (
|
||||||
parse_date, parse_datetime, parse_duration, parse_time
|
parse_date, parse_datetime, parse_duration, parse_time
|
||||||
)
|
)
|
||||||
from django.utils.duration import duration_string
|
from django.utils.duration import duration_string
|
||||||
from django.utils.encoding import is_protected_type, smart_text
|
from django.utils.encoding import force_text, is_protected_type, smart_text
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.ipv6 import clean_ipv6_address
|
from django.utils.ipv6 import clean_ipv6_address
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -262,6 +262,7 @@ MISSING_ERROR_MESSAGE = (
|
||||||
class Field(object):
|
class Field(object):
|
||||||
_creation_counter = 0
|
_creation_counter = 0
|
||||||
|
|
||||||
|
type = 'field'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'required': _('This field is required.'),
|
'required': _('This field is required.'),
|
||||||
'null': _('This field may not be null.')
|
'null': _('This field may not be null.')
|
||||||
|
@ -597,10 +598,23 @@ class Field(object):
|
||||||
"""
|
"""
|
||||||
return unicode_to_repr(representation.field_repr(self))
|
return unicode_to_repr(representation.field_repr(self))
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
metadata = OrderedDict([
|
||||||
|
('type', self.type),
|
||||||
|
('required', self.required),
|
||||||
|
('read_only', self.read_only),
|
||||||
|
])
|
||||||
|
for attr in ['label', 'help_text']:
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if value is not None and value != '':
|
||||||
|
metadata[attr] = force_text(value, strings_only=True)
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
# Boolean types...
|
# Boolean types...
|
||||||
|
|
||||||
class BooleanField(Field):
|
class BooleanField(Field):
|
||||||
|
type = 'boolean'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('"{input}" is not a valid boolean.')
|
'invalid': _('"{input}" is not a valid boolean.')
|
||||||
}
|
}
|
||||||
|
@ -632,6 +646,7 @@ class BooleanField(Field):
|
||||||
|
|
||||||
|
|
||||||
class NullBooleanField(Field):
|
class NullBooleanField(Field):
|
||||||
|
type = 'boolean'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('"{input}" is not a valid boolean.')
|
'invalid': _('"{input}" is not a valid boolean.')
|
||||||
}
|
}
|
||||||
|
@ -667,6 +682,7 @@ class NullBooleanField(Field):
|
||||||
# String types...
|
# String types...
|
||||||
|
|
||||||
class CharField(Field):
|
class CharField(Field):
|
||||||
|
type = 'string'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'blank': _('This field may not be blank.'),
|
'blank': _('This field may not be blank.'),
|
||||||
'max_length': _('Ensure this field has no more than {max_length} characters.'),
|
'max_length': _('Ensure this field has no more than {max_length} characters.'),
|
||||||
|
@ -704,8 +720,17 @@ class CharField(Field):
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
return six.text_type(value)
|
return six.text_type(value)
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
metadata = super(CharField, self).get_metadata()
|
||||||
|
for attr in ['min_length', 'max_length']:
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if value is not None:
|
||||||
|
metadata[attr] = value
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
class EmailField(CharField):
|
class EmailField(CharField):
|
||||||
|
type = 'email'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Enter a valid email address.')
|
'invalid': _('Enter a valid email address.')
|
||||||
}
|
}
|
||||||
|
@ -717,6 +742,7 @@ class EmailField(CharField):
|
||||||
|
|
||||||
|
|
||||||
class RegexField(CharField):
|
class RegexField(CharField):
|
||||||
|
type = 'regex'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('This value does not match the required pattern.')
|
'invalid': _('This value does not match the required pattern.')
|
||||||
}
|
}
|
||||||
|
@ -728,6 +754,7 @@ class RegexField(CharField):
|
||||||
|
|
||||||
|
|
||||||
class SlugField(CharField):
|
class SlugField(CharField):
|
||||||
|
type = 'slug'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.')
|
'invalid': _('Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.')
|
||||||
}
|
}
|
||||||
|
@ -740,6 +767,7 @@ class SlugField(CharField):
|
||||||
|
|
||||||
|
|
||||||
class URLField(CharField):
|
class URLField(CharField):
|
||||||
|
type = 'url'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Enter a valid URL.')
|
'invalid': _('Enter a valid URL.')
|
||||||
}
|
}
|
||||||
|
@ -814,6 +842,7 @@ class IPAddressField(CharField):
|
||||||
# Number types...
|
# Number types...
|
||||||
|
|
||||||
class IntegerField(Field):
|
class IntegerField(Field):
|
||||||
|
type = 'integer'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('A valid integer is required.'),
|
'invalid': _('A valid integer is required.'),
|
||||||
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
|
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
|
||||||
|
@ -847,8 +876,17 @@ class IntegerField(Field):
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
return int(value)
|
return int(value)
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
metadata = super(IntegerField, self).get_metadata()
|
||||||
|
for attr in ['max_value', 'min_value']:
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if value is not None:
|
||||||
|
metadata[attr] = value
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
class FloatField(Field):
|
class FloatField(Field):
|
||||||
|
type = 'float'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('A valid number is required.'),
|
'invalid': _('A valid number is required.'),
|
||||||
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
|
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
|
||||||
|
@ -880,8 +918,17 @@ class FloatField(Field):
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
return float(value)
|
return float(value)
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
metadata = super(FloatField, self).get_metadata()
|
||||||
|
for attr in ['max_value', 'min_value']:
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if value is not None:
|
||||||
|
metadata[attr] = value
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
class DecimalField(Field):
|
class DecimalField(Field):
|
||||||
|
type = 'decimal'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('A valid number is required.'),
|
'invalid': _('A valid number is required.'),
|
||||||
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
|
'max_value': _('Ensure this value is less than or equal to {max_value}.'),
|
||||||
|
@ -1001,10 +1048,19 @@ class DecimalField(Field):
|
||||||
decimal.Decimal('.1') ** self.decimal_places,
|
decimal.Decimal('.1') ** self.decimal_places,
|
||||||
context=context)
|
context=context)
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
metadata = super(DecimalField, self).get_metadata()
|
||||||
|
for attr in ['max_value', 'min_value']:
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if value is not None:
|
||||||
|
metadata[attr] = value
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
# Date & time fields...
|
# Date & time fields...
|
||||||
|
|
||||||
class DateTimeField(Field):
|
class DateTimeField(Field):
|
||||||
|
type = 'datetime'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'),
|
'invalid': _('Datetime has wrong format. Use one of these formats instead: {format}.'),
|
||||||
'date': _('Expected a datetime but got a date.'),
|
'date': _('Expected a datetime but got a date.'),
|
||||||
|
@ -1083,6 +1139,7 @@ class DateTimeField(Field):
|
||||||
|
|
||||||
|
|
||||||
class DateField(Field):
|
class DateField(Field):
|
||||||
|
type = 'date'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Date has wrong format. Use one of these formats instead: {format}.'),
|
'invalid': _('Date has wrong format. Use one of these formats instead: {format}.'),
|
||||||
'datetime': _('Expected a date but got a datetime.'),
|
'datetime': _('Expected a date but got a datetime.'),
|
||||||
|
@ -1152,6 +1209,7 @@ class DateField(Field):
|
||||||
|
|
||||||
|
|
||||||
class TimeField(Field):
|
class TimeField(Field):
|
||||||
|
type = 'time'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Time has wrong format. Use one of these formats instead: {format}.'),
|
'invalid': _('Time has wrong format. Use one of these formats instead: {format}.'),
|
||||||
}
|
}
|
||||||
|
@ -1235,6 +1293,7 @@ class DurationField(Field):
|
||||||
# Choice types...
|
# Choice types...
|
||||||
|
|
||||||
class ChoiceField(Field):
|
class ChoiceField(Field):
|
||||||
|
type = 'choice'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid_choice': _('"{input}" is not a valid choice.')
|
'invalid_choice': _('"{input}" is not a valid choice.')
|
||||||
}
|
}
|
||||||
|
@ -1282,8 +1341,21 @@ class ChoiceField(Field):
|
||||||
cutoff_text=self.html_cutoff_text
|
cutoff_text=self.html_cutoff_text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
metadata = super(ChoiceField, self).get_metadata()
|
||||||
|
if not self.read_only:
|
||||||
|
metadata['choices'] = [
|
||||||
|
{
|
||||||
|
'value': choice_value,
|
||||||
|
'display_name': force_text(choice_name, strings_only=True)
|
||||||
|
}
|
||||||
|
for choice_value, choice_name in self.choices.items()
|
||||||
|
]
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
class MultipleChoiceField(ChoiceField):
|
class MultipleChoiceField(ChoiceField):
|
||||||
|
type = 'multiple choice'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid_choice': _('"{input}" is not a valid choice.'),
|
'invalid_choice': _('"{input}" is not a valid choice.'),
|
||||||
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
|
'not_a_list': _('Expected a list of items but got type "{input_type}".'),
|
||||||
|
@ -1342,6 +1414,7 @@ class FilePathField(ChoiceField):
|
||||||
# File types...
|
# File types...
|
||||||
|
|
||||||
class FileField(Field):
|
class FileField(Field):
|
||||||
|
type = 'file upload'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'required': _('No file was submitted.'),
|
'required': _('No file was submitted.'),
|
||||||
'invalid': _('The submitted data was not a file. Check the encoding type on the form.'),
|
'invalid': _('The submitted data was not a file. Check the encoding type on the form.'),
|
||||||
|
@ -1391,8 +1464,15 @@ class FileField(Field):
|
||||||
return url
|
return url
|
||||||
return value.name
|
return value.name
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
metadata = super(FileField, self).get_metadata()
|
||||||
|
if self.max_length is not None:
|
||||||
|
metadata['max_length'] = self.max_length
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
class ImageField(FileField):
|
class ImageField(FileField):
|
||||||
|
type = 'image upload'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid_image': _(
|
'invalid_image': _(
|
||||||
'Upload a valid image. The file you uploaded was either not an image or a corrupted image.'
|
'Upload a valid image. The file you uploaded was either not an image or a corrupted image.'
|
||||||
|
@ -1430,6 +1510,7 @@ class _UnvalidatedField(Field):
|
||||||
|
|
||||||
|
|
||||||
class ListField(Field):
|
class ListField(Field):
|
||||||
|
type = 'list'
|
||||||
child = _UnvalidatedField()
|
child = _UnvalidatedField()
|
||||||
initial = []
|
initial = []
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
|
@ -1482,8 +1563,14 @@ class ListField(Field):
|
||||||
"""
|
"""
|
||||||
return [self.child.to_representation(item) if item is not None else None for item in data]
|
return [self.child.to_representation(item) if item is not None else None for item in data]
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
metadata = super(ListField, self).get_metadata()
|
||||||
|
metadata['child'] = self.child.get_metadata()
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
class DictField(Field):
|
class DictField(Field):
|
||||||
|
type = 'nested object'
|
||||||
child = _UnvalidatedField()
|
child = _UnvalidatedField()
|
||||||
initial = {}
|
initial = {}
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
|
@ -1531,6 +1618,11 @@ class DictField(Field):
|
||||||
for key, val in value.items()
|
for key, val in value.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
metadata = super(DictField, self).get_metadata()
|
||||||
|
metadata['child'] = self.child.get_metadata()
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
class JSONField(Field):
|
class JSONField(Field):
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
|
|
|
@ -103,47 +103,11 @@ class SimpleMetadata(BaseMetadata):
|
||||||
Given an instance of a serializer, return a dictionary of metadata
|
Given an instance of a serializer, return a dictionary of metadata
|
||||||
about its fields.
|
about its fields.
|
||||||
"""
|
"""
|
||||||
if hasattr(serializer, 'child'):
|
return serializer.get_metadata()
|
||||||
# If this is a `ListSerializer` then we want to examine the
|
|
||||||
# underlying child serializer instance instead.
|
|
||||||
serializer = serializer.child
|
|
||||||
return OrderedDict([
|
|
||||||
(field_name, self.get_field_info(field))
|
|
||||||
for field_name, field in serializer.fields.items()
|
|
||||||
])
|
|
||||||
|
|
||||||
def get_field_info(self, field):
|
def get_field_info(self, field):
|
||||||
"""
|
"""
|
||||||
Given an instance of a serializer field, return a dictionary
|
Given an instance of a serializer field, return a dictionary
|
||||||
of metadata about it.
|
of metadata about it.
|
||||||
"""
|
"""
|
||||||
field_info = OrderedDict()
|
return field.get_metadata()
|
||||||
field_info['type'] = self.label_lookup[field]
|
|
||||||
field_info['required'] = getattr(field, 'required', False)
|
|
||||||
|
|
||||||
attrs = [
|
|
||||||
'read_only', 'label', 'help_text',
|
|
||||||
'min_length', 'max_length',
|
|
||||||
'min_value', 'max_value'
|
|
||||||
]
|
|
||||||
|
|
||||||
for attr in attrs:
|
|
||||||
value = getattr(field, attr, None)
|
|
||||||
if value is not None and value != '':
|
|
||||||
field_info[attr] = force_text(value, strings_only=True)
|
|
||||||
|
|
||||||
if getattr(field, 'child', None):
|
|
||||||
field_info['child'] = self.get_field_info(field.child)
|
|
||||||
elif getattr(field, 'fields', None):
|
|
||||||
field_info['children'] = self.get_serializer_info(field)
|
|
||||||
|
|
||||||
if not field_info.get('read_only') and hasattr(field, 'choices'):
|
|
||||||
field_info['choices'] = [
|
|
||||||
{
|
|
||||||
'value': choice_value,
|
|
||||||
'display_name': force_text(choice_name, strings_only=True)
|
|
||||||
}
|
|
||||||
for choice_value, choice_name in field.choices.items()
|
|
||||||
]
|
|
||||||
|
|
||||||
return field_info
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ from django.core.urlresolvers import (
|
||||||
from django.db.models import Manager
|
from django.db.models import Manager
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import force_text, smart_text
|
||||||
from django.utils.six.moves.urllib import parse as urlparse
|
from django.utils.six.moves.urllib import parse as urlparse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
@ -186,6 +186,18 @@ class RelatedField(Field):
|
||||||
def display_value(self, instance):
|
def display_value(self, instance):
|
||||||
return six.text_type(instance)
|
return six.text_type(instance)
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
metadata = super(RelatedField, self).get_metadata()
|
||||||
|
if not self.read_only:
|
||||||
|
metadata['choices'] = [
|
||||||
|
{
|
||||||
|
'value': choice_value,
|
||||||
|
'display_name': force_text(choice_name, strings_only=True)
|
||||||
|
}
|
||||||
|
for choice_value, choice_name in self.choices.items()
|
||||||
|
]
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
class StringRelatedField(RelatedField):
|
class StringRelatedField(RelatedField):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -322,6 +322,7 @@ def get_validation_error_detail(exc):
|
||||||
|
|
||||||
@six.add_metaclass(SerializerMetaclass)
|
@six.add_metaclass(SerializerMetaclass)
|
||||||
class Serializer(BaseSerializer):
|
class Serializer(BaseSerializer):
|
||||||
|
type = 'nested object'
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.')
|
'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.')
|
||||||
}
|
}
|
||||||
|
@ -512,6 +513,17 @@ class Serializer(BaseSerializer):
|
||||||
ret = super(Serializer, self).errors
|
ret = super(Serializer, self).errors
|
||||||
return ReturnDict(ret, serializer=self)
|
return ReturnDict(ret, serializer=self)
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
fields_metadata = OrderedDict([
|
||||||
|
(field_name, field.get_metadata())
|
||||||
|
for field_name, field in self.fields.items()
|
||||||
|
])
|
||||||
|
if self.root is self:
|
||||||
|
return fields_metadata
|
||||||
|
metadata = super(Serializer, self).get_metadata()
|
||||||
|
metadata['children'] = fields_metadata
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
# There's some replication of `ListField` here,
|
# There's some replication of `ListField` here,
|
||||||
# but that's probably better than obfuscating the call hierarchy.
|
# but that's probably better than obfuscating the call hierarchy.
|
||||||
|
@ -685,6 +697,16 @@ class ListSerializer(BaseSerializer):
|
||||||
return ReturnDict(ret, serializer=self)
|
return ReturnDict(ret, serializer=self)
|
||||||
return ReturnList(ret, serializer=self)
|
return ReturnList(ret, serializer=self)
|
||||||
|
|
||||||
|
def get_metadata(self):
|
||||||
|
if self.root is self:
|
||||||
|
return OrderedDict([
|
||||||
|
(field_name, field.get_metadata())
|
||||||
|
for field_name, field in self.child.fields.items()
|
||||||
|
])
|
||||||
|
metadata = super(ListSerializer, self).get_metadata()
|
||||||
|
metadata['child'] = self.child.get_metadata()
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
# ModelSerializer & HyperlinkedModelSerializer
|
# ModelSerializer & HyperlinkedModelSerializer
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django import forms
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
|
||||||
from rest_framework import (
|
from rest_framework import (
|
||||||
exceptions, metadata, serializers, status, versioning, views
|
exceptions, metadata, serializers, status, versioning, views
|
||||||
|
@ -261,10 +266,311 @@ class TestMetadata:
|
||||||
view = ExampleView.as_view(versioning_class=scheme)
|
view = ExampleView.as_view(versioning_class=scheme)
|
||||||
view(request=request)
|
view(request=request)
|
||||||
|
|
||||||
def test_null_boolean_field_info_type(self):
|
|
||||||
options = metadata.SimpleMetadata()
|
class TestFieldMetadata(TestCase):
|
||||||
field_info = options.get_field_info(serializers.NullBooleanField())
|
simple_metadata = metadata.SimpleMetadata()
|
||||||
assert field_info['type'] == 'boolean'
|
|
||||||
|
def get_field_metadata(self, field):
|
||||||
|
return self.simple_metadata.get_field_info(field)
|
||||||
|
|
||||||
|
def get_serializer_metadata(self, serializer):
|
||||||
|
return self.simple_metadata.get_serializer_info(serializer)
|
||||||
|
|
||||||
|
def assertMetadataEqual(self, field, expected_metadata):
|
||||||
|
if isinstance(field, serializers.BaseSerializer):
|
||||||
|
metadata = self.get_serializer_metadata(field)
|
||||||
|
else:
|
||||||
|
metadata = self.get_field_metadata(field)
|
||||||
|
self.assertEqual(metadata, expected_metadata)
|
||||||
|
|
||||||
|
def test_field(self, field_factory=serializers.Field, expected_type='field', **expected_metadata):
|
||||||
|
field = field_factory()
|
||||||
|
self.assertMetadataEqual(field, dict({
|
||||||
|
'type': expected_type,
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
}, **expected_metadata))
|
||||||
|
field = field_factory(
|
||||||
|
required=False,
|
||||||
|
label=_('label'),
|
||||||
|
help_text=_('help text'),
|
||||||
|
)
|
||||||
|
self.assertMetadataEqual(field, dict({
|
||||||
|
'type': expected_type,
|
||||||
|
'required': False,
|
||||||
|
'read_only': False,
|
||||||
|
'label': ugettext('label'),
|
||||||
|
'help_text': ugettext('help text'),
|
||||||
|
}, **expected_metadata))
|
||||||
|
# Empty string label and help_text should be ignored.
|
||||||
|
field = field_factory(
|
||||||
|
label='',
|
||||||
|
help_text='',
|
||||||
|
)
|
||||||
|
self.assertMetadataEqual(field, dict({
|
||||||
|
'type': expected_type,
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
}, **expected_metadata))
|
||||||
|
|
||||||
|
def test_read_only_field(self, field_factory=serializers.ReadOnlyField, expected_type='field',
|
||||||
|
**expected_metadata):
|
||||||
|
field = field_factory(read_only=True)
|
||||||
|
self.assertMetadataEqual(field, dict({
|
||||||
|
'type': expected_type,
|
||||||
|
'required': False,
|
||||||
|
'read_only': True,
|
||||||
|
}, **expected_metadata))
|
||||||
|
|
||||||
|
def test_boolean_field(self):
|
||||||
|
self.test_field(serializers.BooleanField, 'boolean')
|
||||||
|
self.test_read_only_field(serializers.BooleanField, 'boolean')
|
||||||
|
|
||||||
|
def test_null_boolean_field(self):
|
||||||
|
self.test_field(serializers.NullBooleanField, 'boolean')
|
||||||
|
self.test_read_only_field(serializers.NullBooleanField, 'boolean')
|
||||||
|
|
||||||
|
def test_char_field(self, field_factory=serializers.CharField, expected_type='string'):
|
||||||
|
self.test_field(field_factory, expected_type)
|
||||||
|
self.test_read_only_field(field_factory, expected_type)
|
||||||
|
field = field_factory(min_length=0, max_length=0)
|
||||||
|
self.assertMetadataEqual(field, {
|
||||||
|
'type': expected_type,
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'min_length': 0,
|
||||||
|
'max_length': 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_email_field(self):
|
||||||
|
self.test_char_field(serializers.EmailField, 'email')
|
||||||
|
|
||||||
|
def test_url_field(self):
|
||||||
|
self.test_char_field(serializers.URLField, 'url')
|
||||||
|
|
||||||
|
def test_slug_field(self):
|
||||||
|
self.test_char_field(serializers.SlugField, 'slug')
|
||||||
|
|
||||||
|
def test_regex_field(self):
|
||||||
|
self.test_char_field(functools.partial(serializers.RegexField, regex='regex'), 'regex')
|
||||||
|
|
||||||
|
def test_ip_address_field(self):
|
||||||
|
self.test_char_field(serializers.IPAddressField)
|
||||||
|
|
||||||
|
def test_uuid_field(self):
|
||||||
|
self.test_field(serializers.UUIDField)
|
||||||
|
self.test_read_only_field(serializers.UUIDField)
|
||||||
|
|
||||||
|
def test_integer_field(self, field_factory=serializers.IntegerField, expected_type='integer'):
|
||||||
|
self.test_field(field_factory, expected_type)
|
||||||
|
self.test_read_only_field(field_factory, expected_type)
|
||||||
|
field = field_factory(min_value=0, max_value=0)
|
||||||
|
self.assertMetadataEqual(field, {
|
||||||
|
'type': expected_type,
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'min_value': 0,
|
||||||
|
'max_value': 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_float_field(self):
|
||||||
|
self.test_integer_field(serializers.FloatField, 'float')
|
||||||
|
|
||||||
|
def test_decimal_field(self):
|
||||||
|
decimal_field_factory = functools.partial(serializers.DecimalField, max_digits=5, decimal_places=2)
|
||||||
|
self.test_integer_field(decimal_field_factory, 'decimal')
|
||||||
|
|
||||||
|
def test_date_time_field(self):
|
||||||
|
self.test_field(serializers.DateTimeField, 'datetime')
|
||||||
|
self.test_read_only_field(serializers.DateTimeField, 'datetime')
|
||||||
|
|
||||||
|
def test_date_field(self):
|
||||||
|
self.test_field(serializers.DateField, 'date')
|
||||||
|
self.test_read_only_field(serializers.DateField, 'date')
|
||||||
|
|
||||||
|
def test_time_field(self):
|
||||||
|
self.test_field(serializers.TimeField, 'time')
|
||||||
|
self.test_read_only_field(serializers.TimeField, 'time')
|
||||||
|
|
||||||
|
def test_duration_field(self):
|
||||||
|
self.test_field(serializers.DurationField)
|
||||||
|
self.test_read_only_field(serializers.DurationField)
|
||||||
|
|
||||||
|
def test_choice_field(self, field_factory=serializers.ChoiceField, expected_type='choice'):
|
||||||
|
choice_field_factory = functools.partial(field_factory, choices=[])
|
||||||
|
self.test_field(choice_field_factory, expected_type, choices=[])
|
||||||
|
self.test_read_only_field(choice_field_factory, expected_type)
|
||||||
|
field = field_factory([('value', _('label'))])
|
||||||
|
self.assertMetadataEqual(field, {
|
||||||
|
'type': expected_type,
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'choices': [{'value': 'value', 'display_name': ugettext('label')}]
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_multiple_choice_field(self):
|
||||||
|
self.test_choice_field(serializers.MultipleChoiceField, 'multiple choice')
|
||||||
|
|
||||||
|
def test_file_path_field(self):
|
||||||
|
# We have to special case FilePathField as it deals differently with
|
||||||
|
# the `required` argument by changing the choices values to include
|
||||||
|
# an empty string choice instead.
|
||||||
|
path = os.path.dirname(__file__)
|
||||||
|
choices = [
|
||||||
|
{'value': value, 'display_name': display_name}
|
||||||
|
for value, display_name in forms.FilePathField(path, required=False).choices
|
||||||
|
]
|
||||||
|
file_path_field_factory = functools.partial(serializers.FilePathField, path=path)
|
||||||
|
field = file_path_field_factory()
|
||||||
|
self.assertMetadataEqual(field, {
|
||||||
|
'type': 'choice',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'choices': choices,
|
||||||
|
})
|
||||||
|
field = file_path_field_factory(
|
||||||
|
required=False,
|
||||||
|
label=_('label'),
|
||||||
|
help_text=_('help text'),
|
||||||
|
)
|
||||||
|
self.assertMetadataEqual(field, {
|
||||||
|
'type': 'choice',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'label': ugettext('label'),
|
||||||
|
'help_text': ugettext('help text'),
|
||||||
|
'choices': choices,
|
||||||
|
})
|
||||||
|
# Empty string label and help_text should be ignored.
|
||||||
|
field = file_path_field_factory(
|
||||||
|
label='',
|
||||||
|
help_text='',
|
||||||
|
)
|
||||||
|
self.assertMetadataEqual(field, {
|
||||||
|
'type': 'choice',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'choices': choices,
|
||||||
|
})
|
||||||
|
self.test_read_only_field(file_path_field_factory, 'choice')
|
||||||
|
|
||||||
|
def test_file_field(self, field_factory=serializers.FileField, expected_type='file upload'):
|
||||||
|
self.test_field(field_factory, expected_type)
|
||||||
|
self.test_read_only_field(field_factory, expected_type)
|
||||||
|
field = field_factory(max_length=0)
|
||||||
|
self.assertMetadataEqual(field, {
|
||||||
|
'type': expected_type,
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'max_length': 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_image_field(self):
|
||||||
|
self.test_file_field(serializers.ImageField, expected_type='image upload')
|
||||||
|
|
||||||
|
def test_list_field(self):
|
||||||
|
def list_field_factory(*args, **kwargs):
|
||||||
|
return serializers.ListField(child=serializers.Field(), *args, **kwargs)
|
||||||
|
child_metadata = self.get_field_metadata(serializers.Field())
|
||||||
|
self.test_field(list_field_factory, 'list', child=child_metadata)
|
||||||
|
self.test_read_only_field(list_field_factory, 'list', child=child_metadata)
|
||||||
|
|
||||||
|
def test_dict_field(self):
|
||||||
|
def dict_field_factory(*args, **kwargs):
|
||||||
|
return serializers.DictField(child=serializers.Field(), *args, **kwargs)
|
||||||
|
child_metadata = self.get_field_metadata(serializers.Field())
|
||||||
|
self.test_field(dict_field_factory, 'nested object', child=child_metadata)
|
||||||
|
self.test_read_only_field(dict_field_factory, 'nested object', child=child_metadata)
|
||||||
|
|
||||||
|
def test_json_field(self):
|
||||||
|
self.test_field(serializers.JSONField)
|
||||||
|
self.test_read_only_field(serializers.JSONField)
|
||||||
|
|
||||||
|
def test_serializer_method_field(self):
|
||||||
|
self.test_read_only_field(functools.partial(serializers.SerializerMethodField, method_name='method'))
|
||||||
|
|
||||||
|
def test_model_field(self):
|
||||||
|
model_field_factory = functools.partial(serializers.ModelField, model_field=models.Field())
|
||||||
|
self.test_field(model_field_factory)
|
||||||
|
self.test_read_only_field(model_field_factory)
|
||||||
|
|
||||||
|
def test_serializer(self):
|
||||||
|
class TestSerializer(serializers.Serializer):
|
||||||
|
field = serializers.Field()
|
||||||
|
|
||||||
|
serializer = TestSerializer()
|
||||||
|
self.assertMetadataEqual(serializer, {
|
||||||
|
'field': {
|
||||||
|
'type': 'field',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'Field',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
list_serializer = TestSerializer(many=True)
|
||||||
|
self.assertMetadataEqual(list_serializer, {
|
||||||
|
'field': {
|
||||||
|
'type': 'field',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'Field',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_nested_serializer(self):
|
||||||
|
class TestNestedSerializer(serializers.Serializer):
|
||||||
|
field = serializers.Field()
|
||||||
|
|
||||||
|
class TestSerializer(serializers.Serializer):
|
||||||
|
serializer = TestNestedSerializer(
|
||||||
|
label=_('label'), help_text=_('help text'), required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
serializer = TestSerializer()
|
||||||
|
self.assertMetadataEqual(serializer, {
|
||||||
|
'serializer': {
|
||||||
|
'type': 'nested object',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'label': ugettext('label'),
|
||||||
|
'help_text': ugettext('help text'),
|
||||||
|
'children': {
|
||||||
|
'field': {
|
||||||
|
'type': 'field',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'Field',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
class TestListSerializer(serializers.Serializer):
|
||||||
|
serializer = TestNestedSerializer(many=True)
|
||||||
|
|
||||||
|
list_serializer = TestListSerializer()
|
||||||
|
self.assertMetadataEqual(list_serializer, {
|
||||||
|
'serializer': {
|
||||||
|
'type': 'field',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'Serializer',
|
||||||
|
'child': {
|
||||||
|
'type': 'nested object',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'children': {
|
||||||
|
'field': {
|
||||||
|
'type': 'field',
|
||||||
|
'required': True,
|
||||||
|
'read_only': False,
|
||||||
|
'label': 'Field',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class TestModelSerializerMetadata(TestCase):
|
class TestModelSerializerMetadata(TestCase):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user