django-rest-framework/tests/test_model_serializer.py

999 lines
37 KiB
Python
Raw Normal View History

2014-09-09 20:46:28 +04:00
"""
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
2015-06-25 23:55:51 +03:00
import decimal
2015-09-22 17:35:38 +03:00
from collections import OrderedDict
from django.core.exceptions import ImproperlyConfigured
2015-06-25 23:55:51 +03:00
from django.core.validators import (
MaxValueValidator, MinLengthValidator, MinValueValidator
)
2014-09-09 20:46:28 +04:00
from django.db import models
from django.db.models import DurationField as ModelDurationField
2014-09-09 20:46:28 +04:00
from django.test import TestCase
from django.utils import six
2015-06-25 23:55:51 +03:00
2014-09-09 20:46:28 +04:00
from rest_framework import serializers
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
from rest_framework.compat import set_many, unicode_repr
2014-09-09 20:46:28 +04:00
2014-09-15 16:38:28 +04:00
def dedent(blocktext):
return '\n'.join([line[12:] for line in blocktext.splitlines()[1:-1]])
2014-09-23 17:15:00 +04:00
# Tests for regular field mappings.
# ---------------------------------
2014-09-09 20:46:28 +04:00
2014-09-18 16:07:38 +04:00
class CustomField(models.Field):
2014-09-19 12:09:26 +04:00
"""
A custom model field simply for testing purposes.
"""
2014-09-18 16:07:38 +04:00
pass
class OneFieldModel(models.Model):
char_field = models.CharField(max_length=100)
2014-09-09 20:46:28 +04:00
class RegularFieldsModel(models.Model):
2014-09-19 12:09:26 +04:00
"""
A model class for testing regular flat fields.
"""
2014-09-09 20:46:28 +04:00
auto_field = models.AutoField(primary_key=True)
big_integer_field = models.BigIntegerField()
2014-09-11 16:20:44 +04:00
boolean_field = models.BooleanField(default=False)
2014-09-09 20:46:28 +04:00
char_field = models.CharField(max_length=100)
comma_separated_integer_field = models.CommaSeparatedIntegerField(max_length=100)
2014-09-09 20:46:28 +04:00
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()
2015-10-16 14:17:33 +03:00
text_field = models.TextField(max_length=100)
2014-09-09 20:46:28 +04:00
time_field = models.TimeField()
url_field = models.URLField(max_length=100)
2014-09-18 16:07:38 +04:00
custom_field = CustomField()
file_path_field = models.FilePathField(path='/tmp/')
2014-09-09 20:46:28 +04:00
def method(self):
return 'method'
2014-09-09 20:46:28 +04:00
2014-09-23 17:15:00 +04:00
COLOR_CHOICES = (('red', 'Red'), ('blue', 'Blue'), ('green', 'Green'))
DECIMAL_CHOICES = (('low', decimal.Decimal('0.1')), ('medium', decimal.Decimal('0.5')), ('high', decimal.Decimal('0.9')))
2014-09-23 17:15:00 +04:00
class FieldOptionsModel(models.Model):
value_limit_field = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(10)])
length_limit_field = models.CharField(validators=[MinLengthValidator(3)], max_length=12)
blank_field = models.CharField(blank=True, max_length=10)
null_field = models.IntegerField(null=True)
default_field = models.IntegerField(default=0)
descriptive_field = models.IntegerField(help_text='Some help text', verbose_name='A label')
choices_field = models.CharField(max_length=100, choices=COLOR_CHOICES)
class ChoicesModel(models.Model):
choices_field_with_nonstandard_args = models.DecimalField(max_digits=3, decimal_places=1, choices=DECIMAL_CHOICES, verbose_name='A label')
class TestModelSerializer(TestCase):
def test_create_method(self):
class TestSerializer(serializers.ModelSerializer):
non_model_field = serializers.CharField()
class Meta:
model = OneFieldModel
fields = ('char_field', 'non_model_field')
serializer = TestSerializer(data={
'char_field': 'foo',
'non_model_field': 'bar',
})
serializer.is_valid()
with self.assertRaises(TypeError) as excinfo:
serializer.save()
msginitial = 'Got a `TypeError` when calling `OneFieldModel.objects.create()`.'
assert str(excinfo.exception).startswith(msginitial)
def test_abstract_model(self):
"""
Test that trying to use ModelSerializer with Abstract Models
throws a ValueError exception.
"""
class AbstractModel(models.Model):
afield = models.CharField(max_length=255)
class Meta:
abstract = True
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = AbstractModel
fields = ('afield',)
serializer = TestSerializer(data={
'afield': 'foo',
})
with self.assertRaises(ValueError) as excinfo:
serializer.is_valid()
msginitial = 'Cannot use ModelSerializer with Abstract Models.'
assert str(excinfo.exception).startswith(msginitial)
2014-09-15 16:38:28 +04:00
class TestRegularFieldMappings(TestCase):
def test_regular_fields(self):
2014-09-18 16:07:38 +04:00
"""
2016-08-08 11:32:22 +03:00
Model fields should map to their equivalent serializer fields.
2014-09-18 16:07:38 +04:00
"""
2014-09-15 16:38:28 +04:00
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = '__all__'
2014-09-15 16:38:28 +04:00
expected = dedent("""
TestSerializer():
auto_field = IntegerField(read_only=True)
big_integer_field = IntegerField()
2014-09-23 17:15:00 +04:00
boolean_field = BooleanField(required=False)
2014-09-15 16:38:28 +04:00
char_field = CharField(max_length=100)
comma_separated_integer_field = CharField(max_length=100, validators=[<django.core.validators.RegexValidator object>])
2014-09-15 16:38:28 +04:00
date_field = DateField()
datetime_field = DateTimeField()
decimal_field = DecimalField(decimal_places=1, max_digits=3)
email_field = EmailField(max_length=100)
float_field = FloatField()
integer_field = IntegerField()
null_boolean_field = NullBooleanField(required=False)
2014-09-15 16:38:28 +04:00
positive_integer_field = IntegerField()
positive_small_integer_field = IntegerField()
slug_field = SlugField(max_length=100)
small_integer_field = IntegerField()
2015-10-16 14:17:33 +03:00
text_field = CharField(max_length=100, style={'base_template': 'textarea.html'})
2014-09-15 16:38:28 +04:00
time_field = TimeField()
url_field = URLField(max_length=100)
2014-09-18 16:07:38 +04:00
custom_field = ModelField(model_field=<tests.test_model_serializer.CustomField: custom_field>)
file_path_field = FilePathField(path='/tmp/')
2014-09-15 16:38:28 +04:00
""")
2015-10-16 14:17:33 +03:00
self.assertEqual(unicode_repr(TestSerializer()), expected)
2014-09-09 20:46:28 +04:00
2014-09-23 17:15:00 +04:00
def test_field_options(self):
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = FieldOptionsModel
fields = '__all__'
2014-09-23 17:15:00 +04:00
expected = dedent("""
TestSerializer():
id = IntegerField(label='ID', read_only=True)
value_limit_field = IntegerField(max_value=10, min_value=1)
length_limit_field = CharField(max_length=12, min_length=3)
blank_field = CharField(allow_blank=True, max_length=10, required=False)
null_field = IntegerField(allow_null=True, required=False)
2014-09-23 17:15:00 +04:00
default_field = IntegerField(required=False)
descriptive_field = IntegerField(help_text='Some help text', label='A label')
2015-08-06 13:43:03 +03:00
choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')))
2014-09-23 17:15:00 +04:00
""")
if six.PY2:
# This particular case is too awkward to resolve fully across
# both py2 and py3.
expected = expected.replace(
"('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')",
"(u'red', u'Red'), (u'blue', u'Blue'), (u'green', u'Green')"
)
self.assertEqual(unicode_repr(TestSerializer()), expected)
2014-09-23 17:15:00 +04:00
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(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = ('auto_field', 'method')
expected = dedent("""
TestSerializer():
auto_field = IntegerField(read_only=True)
method = ReadOnlyField()
""")
self.assertEqual(repr(TestSerializer()), expected)
def test_pk_fields(self):
"""
Both `pk` and the actual primary key name are valid in `Meta.fields`.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = ('pk', 'auto_field')
expected = dedent("""
TestSerializer():
pk = IntegerField(label='Auto field', read_only=True)
auto_field = IntegerField(read_only=True)
""")
self.assertEqual(repr(TestSerializer()), expected)
def test_extra_field_kwargs(self):
"""
Ensure `extra_kwargs` are passed to generated fields.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
2014-09-19 12:09:26 +04:00
fields = ('auto_field', 'char_field')
extra_kwargs = {'char_field': {'default': 'extra'}}
expected = dedent("""
TestSerializer():
2014-09-19 12:09:26 +04:00
auto_field = IntegerField(read_only=True)
char_field = CharField(default='extra', max_length=100)
""")
self.assertEqual(repr(TestSerializer()), expected)
def test_extra_field_kwargs_required(self):
"""
Ensure `extra_kwargs` are passed to generated fields.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = ('auto_field', 'char_field')
extra_kwargs = {'auto_field': {'required': False, 'read_only': False}}
expected = dedent("""
TestSerializer():
auto_field = IntegerField(read_only=False, required=False)
char_field = CharField(max_length=100)
""")
self.assertEqual(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(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = ('auto_field', 'invalid')
with self.assertRaises(ImproperlyConfigured) as excinfo:
TestSerializer().fields
expected = 'Field name `invalid` is not valid for model `RegularFieldsModel`.'
assert str(excinfo.exception) == expected
def test_missing_field(self):
2014-09-18 16:07:38 +04:00
"""
Fields that have been declared on the serializer class must be included
in the `Meta.fields` if it exists.
"""
class TestSerializer(serializers.ModelSerializer):
missing = serializers.ReadOnlyField()
class Meta:
model = RegularFieldsModel
fields = ('auto_field',)
with self.assertRaises(AssertionError) as excinfo:
TestSerializer().fields
expected = (
"The field 'missing' was declared on serializer TestSerializer, "
"but has not been included in the 'fields' option."
)
assert str(excinfo.exception) == expected
def test_missing_superclass_field(self):
"""
Fields that have been declared on a parent of the serializer class may
be excluded from the `Meta.fields` option.
"""
class TestSerializer(serializers.ModelSerializer):
missing = serializers.ReadOnlyField()
class Meta:
model = RegularFieldsModel
fields = '__all__'
class ChildSerializer(TestSerializer):
missing = serializers.ReadOnlyField()
class Meta:
model = RegularFieldsModel
fields = ('auto_field',)
ChildSerializer().fields
def test_choices_with_nonstandard_args(self):
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = ChoicesModel
fields = '__all__'
ExampleSerializer()
2015-08-27 20:09:08 +03:00
def test_fields_and_exclude_behavior(self):
class ImplicitFieldsSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = '__all__'
2015-08-27 20:09:08 +03:00
class ExplicitFieldsSerializer(serializers.ModelSerializer):
class Meta:
model = RegularFieldsModel
fields = '__all__'
implicit = ImplicitFieldsSerializer()
explicit = ExplicitFieldsSerializer()
assert implicit.data == explicit.data
2014-09-09 20:46:28 +04:00
2015-06-01 19:20:53 +03:00
class TestDurationFieldMapping(TestCase):
def test_duration_field(self):
class DurationFieldModel(models.Model):
"""
A model that defines DurationField.
"""
duration_field = ModelDurationField()
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = DurationFieldModel
fields = '__all__'
2015-06-01 19:20:53 +03:00
expected = dedent("""
TestSerializer():
id = IntegerField(label='ID', read_only=True)
duration_field = DurationField()
""")
self.assertEqual(unicode_repr(TestSerializer()), expected)
class TestGenericIPAddressFieldValidation(TestCase):
def test_ip_address_validation(self):
class IPAddressFieldModel(models.Model):
address = models.GenericIPAddressField()
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = IPAddressFieldModel
fields = '__all__'
s = TestSerializer(data={'address': 'not an ip address'})
self.assertFalse(s.is_valid())
self.assertEqual(1, len(s.errors['address']),
'Unexpected number of validation errors: '
'{0}'.format(s.errors))
2014-09-23 17:15:00 +04:00
# Tests for relational field mappings.
# ------------------------------------
2014-09-09 20:46:28 +04:00
2014-09-11 16:20:44 +04:00
class ForeignKeyTargetModel(models.Model):
name = models.CharField(max_length=100)
2014-09-09 20:46:28 +04:00
2014-09-11 16:20:44 +04:00
class ManyToManyTargetModel(models.Model):
name = models.CharField(max_length=100)
2014-09-09 20:46:28 +04:00
2014-09-11 16:20:44 +04:00
class OneToOneTargetModel(models.Model):
name = models.CharField(max_length=100)
2014-09-09 20:46:28 +04:00
2014-09-15 17:05:58 +04:00
class ThroughTargetModel(models.Model):
name = models.CharField(max_length=100)
class Supplementary(models.Model):
extra = models.IntegerField()
forwards = models.ForeignKey('ThroughTargetModel', on_delete=models.CASCADE)
backwards = models.ForeignKey('RelationalModel', on_delete=models.CASCADE)
2014-09-15 17:05:58 +04:00
2014-09-09 20:46:28 +04:00
class RelationalModel(models.Model):
foreign_key = models.ForeignKey(ForeignKeyTargetModel, related_name='reverse_foreign_key', on_delete=models.CASCADE)
2014-09-13 00:32:20 +04:00
many_to_many = models.ManyToManyField(ManyToManyTargetModel, related_name='reverse_many_to_many')
one_to_one = models.OneToOneField(OneToOneTargetModel, related_name='reverse_one_to_one', on_delete=models.CASCADE)
2014-09-15 17:05:58 +04:00
through = models.ManyToManyField(ThroughTargetModel, through=Supplementary, related_name='reverse_through')
2014-09-09 20:46:28 +04:00
2015-05-28 05:06:57 +03:00
class UniqueTogetherModel(models.Model):
foreign_key = models.ForeignKey(ForeignKeyTargetModel, related_name='unique_foreign_key', on_delete=models.CASCADE)
one_to_one = models.OneToOneField(OneToOneTargetModel, related_name='unique_one_to_one', on_delete=models.CASCADE)
2015-05-28 16:29:15 +03:00
2015-05-28 05:06:57 +03:00
class Meta:
unique_together = ("foreign_key", "one_to_one")
2014-09-15 16:38:28 +04:00
class TestRelationalFieldMappings(TestCase):
2014-09-18 17:23:00 +04:00
def test_pk_relations(self):
2014-09-09 20:46:28 +04:00
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RelationalModel
fields = '__all__'
2014-09-15 16:38:28 +04:00
expected = dedent("""
TestSerializer():
id = IntegerField(label='ID', read_only=True)
foreign_key = PrimaryKeyRelatedField(queryset=ForeignKeyTargetModel.objects.all())
one_to_one = PrimaryKeyRelatedField(queryset=OneToOneTargetModel.objects.all(), validators=[<UniqueValidator(queryset=RelationalModel.objects.all())>])
many_to_many = PrimaryKeyRelatedField(allow_empty=False, many=True, queryset=ManyToManyTargetModel.objects.all())
2014-09-15 17:05:58 +04:00
through = PrimaryKeyRelatedField(many=True, read_only=True)
2014-09-15 16:38:28 +04:00
""")
self.assertEqual(unicode_repr(TestSerializer()), expected)
2014-09-09 20:46:28 +04:00
2014-09-18 17:23:00 +04:00
def test_nested_relations(self):
2014-09-09 20:46:28 +04:00
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RelationalModel
depth = 1
fields = '__all__'
2014-09-15 16:38:28 +04:00
expected = dedent("""
TestSerializer():
id = IntegerField(label='ID', read_only=True)
2014-09-18 14:20:56 +04:00
foreign_key = NestedSerializer(read_only=True):
2014-09-15 16:38:28 +04:00
id = IntegerField(label='ID', read_only=True)
name = CharField(max_length=100)
2014-09-18 14:20:56 +04:00
one_to_one = NestedSerializer(read_only=True):
2014-09-15 16:38:28 +04:00
id = IntegerField(label='ID', read_only=True)
name = CharField(max_length=100)
2014-09-18 14:20:56 +04:00
many_to_many = NestedSerializer(many=True, read_only=True):
2014-09-15 16:38:28 +04:00
id = IntegerField(label='ID', read_only=True)
name = CharField(max_length=100)
2014-09-18 14:20:56 +04:00
through = NestedSerializer(many=True, read_only=True):
2014-09-15 17:05:58 +04:00
id = IntegerField(label='ID', read_only=True)
name = CharField(max_length=100)
2014-09-15 16:38:28 +04:00
""")
self.assertEqual(unicode_repr(TestSerializer()), expected)
2014-09-09 20:46:28 +04:00
2014-09-18 17:23:00 +04:00
def test_hyperlinked_relations(self):
2014-09-09 20:46:28 +04:00
class TestSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = RelationalModel
fields = '__all__'
2014-09-15 16:38:28 +04:00
expected = dedent("""
TestSerializer():
url = HyperlinkedIdentityField(view_name='relationalmodel-detail')
foreign_key = HyperlinkedRelatedField(queryset=ForeignKeyTargetModel.objects.all(), view_name='foreignkeytargetmodel-detail')
one_to_one = HyperlinkedRelatedField(queryset=OneToOneTargetModel.objects.all(), validators=[<UniqueValidator(queryset=RelationalModel.objects.all())>], view_name='onetoonetargetmodel-detail')
many_to_many = HyperlinkedRelatedField(allow_empty=False, many=True, queryset=ManyToManyTargetModel.objects.all(), view_name='manytomanytargetmodel-detail')
2014-09-15 17:05:58 +04:00
through = HyperlinkedRelatedField(many=True, read_only=True, view_name='throughtargetmodel-detail')
2014-09-15 16:38:28 +04:00
""")
self.assertEqual(unicode_repr(TestSerializer()), expected)
2014-09-09 20:46:28 +04:00
2014-09-18 17:23:00 +04:00
def test_nested_hyperlinked_relations(self):
2014-09-09 20:46:28 +04:00
class TestSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = RelationalModel
depth = 1
fields = '__all__'
2014-09-15 16:38:28 +04:00
expected = dedent("""
TestSerializer():
url = HyperlinkedIdentityField(view_name='relationalmodel-detail')
2014-09-18 14:20:56 +04:00
foreign_key = NestedSerializer(read_only=True):
2014-09-15 16:55:09 +04:00
url = HyperlinkedIdentityField(view_name='foreignkeytargetmodel-detail')
2014-09-15 16:38:28 +04:00
name = CharField(max_length=100)
2014-09-18 14:20:56 +04:00
one_to_one = NestedSerializer(read_only=True):
2014-09-15 16:55:09 +04:00
url = HyperlinkedIdentityField(view_name='onetoonetargetmodel-detail')
2014-09-15 16:38:28 +04:00
name = CharField(max_length=100)
2014-09-18 14:20:56 +04:00
many_to_many = NestedSerializer(many=True, read_only=True):
2014-09-15 16:55:09 +04:00
url = HyperlinkedIdentityField(view_name='manytomanytargetmodel-detail')
2014-09-15 16:38:28 +04:00
name = CharField(max_length=100)
2014-09-18 14:20:56 +04:00
through = NestedSerializer(many=True, read_only=True):
2014-09-15 17:05:58 +04:00
url = HyperlinkedIdentityField(view_name='throughtargetmodel-detail')
name = CharField(max_length=100)
2014-09-15 16:48:03 +04:00
""")
self.assertEqual(unicode_repr(TestSerializer()), expected)
2014-09-15 16:48:03 +04:00
2015-05-28 05:06:57 +03:00
def test_nested_unique_together_relations(self):
class TestSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UniqueTogetherModel
depth = 1
fields = '__all__'
2015-05-28 05:06:57 +03:00
expected = dedent("""
TestSerializer():
url = HyperlinkedIdentityField(view_name='uniquetogethermodel-detail')
foreign_key = NestedSerializer(read_only=True):
url = HyperlinkedIdentityField(view_name='foreignkeytargetmodel-detail')
name = CharField(max_length=100)
one_to_one = NestedSerializer(read_only=True):
url = HyperlinkedIdentityField(view_name='onetoonetargetmodel-detail')
name = CharField(max_length=100)
""")
2015-05-28 16:20:43 +03:00
if six.PY2:
# This case is also too awkward to resolve fully across both py2
# and py3. (See above)
expected = expected.replace(
"('foreign_key', 'one_to_one')",
"(u'foreign_key', u'one_to_one')"
)
2015-05-28 05:06:57 +03:00
self.assertEqual(unicode_repr(TestSerializer()), expected)
2014-09-18 17:23:00 +04:00
def test_pk_reverse_foreign_key(self):
2014-09-15 16:48:03 +04:00
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = ForeignKeyTargetModel
fields = ('id', 'name', 'reverse_foreign_key')
expected = dedent("""
TestSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(max_length=100)
reverse_foreign_key = PrimaryKeyRelatedField(many=True, queryset=RelationalModel.objects.all())
""")
self.assertEqual(unicode_repr(TestSerializer()), expected)
2014-09-13 00:32:20 +04:00
2014-09-18 17:23:00 +04:00
def test_pk_reverse_one_to_one(self):
2014-09-15 16:48:03 +04:00
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = OneToOneTargetModel
fields = ('id', 'name', 'reverse_one_to_one')
expected = dedent("""
TestSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(max_length=100)
reverse_one_to_one = PrimaryKeyRelatedField(queryset=RelationalModel.objects.all())
""")
self.assertEqual(unicode_repr(TestSerializer()), expected)
2014-09-15 16:48:03 +04:00
2014-09-18 17:23:00 +04:00
def test_pk_reverse_many_to_many(self):
2014-09-15 16:48:03 +04:00
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = ManyToManyTargetModel
fields = ('id', 'name', 'reverse_many_to_many')
expected = dedent("""
TestSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(max_length=100)
reverse_many_to_many = PrimaryKeyRelatedField(many=True, queryset=RelationalModel.objects.all())
""")
self.assertEqual(unicode_repr(TestSerializer()), expected)
2014-09-15 17:05:58 +04:00
2014-09-18 17:23:00 +04:00
def test_pk_reverse_through(self):
2014-09-15 17:05:58 +04:00
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = ThroughTargetModel
fields = ('id', 'name', 'reverse_through')
expected = dedent("""
TestSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(max_length=100)
reverse_through = PrimaryKeyRelatedField(many=True, read_only=True)
""")
self.assertEqual(unicode_repr(TestSerializer()), expected)
2014-09-18 17:23:00 +04:00
2015-08-10 14:19:46 +03:00
class DisplayValueTargetModel(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return '%s Color' % (self.name)
class DisplayValueModel(models.Model):
color = models.ForeignKey(DisplayValueTargetModel, on_delete=models.CASCADE)
2015-08-10 14:19:46 +03:00
class TestRelationalFieldDisplayValue(TestCase):
def setUp(self):
DisplayValueTargetModel.objects.bulk_create([
DisplayValueTargetModel(name='Red'),
DisplayValueTargetModel(name='Yellow'),
DisplayValueTargetModel(name='Green'),
])
def test_default_display_value(self):
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = DisplayValueModel
fields = '__all__'
2015-08-10 14:19:46 +03:00
serializer = TestSerializer()
expected = OrderedDict([(1, 'Red Color'), (2, 'Yellow Color'), (3, 'Green Color')])
2015-08-10 14:19:46 +03:00
self.assertEqual(serializer.fields['color'].choices, expected)
def test_custom_display_value(self):
class TestField(serializers.PrimaryKeyRelatedField):
def display_value(self, instance):
return 'My %s Color' % (instance.name)
class TestSerializer(serializers.ModelSerializer):
color = TestField(queryset=DisplayValueTargetModel.objects.all())
class Meta:
model = DisplayValueModel
fields = '__all__'
2015-08-10 14:19:46 +03:00
serializer = TestSerializer()
expected = OrderedDict([(1, 'My Red Color'), (2, 'My Yellow Color'), (3, 'My Green Color')])
2015-08-10 14:19:46 +03:00
self.assertEqual(serializer.fields['color'].choices, expected)
2014-09-18 17:23:00 +04:00
class TestIntegration(TestCase):
def setUp(self):
self.foreign_key_target = ForeignKeyTargetModel.objects.create(
name='foreign_key'
)
self.one_to_one_target = OneToOneTargetModel.objects.create(
name='one_to_one'
)
self.many_to_many_targets = [
ManyToManyTargetModel.objects.create(
name='many_to_many (%d)' % idx
) for idx in range(3)
]
self.instance = RelationalModel.objects.create(
foreign_key=self.foreign_key_target,
one_to_one=self.one_to_one_target,
)
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
set_many(self.instance, 'many_to_many', self.many_to_many_targets)
2014-09-18 17:23:00 +04:00
self.instance.save()
2014-09-19 12:09:26 +04:00
def test_pk_retrival(self):
2014-09-18 17:23:00 +04:00
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RelationalModel
fields = '__all__'
2014-09-18 17:23:00 +04:00
2014-09-19 12:09:26 +04:00
serializer = TestSerializer(self.instance)
2014-09-18 17:23:00 +04:00
expected = {
'id': self.instance.pk,
'foreign_key': self.foreign_key_target.pk,
'one_to_one': self.one_to_one_target.pk,
'many_to_many': [item.pk for item in self.many_to_many_targets],
'through': []
}
self.assertEqual(serializer.data, expected)
def test_pk_create(self):
2014-09-19 12:09:26 +04:00
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RelationalModel
fields = '__all__'
2014-09-19 12:09:26 +04:00
new_foreign_key = ForeignKeyTargetModel.objects.create(
name='foreign_key'
)
new_one_to_one = OneToOneTargetModel.objects.create(
name='one_to_one'
)
new_many_to_many = [
ManyToManyTargetModel.objects.create(
name='new many_to_many (%d)' % idx
) for idx in range(3)
]
data = {
'foreign_key': new_foreign_key.pk,
'one_to_one': new_one_to_one.pk,
'many_to_many': [item.pk for item in new_many_to_many],
}
# Serializer should validate okay.
2014-09-19 12:09:26 +04:00
serializer = TestSerializer(data=data)
assert serializer.is_valid()
# Creating the instance, relationship attributes should be set.
instance = serializer.save()
assert instance.foreign_key.pk == new_foreign_key.pk
assert instance.one_to_one.pk == new_one_to_one.pk
assert [
item.pk for item in instance.many_to_many.all()
] == [
item.pk for item in new_many_to_many
]
assert list(instance.through.all()) == []
# Representation should be correct.
expected = {
'id': instance.pk,
'foreign_key': new_foreign_key.pk,
'one_to_one': new_one_to_one.pk,
'many_to_many': [item.pk for item in new_many_to_many],
'through': []
}
self.assertEqual(serializer.data, expected)
2014-09-18 18:47:27 +04:00
def test_pk_update(self):
2014-09-19 12:09:26 +04:00
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = RelationalModel
fields = '__all__'
2014-09-19 12:09:26 +04:00
2014-09-18 18:47:27 +04:00
new_foreign_key = ForeignKeyTargetModel.objects.create(
name='foreign_key'
)
new_one_to_one = OneToOneTargetModel.objects.create(
name='one_to_one'
)
new_many_to_many = [
ManyToManyTargetModel.objects.create(
name='new many_to_many (%d)' % idx
) for idx in range(3)
]
data = {
'foreign_key': new_foreign_key.pk,
'one_to_one': new_one_to_one.pk,
'many_to_many': [item.pk for item in new_many_to_many],
}
# Serializer should validate okay.
2014-09-19 12:09:26 +04:00
serializer = TestSerializer(self.instance, data=data)
2014-09-18 18:47:27 +04:00
assert serializer.is_valid()
# Creating the instance, relationship attributes should be set.
instance = serializer.save()
assert instance.foreign_key.pk == new_foreign_key.pk
assert instance.one_to_one.pk == new_one_to_one.pk
assert [
item.pk for item in instance.many_to_many.all()
] == [
item.pk for item in new_many_to_many
]
assert list(instance.through.all()) == []
# Representation should be correct.
expected = {
'id': self.instance.pk,
'foreign_key': new_foreign_key.pk,
'one_to_one': new_one_to_one.pk,
'many_to_many': [item.pk for item in new_many_to_many],
'through': []
}
self.assertEqual(serializer.data, expected)
2014-09-19 19:43:13 +04:00
# Tests for bulk create using `ListSerializer`.
class BulkCreateModel(models.Model):
name = models.CharField(max_length=10)
class TestBulkCreate(TestCase):
def test_bulk_create(self):
class BasicModelSerializer(serializers.ModelSerializer):
class Meta:
model = BulkCreateModel
fields = ('name',)
class BulkCreateSerializer(serializers.ListSerializer):
child = BasicModelSerializer()
data = [{'name': 'a'}, {'name': 'b'}, {'name': 'c'}]
serializer = BulkCreateSerializer(data=data)
assert serializer.is_valid()
# Objects are returned by save().
instances = serializer.save()
assert len(instances) == 3
assert [item.name for item in instances] == ['a', 'b', 'c']
# Objects have been created in the database.
assert BulkCreateModel.objects.count() == 3
assert list(BulkCreateModel.objects.values_list('name', flat=True)) == ['a', 'b', 'c']
# Serializer returns correct data.
assert serializer.data == data
2014-12-05 17:15:58 +03:00
class MetaClassTestModel(models.Model):
2014-12-05 17:15:58 +03:00
text = models.CharField(max_length=100)
class TestSerializerMetaClass(TestCase):
def test_meta_class_fields_option(self):
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = MetaClassTestModel
2014-12-05 17:15:58 +03:00
fields = 'text'
with self.assertRaises(TypeError) as result:
ExampleSerializer().fields
exception = result.exception
assert str(exception).startswith(
"The `fields` option must be a list or tuple"
)
def test_meta_class_exclude_option(self):
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = MetaClassTestModel
2014-12-05 17:15:58 +03:00
exclude = 'text'
with self.assertRaises(TypeError) as result:
ExampleSerializer().fields
exception = result.exception
assert str(exception).startswith(
"The `exclude` option must be a list or tuple"
)
def test_meta_class_fields_and_exclude_options(self):
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = MetaClassTestModel
2014-12-05 17:15:58 +03:00
fields = ('text',)
exclude = ('text',)
with self.assertRaises(AssertionError) as result:
ExampleSerializer().fields
exception = result.exception
self.assertEqual(
str(exception),
"Cannot set both 'fields' and 'exclude' options on serializer ExampleSerializer."
2014-12-05 17:15:58 +03:00
)
2015-06-25 05:02:28 +03:00
class Issue2704TestCase(TestCase):
def test_queryset_all(self):
class TestSerializer(serializers.ModelSerializer):
additional_attr = serializers.CharField()
class Meta:
model = OneFieldModel
fields = ('char_field', 'additional_attr')
OneFieldModel.objects.create(char_field='abc')
qs = OneFieldModel.objects.all()
for o in qs:
o.additional_attr = '123'
serializer = TestSerializer(instance=qs, many=True)
expected = [{
'char_field': 'abc',
'additional_attr': '123',
}]
assert serializer.data == expected
class DecimalFieldModel(models.Model):
decimal_field = models.DecimalField(
max_digits=3,
decimal_places=1,
validators=[MinValueValidator(1), MaxValueValidator(3)]
)
class TestDecimalFieldMappings(TestCase):
def test_decimal_field_has_decimal_validator(self):
"""
Test that a `DecimalField` has no `DecimalValidator`.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = DecimalFieldModel
fields = '__all__'
serializer = TestSerializer()
assert len(serializer.fields['decimal_field'].validators) == 2
def test_min_value_is_passed(self):
"""
Test that the `MinValueValidator` is converted to the `min_value`
argument for the field.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = DecimalFieldModel
fields = '__all__'
serializer = TestSerializer()
assert serializer.fields['decimal_field'].min_value == 1
def test_max_value_is_passed(self):
"""
Test that the `MaxValueValidator` is converted to the `max_value`
argument for the field.
"""
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = DecimalFieldModel
fields = '__all__'
serializer = TestSerializer()
assert serializer.fields['decimal_field'].max_value == 3
class TestMetaInheritance(TestCase):
def test_extra_kwargs_not_altered(self):
class TestSerializer(serializers.ModelSerializer):
non_model_field = serializers.CharField()
class Meta:
model = OneFieldModel
read_only_fields = ('char_field', 'non_model_field')
fields = read_only_fields
extra_kwargs = {}
class ChildSerializer(TestSerializer):
class Meta(TestSerializer.Meta):
read_only_fields = ()
test_expected = dedent("""
TestSerializer():
char_field = CharField(read_only=True)
non_model_field = CharField()
""")
child_expected = dedent("""
ChildSerializer():
char_field = CharField(max_length=100)
non_model_field = CharField()
""")
self.assertEqual(unicode_repr(ChildSerializer()), child_expected)
self.assertEqual(unicode_repr(TestSerializer()), test_expected)
self.assertEqual(unicode_repr(ChildSerializer()), child_expected)
class OneToOneTargetTestModel(models.Model):
text = models.CharField(max_length=100)
class OneToOneSourceTestModel(models.Model):
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
target = models.OneToOneField(OneToOneTargetTestModel, primary_key=True, on_delete=models.CASCADE)
class TestModelFieldValues(TestCase):
def test_model_field(self):
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = OneToOneSourceTestModel
fields = ('target',)
target = OneToOneTargetTestModel(id=1, text='abc')
source = OneToOneSourceTestModel(target=target)
serializer = ExampleSerializer(source)
self.assertEqual(serializer.data, {'target': 1})
class TestUniquenessOverride(TestCase):
def test_required_not_overwritten(self):
class TestModel(models.Model):
field_1 = models.IntegerField(null=True)
field_2 = models.IntegerField()
class Meta:
unique_together = (('field_1', 'field_2'),)
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = TestModel
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
fields = '__all__'
extra_kwargs = {'field_1': {'required': False}}
fields = TestSerializer().fields
self.assertFalse(fields['field_1'].required)
self.assertTrue(fields['field_2'].required)