""" 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 __future__ import unicode_literals from datetime import datetime from decimal import Decimal from uuid import UUID import pytest from bson import ObjectId from django.core.exceptions import ImproperlyConfigured from django.test import TestCase from mongoengine import Document, fields from rest_framework import serializers from rest_framework_mongoengine.serializers import DocumentSerializer from .utils import dedent class CustomField(fields.BaseField): pass class OneFieldModel(Document): str_field = fields.StringField() class AutoFieldModel(Document): auto_field = fields.SequenceField(primary_key=True) class RegularModel(Document): """ A model class for testing regular flat fields. """ str_field = fields.StringField() str_regex_field = fields.StringField(regex="^valid_regex") url_field = fields.URLField() email_field = fields.EmailField() int_field = fields.IntField() long_field = fields.LongField() float_field = fields.FloatField() boolean_field = fields.BooleanField() nullboolean_field = fields.BooleanField(null=True) date_field = fields.DateTimeField() complexdate_field = fields.ComplexDateTimeField() uuid_field = fields.UUIDField() id_field = fields.ObjectIdField() decimal_field = fields.DecimalField() custom_field = CustomField() # TODO # dynamic_field = fields.DynamicField() # bin_field = fields.BinaryField() # file_field = fields.FileField() # image_field = fields.ImageField() def method(self): return 'method' COLOR_CHOICES = (('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')) class FieldOptionsModel(Document): required_field = fields.IntField(required=True) int_null_field = fields.IntField(null=True) string_null_field = fields.StringField(null=True) required_list_field = fields.ListField(fields.IntField(), required=True) non_required_list_field = fields.ListField(fields.IntField(), required=False) required_dict_field = fields.DictField(required=True) choices_field = fields.StringField(choices=COLOR_CHOICES) length_limit_field = fields.StringField(min_length=3, max_length=12) value_limit_field = fields.IntField(min_value=3, max_value=12) decimal_field = fields.DecimalField(precision=4, max_value=9999) DECIMAL_CHOICES = (('low', Decimal('0.1')), ('medium', Decimal('0.5')), ('high', Decimal('0.9'))) class ComplexChoicesModel(Document): choices_field_with_nonstandard_args = fields.DecimalField(precision=1, choices=DECIMAL_CHOICES, verbose_name='A label') class TestRegularFieldMappings(TestCase): maxDiff = 10000 def test_regular_fields(self): """ Model fields should map to their equivelent serializer fields. """ class TestSerializer(DocumentSerializer): class Meta: model = RegularModel fields = '__all__' regex_repr = "re.compile('^valid_regex')" expected = dedent(""" TestSerializer(): id = ObjectIdField(read_only=True) str_field = CharField(required=False) str_regex_field = RegexField(regex=%s, required=False) url_field = URLField(required=False) email_field = EmailField(required=False) int_field = IntegerField(required=False) long_field = IntegerField(required=False) float_field = FloatField(required=False) boolean_field = BooleanField(required=False) nullboolean_field = NullBooleanField(required=False) date_field = DateTimeField(required=False) complexdate_field = DateTimeField(required=False) uuid_field = UUIDField(required=False) id_field = ObjectIdField(required=False) decimal_field = DecimalField(decimal_places=2, max_digits=65536, required=False) custom_field = DocumentField(model_field=<tests.test_basic.CustomField: custom_field>, required=False) """ % regex_repr) assert repr(TestSerializer()) == expected def test_meta_fields(self): """ Serializer should respect Meta.fields """ class TestSerializer(DocumentSerializer): class Meta: model = RegularModel fields = ('id', 'str_field') expected = dedent(""" TestSerializer(): id = ObjectIdField(read_only=True) str_field = CharField(required=False) """) assert repr(TestSerializer()) == expected def test_meta_exclude(self): """ Serializer should respect Meta.exclude """ class TestSerializer(DocumentSerializer): class Meta: model = RegularModel exclude = ('decimal_field', 'custom_field') regex_repr = "re.compile('^valid_regex')" expected = dedent(""" TestSerializer(): id = ObjectIdField(read_only=True) str_field = CharField(required=False) str_regex_field = RegexField(regex=%s, required=False) url_field = URLField(required=False) email_field = EmailField(required=False) int_field = IntegerField(required=False) long_field = IntegerField(required=False) float_field = FloatField(required=False) boolean_field = BooleanField(required=False) nullboolean_field = NullBooleanField(required=False) date_field = DateTimeField(required=False) complexdate_field = DateTimeField(required=False) uuid_field = UUIDField(required=False) id_field = ObjectIdField(required=False) """ % regex_repr) assert repr(TestSerializer()) == expected def test_field_options(self): class TestSerializer(DocumentSerializer): class Meta: model = FieldOptionsModel fields = '__all__' expected = dedent(""" TestSerializer(): id = ObjectIdField(read_only=True) required_field = IntegerField(required=True) int_null_field = IntegerField(allow_null=True, required=False) string_null_field = CharField(allow_blank=True, allow_null=True, required=False) required_list_field = ListField(allow_empty=False, child=IntegerField(required=False), required=True) non_required_list_field = ListField(child=IntegerField(required=False), required=False) required_dict_field = DictField(allow_empty=False, required=True) choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')), required=False) length_limit_field = CharField(max_length=12, min_length=3, required=False) value_limit_field = IntegerField(max_value=12, min_value=3, required=False) decimal_field = DecimalField(decimal_places=4, max_digits=8, max_value=9999, required=False) """) assert repr(TestSerializer()) == expected def test_method_field(self): """ Properties and methods on the model should be allowed as `Meta.fields` values, and should map to `ReadOnlyField`. """ class TestSerializer(DocumentSerializer): class Meta: model = RegularModel fields = ('id', 'method') expected = dedent(""" TestSerializer(): id = ObjectIdField(read_only=True) method = ReadOnlyField() """) assert repr(TestSerializer()) == expected def test_pk_fields(self): """ Both `pk` and the actual primary key name are valid in `Meta.fields`. """ class TestSerializer(DocumentSerializer): class Meta: model = AutoFieldModel fields = ('pk', 'auto_field') expected = dedent(""" TestSerializer(): pk = IntegerField(read_only=True) auto_field = IntegerField(read_only=True) """) assert repr(TestSerializer()) == expected def test_id_field(self): """ The autocreated id field should be mapped properly """ class TestSerializer(DocumentSerializer): class Meta: model = OneFieldModel fields = ('id',) expected = dedent(""" TestSerializer(): id = ObjectIdField(read_only=True) """) assert repr(TestSerializer()) == expected def test_extra_field_kwargs(self): """ Ensure `extra_kwargs` are passed to generated fields. """ class TestSerializer(DocumentSerializer): class Meta: model = RegularModel fields = ('id', 'str_field') extra_kwargs = {'str_field': {'default': 'extra'}} expected = dedent(""" TestSerializer(): id = ObjectIdField(read_only=True) str_field = CharField(default='extra') """) assert repr(TestSerializer()) == expected def test_invalid_field(self): """ Field names that do not map to a model field or relationship should raise a configuration errror. """ class TestSerializer(DocumentSerializer): class Meta: model = RegularModel fields = ('id', 'invalid') with pytest.raises(ImproperlyConfigured) as exc: TestSerializer().fields expected = 'Field name `invalid` is not valid for model `RegularModel`.' assert expected in str(exc.value) def test_missing_field(self): """ Fields that have been declared on the serializer class must be included in the `Meta.fields` if it exists. """ class TestSerializer(DocumentSerializer): missing = serializers.ReadOnlyField() class Meta: model = RegularModel fields = ('id',) with pytest.raises(AssertionError) as exc: TestSerializer().fields expected = ( "The field 'missing' was declared on serializer TestSerializer, " "but has not been included in the 'fields' option." ) assert expected in str(exc.value) def test_missing_superclass_field_not_included(self): """ Fields that have been declared on a parent of the serializer class may be excluded from the `Meta.fields` option. """ class TestSerializer(DocumentSerializer): missing = serializers.ReadOnlyField() class Meta: model = RegularModel fields = '__all__' class ChildSerializer(TestSerializer): missing = serializers.ReadOnlyField() class Meta: model = RegularModel fields = ('id',) ChildSerializer().fields def test_missing_superclass_field_excluded(self): """ Fields that have been declared on a parent of the serializer class may be excluded from the `Meta.fields` option. """ class TestSerializer(DocumentSerializer): missing = serializers.ReadOnlyField() class Meta: model = RegularModel fields = '__all__' class ChildSerializer(TestSerializer): missing = serializers.ReadOnlyField() class Meta: model = RegularModel exclude = ('missing',) ChildSerializer().fields def test_choices_with_nonstandard_args(self): class TestSerializer(DocumentSerializer): missing = serializers.ReadOnlyField() class Meta: model = ComplexChoicesModel fields = '__all__' TestSerializer().fields class TestRegexStringValidation(TestCase): valid = "valid_regex_str" invalid = "invalid_regex_str" def test_validation(self): class TestSerializer(DocumentSerializer): class Meta: model = RegularModel fields = '__all__' data = {'str_regex_field': "valid_regex_str"} serializer = TestSerializer(data=data) assert serializer.is_valid() data = {'str_regex_field': "invalid_regex_str"} serializer = TestSerializer(data=data) assert not serializer.is_valid() class TestIntegration(TestCase): maxDiff = 0 def doCleanups(self): RegularModel.drop_collection() def test_parsing(self): class TestSerializer(DocumentSerializer): class Meta: model = RegularModel fields = '__all__' input_data = { 'str_field': "str", 'str_regex_field': "valid_regex_str", 'url_field': "http://qwe.qw/", 'email_field': "qwe@qwe.qw", 'int_field': "42", 'long_field': "9223372036854775807", 'float_field': "42e-10", 'boolean_field': "True", 'nullboolean_field': None, 'date_field': "2015-11-14T06:13:14.123000", 'complexdate_field': "2015-11-14T06:13:14.123456", 'uuid_field': "36195645-d9d8-4c86-bd88-29143cdb7ad4", 'id_field': "56467a4ba21aab16872f5867", 'decimal_field': "12.3" } serializer = TestSerializer(data=input_data) assert serializer.is_valid(), serializer.errors expected = { 'str_field': "str", 'str_regex_field': "valid_regex_str", 'url_field': "http://qwe.qw/", 'email_field': "qwe@qwe.qw", 'int_field': 42, 'long_field': 9223372036854775807, 'float_field': 42e-10, 'boolean_field': True, 'nullboolean_field': None, 'date_field': datetime(2015, 11, 14, 6, 13, 14, 123000), 'complexdate_field': datetime(2015, 11, 14, 6, 13, 14, 123456), 'uuid_field': UUID("36195645-d9d8-4c86-bd88-29143cdb7ad4"), 'id_field': ObjectId("56467a4ba21aab16872f5867"), 'decimal_field': Decimal('12.3') } assert serializer.validated_data == expected def test_retrieval(self): instance = RegularModel.objects.create( str_field="str", str_regex_field="valid_regex_str", url_field="http://qwe.qw/", email_field="qwe@qwe.qw", int_field=42, long_field=9223372036854775807, float_field=42e-10, boolean_field=True, nullboolean_field=None, date_field=datetime(2015, 11, 14, 6, 13, 14, 123000), complexdate_field=datetime(2015, 11, 14, 6, 13, 14, 123456), uuid_field=UUID("36195645-d9d8-4c86-bd88-29143cdb7ad4"), id_field=ObjectId("56467a4ba21aab16872f5867"), decimal_field=Decimal(1) / Decimal(3) ) class TestSerializer(DocumentSerializer): class Meta: model = RegularModel fields = '__all__' serializer = TestSerializer(instance) expected = { 'id': str(instance.id), 'str_field': "str", 'str_regex_field': "valid_regex_str", 'url_field': "http://qwe.qw/", 'email_field': "qwe@qwe.qw", 'int_field': 42, 'long_field': 9223372036854775807, 'float_field': 42e-10, 'boolean_field': True, 'nullboolean_field': None, 'date_field': "2015-11-14T06:13:14.123000", 'complexdate_field': "2015-11-14T06:13:14.123456", 'uuid_field': "36195645-d9d8-4c86-bd88-29143cdb7ad4", 'id_field': "56467a4ba21aab16872f5867", 'decimal_field': "0.33", # default DRF rounding 'custom_field': None } assert serializer.data == expected def test_create(self): class TestSerializer(DocumentSerializer): class Meta: model = RegularModel fields = '__all__' data = { 'str_field': "str", 'url_field': "http://qwe.qw/", 'str_regex_field': "valid_regex_str", 'email_field': "qwe@qwe.qw", 'int_field': 42, 'long_field': 9223372036854775807, 'float_field': 42e-10, 'boolean_field': True, 'nullboolean_field': None, 'date_field': "2015-11-14T06:13:14.123000", 'complexdate_field': "2015-11-14T06:13:14.123456", 'uuid_field': "36195645-d9d8-4c86-bd88-29143cdb7ad4", 'id_field': "56467a4ba21aab16872f5867", 'decimal_field': "0.33", } serializer = TestSerializer(data=data) assert serializer.is_valid(), serializer.errors instance = serializer.save() assert instance.str_field == "str" assert instance.url_field == "http://qwe.qw/" assert instance.email_field == "qwe@qwe.qw" assert instance.int_field == 42 assert instance.long_field == 9223372036854775807 assert instance.float_field == 42e-10 assert instance.boolean_field assert instance.nullboolean_field is None assert instance.date_field == datetime(2015, 11, 14, 6, 13, 14, 123000) assert instance.complexdate_field == datetime(2015, 11, 14, 6, 13, 14, 123456) assert instance.uuid_field == UUID("36195645-d9d8-4c86-bd88-29143cdb7ad4") assert instance.id_field == ObjectId("56467a4ba21aab16872f5867") assert instance.decimal_field == Decimal("0.33") expected = { 'id': str(instance.id), 'str_field': "str", 'str_regex_field': "valid_regex_str", 'url_field': "http://qwe.qw/", 'email_field': "qwe@qwe.qw", 'int_field': 42, 'long_field': 9223372036854775807, 'float_field': 42e-10, 'boolean_field': True, 'nullboolean_field': None, 'date_field': "2015-11-14T06:13:14.123000", 'complexdate_field': "2015-11-14T06:13:14.123456", 'uuid_field': "36195645-d9d8-4c86-bd88-29143cdb7ad4", 'id_field': "56467a4ba21aab16872f5867", 'decimal_field': "0.33", 'custom_field': None } assert serializer.data == expected def test_update(self): instance = RegularModel.objects.create( str_field="str", str_regex_field="valid_regex_str", url_field="http://qwe.qw/", email_field="qwe@qwe.qw", int_field=42, long_field=9223372036854775807, float_field=42e-10, boolean_field=True, nullboolean_field=None, date_field=datetime(2015, 11, 14, 6, 13, 14, 123000), complexdate_field=datetime(2015, 11, 14, 6, 13, 14, 123456), uuid_field=UUID("36195645-d9d8-4c86-bd88-29143cdb7ad4"), id_field=ObjectId("56467a4ba21aab16872f5867"), decimal_field=Decimal(1) / Decimal(3) ) class TestSerializer(DocumentSerializer): class Meta: model = RegularModel fields = '__all__' data = { 'str_field': "str1", 'str_regex_field': "valid_regex_str", 'url_field': "http://qwe1.qw/", 'email_field': "qwe1@qwe.qw", 'int_field': 41, 'long_field': 9223372036854775801, 'float_field': 41e-10, 'boolean_field': False, 'nullboolean_field': True, 'date_field': "2015-11-14T06:13:14.121000", 'complexdate_field': "2015-11-14T06:13:14.123451", 'uuid_field': "36195645-d9d8-4c86-bd88-29143cdb7ad1", 'id_field': "56467a4ba21aab16872f5861", 'decimal_field': "0.31", } serializer = TestSerializer(instance, data=data) assert serializer.is_valid(), serializer.errors instance = serializer.save() assert instance.str_field == "str1" assert instance.url_field == "http://qwe1.qw/" assert instance.email_field == "qwe1@qwe.qw" assert instance.int_field == 41 assert instance.long_field == 9223372036854775801 assert instance.float_field == 41e-10 assert not instance.boolean_field assert instance.nullboolean_field assert instance.date_field == datetime(2015, 11, 14, 6, 13, 14, 121000) assert instance.complexdate_field == datetime(2015, 11, 14, 6, 13, 14, 123451) assert instance.uuid_field == UUID("36195645-d9d8-4c86-bd88-29143cdb7ad1") assert instance.id_field == ObjectId("56467a4ba21aab16872f5861") assert instance.decimal_field == Decimal("0.31") expected = { 'id': str(instance.id), 'str_field': "str1", 'str_regex_field': "valid_regex_str", 'url_field': "http://qwe1.qw/", 'email_field': "qwe1@qwe.qw", 'int_field': 41, 'long_field': 9223372036854775801, 'float_field': 41e-10, 'boolean_field': False, 'nullboolean_field': True, 'date_field': "2015-11-14T06:13:14.121000", 'complexdate_field': "2015-11-14T06:13:14.123451", 'uuid_field': "36195645-d9d8-4c86-bd88-29143cdb7ad1", 'id_field': "56467a4ba21aab16872f5861", 'decimal_field': "0.31", 'custom_field': None } assert serializer.data == expected