Commit b1c07670 by Tom Christie

Fleshing out serializer fields

parent 21980b80
"""
Helper functions that convert strftime formats into more readable representations.
"""
from rest_framework import ISO_8601
def datetime_formats(formats):
format = ', '.join(formats).replace(
ISO_8601,
'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]'
)
return humanize_strptime(format)
def date_formats(formats):
format = ', '.join(formats).replace(ISO_8601, 'YYYY[-MM[-DD]]')
return humanize_strptime(format)
def time_formats(formats):
format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]')
return humanize_strptime(format)
def humanize_strptime(format_string):
# Note that we're missing some of the locale specific mappings that
# don't really make sense.
mapping = {
"%Y": "YYYY",
"%y": "YY",
"%m": "MM",
"%b": "[Jan-Dec]",
"%B": "[January-December]",
"%d": "DD",
"%H": "hh",
"%I": "hh", # Requires '%p' to differentiate from '%H'.
"%M": "mm",
"%S": "ss",
"%f": "uuuuuu",
"%a": "[Mon-Sun]",
"%A": "[Monday-Sunday]",
"%p": "[AM|PM]",
"%z": "[+HHMM|-HHMM]"
}
for key, val in mapping.items():
format_string = format_string.replace(key, val)
return format_string
"""
Helper functions for returning the field information that is associated
with a model class.
"""
from collections import namedtuple, OrderedDict
from django.db import models
from django.utils import six
import inspect
FieldInfo = namedtuple('FieldResult', ['pk', 'fields', 'forward_relations', 'reverse_relations'])
RelationInfo = namedtuple('RelationInfo', ['field', 'related', 'to_many', 'has_through_model'])
def _resolve_model(obj):
"""
Resolve supplied `obj` to a Django model class.
`obj` must be a Django model class itself, or a string
representation of one. Useful in situtations like GH #1225 where
Django may not have resolved a string-based reference to a model in
another model's foreign key definition.
String representations should have the format:
'appname.ModelName'
"""
if isinstance(obj, six.string_types) and len(obj.split('.')) == 2:
app_name, model_name = obj.split('.')
return models.get_model(app_name, model_name)
elif inspect.isclass(obj) and issubclass(obj, models.Model):
return obj
raise ValueError("{0} is not a Django model".format(obj))
def get_field_info(model):
"""
Given a model class, returns a `FieldInfo` instance containing metadata
about the various field types on the model.
"""
opts = model._meta.concrete_model._meta
# Deal with the primary key.
pk = opts.pk
while pk.rel and pk.rel.parent_link:
# If model is a child via multitable inheritance, use parent's pk.
pk = pk.rel.to._meta.pk
# Deal with regular fields.
fields = OrderedDict()
for field in [field for field in opts.fields if field.serialize and not field.rel]:
fields[field.name] = field
# Deal with forward relationships.
forward_relations = OrderedDict()
for field in [field for field in opts.fields if field.serialize and field.rel]:
forward_relations[field.name] = RelationInfo(
field=field,
related=_resolve_model(field.rel.to),
to_many=False,
has_through_model=False
)
# Deal with forward many-to-many relationships.
for field in [field for field in opts.many_to_many if field.serialize]:
forward_relations[field.name] = RelationInfo(
field=field,
related=_resolve_model(field.rel.to),
to_many=True,
has_through_model=(
not field.rel.through._meta.auto_created
)
)
# Deal with reverse relationships.
reverse_relations = OrderedDict()
for relation in opts.get_all_related_objects():
accessor_name = relation.get_accessor_name()
reverse_relations[accessor_name] = RelationInfo(
field=None,
related=relation.model,
to_many=relation.field.rel.multiple,
has_through_model=False
)
# Deal with reverse many-to-many relationships.
for relation in opts.get_all_related_many_to_many_objects():
accessor_name = relation.get_accessor_name()
reverse_relations[accessor_name] = RelationInfo(
field=None,
related=relation.model,
to_many=True,
has_through_model=(
hasattr(relation.field.rel, 'through') and
not relation.field.rel.through._meta.auto_created
)
)
return FieldInfo(pk, fields, forward_relations, reverse_relations)
"""
Helper functions for creating user-friendly representations
of serializer classes and serializer fields.
"""
import re
def smart_repr(value):
value = repr(value)
# Representations like u'help text'
# should simply be presented as 'help text'
if value.startswith("u'") and value.endswith("'"):
return value[1:]
# Representations like
# <django.core.validators.RegexValidator object at 0x1047af050>
# Should be presented as
# <django.core.validators.RegexValidator object>
value = re.sub(' at 0x[0-9a-f]{8,10}>', '>', value)
return value
def field_repr(field, force_many=False):
kwargs = field._kwargs
if force_many:
kwargs = kwargs.copy()
kwargs['many'] = True
kwargs.pop('child', None)
arg_string = ', '.join([smart_repr(val) for val in field._args])
kwarg_string = ', '.join([
'%s=%s' % (key, smart_repr(val))
for key, val in sorted(kwargs.items())
])
if arg_string and kwarg_string:
arg_string += ', '
if force_many:
class_name = force_many.__class__.__name__
else:
class_name = field.__class__.__name__
return "%s(%s%s)" % (class_name, arg_string, kwarg_string)
def serializer_repr(serializer, indent, force_many=None):
ret = field_repr(serializer, force_many) + ':'
indent_str = ' ' * indent
if force_many:
fields = force_many.fields
else:
fields = serializer.fields
for field_name, field in fields.items():
ret += '\n' + indent_str + field_name + ' = '
if hasattr(field, 'fields'):
ret += serializer_repr(field, indent + 1)
elif hasattr(field, 'child'):
ret += list_repr(field, indent + 1)
else:
ret += field_repr(field)
return ret
def list_repr(serializer, indent):
child = serializer.child
if hasattr(child, 'fields'):
return serializer_repr(serializer, indent, force_many=child)
return field_repr(serializer)
"""
The `ModelSerializer` and `HyperlinkedModelSerializer` classes are essentially
shortcuts for automatically creating serializers based on a given model class.
These tests deal with ensuring that we correctly map the model fields onto
an appropriate set of serializer fields for each case.
"""
from django.db import models
from django.test import TestCase
from rest_framework import serializers
# Models for testing regular field mapping
class RegularFieldsModel(models.Model):
auto_field = models.AutoField(primary_key=True)
big_integer_field = models.BigIntegerField()
boolean_field = models.BooleanField()
char_field = models.CharField(max_length=100)
comma_seperated_integer_field = models.CommaSeparatedIntegerField(max_length=100)
date_field = models.DateField()
datetime_field = models.DateTimeField()
decimal_field = models.DecimalField(max_digits=3, decimal_places=1)
email_field = models.EmailField(max_length=100)
float_field = models.FloatField()
integer_field = models.IntegerField()
null_boolean_field = models.NullBooleanField()
positive_integer_field = models.PositiveIntegerField()
positive_small_integer_field = models.PositiveSmallIntegerField()
slug_field = models.SlugField(max_length=100)
small_integer_field = models.SmallIntegerField()
text_field = models.TextField()
time_field = models.TimeField()
url_field = models.URLField(max_length=100)
REGULAR_FIELDS_REPR = """
TestSerializer():
auto_field = IntegerField(label='auto field', read_only=True)
big_integer_field = IntegerField(label='big integer field')
boolean_field = BooleanField(default=False, label='boolean field')
char_field = CharField(label='char field', max_length=100)
comma_seperated_integer_field = CharField(label='comma seperated integer field', max_length=100, validators=[<django.core.validators.RegexValidator object>])
date_field = DateField(label='date field')
datetime_field = DateTimeField(label='datetime field')
decimal_field = DecimalField(decimal_places=1, label='decimal field', max_digits=3)
email_field = EmailField(label='email field', max_length=100)
float_field = FloatField(label='float field')
integer_field = IntegerField(label='integer field')
null_boolean_field = BooleanField(label='null boolean field', required=False)
positive_integer_field = IntegerField(label='positive integer field')
positive_small_integer_field = IntegerField(label='positive small integer field')
slug_field = SlugField(label='slug field', max_length=100)
small_integer_field = IntegerField(label='small integer field')
text_field = CharField(label='text field')
time_field = TimeField(label='time field')
url_field = URLField(label='url field', max_length=100)
""".strip()
# Model for testing relational field mapping
class ForeignKeyTarget(models.Model):
char_field = models.CharField(max_length=100)
class ManyToManyTarget(models.Model):
char_field = models.CharField(max_length=100)
class OneToOneTarget(models.Model):
char_field = models.CharField(max_length=100)
class RelationalModel(models.Model):
foreign_key = models.ForeignKey(ForeignKeyTarget)
many_to_many = models.ManyToManyField(ManyToManyTarget)
one_to_one = models.OneToOneField(OneToOneTarget)
RELATIONAL_FLAT_REPR = """
TestSerializer():
id = IntegerField(label='ID', read_only=True)
foreign_key = PrimaryKeyRelatedField(label='foreign key', queryset=<django.db.models.manager.Manager object>)
one_to_one = PrimaryKeyRelatedField(label='one to one', queryset=<django.db.models.manager.Manager object>)
many_to_many = PrimaryKeyRelatedField(label='many to many', many=True, queryset=<django.db.models.manager.Manager object>)
""".strip()
RELATIONAL_NESTED_REPR = """
TestSerializer():
id = IntegerField(label='ID', read_only=True)
foreign_key = NestedModelSerializer(read_only=True):
id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100)
one_to_one = NestedModelSerializer(read_only=True):
id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100)
many_to_many = NestedModelSerializer(many=True, read_only=True):
id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100)
""".strip()
HYPERLINKED_FLAT_REPR = """
TestSerializer():
url = HyperlinkedIdentityField(view_name='relationalmodel-detail')
foreign_key = HyperlinkedRelatedField(label='foreign key', queryset=<django.db.models.manager.Manager object>, view_name='foreignkeytarget-detail')
one_to_one = HyperlinkedRelatedField(label='one to one', queryset=<django.db.models.manager.Manager object>, view_name='onetoonetarget-detail')
many_to_many = HyperlinkedRelatedField(label='many to many', many=True, queryset=<django.db.models.manager.Manager object>, view_name='manytomanytarget-detail')
""".strip()
HYPERLINKED_NESTED_REPR = """
TestSerializer():
url = HyperlinkedIdentityField(view_name='relationalmodel-detail')
foreign_key = NestedModelSerializer(read_only=True):
id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100)
one_to_one = NestedModelSerializer(read_only=True):
id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100)
many_to_many = NestedModelSerializer(many=True, read_only=True):
id = IntegerField(label='ID', read_only=True)
name = CharField(label='name', max_length=100)
""".strip()
class TestSerializerMappings(TestCase):
def test_regular_fields(self):
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
self.assertEqual(repr(TestSerializer()), REGULAR_FIELDS_REPR)
def test_flat_relational_fields(self):
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RelationalModel
self.assertEqual(repr(TestSerializer()), RELATIONAL_FLAT_REPR)
def test_nested_relational_fields(self):
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RelationalModel
depth = 1
self.assertEqual(repr(TestSerializer()), RELATIONAL_NESTED_REPR)
def test_flat_hyperlinked_fields(self):
class TestSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = RelationalModel
self.assertEqual(repr(TestSerializer()), HYPERLINKED_FLAT_REPR)
def test_nested_hyperlinked_fields(self):
class TestSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = RelationalModel
depth = 1
self.assertEqual(repr(TestSerializer()), HYPERLINKED_NESTED_REPR)
from django.test import TestCase
from django.utils import six
from rest_framework.serializers import _resolve_model
from rest_framework.utils.modelinfo import _resolve_model
from tests.models import BasicModel
......
......@@ -22,18 +22,18 @@
# https://github.com/tomchristie/django-rest-framework/issues/446
# """
# field = serializers.PrimaryKeyRelatedField(queryset=NullModel.objects.all())
# self.assertRaises(serializers.ValidationError, field.from_native, '')
# self.assertRaises(serializers.ValidationError, field.from_native, [])
# self.assertRaises(serializers.ValidationError, field.to_primative, '')
# self.assertRaises(serializers.ValidationError, field.to_primative, [])
# def test_hyperlinked_related_field_with_empty_string(self):
# field = serializers.HyperlinkedRelatedField(queryset=NullModel.objects.all(), view_name='')
# self.assertRaises(serializers.ValidationError, field.from_native, '')
# self.assertRaises(serializers.ValidationError, field.from_native, [])
# self.assertRaises(serializers.ValidationError, field.to_primative, '')
# self.assertRaises(serializers.ValidationError, field.to_primative, [])
# def test_slug_related_field_with_empty_string(self):
# field = serializers.SlugRelatedField(queryset=NullModel.objects.all(), slug_field='pk')
# self.assertRaises(serializers.ValidationError, field.from_native, '')
# self.assertRaises(serializers.ValidationError, field.from_native, [])
# self.assertRaises(serializers.ValidationError, field.to_primative, '')
# self.assertRaises(serializers.ValidationError, field.to_primative, [])
# class TestManyRelatedMixin(TestCase):
......
......@@ -6,7 +6,7 @@
# def test_empty_serializer(self):
# class FooBarSerializer(serializers.Serializer):
# foo = serializers.IntegerField()
# bar = serializers.SerializerMethodField('get_bar')
# bar = serializers.MethodField()
# def get_bar(self, obj):
# return 'bar'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment