diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index aa5cc84ea..b65e43867 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -491,7 +491,7 @@ As an example, let's create a field that can be used represent the class name of # We pass the object instance onto `to_representation`, # not just the field attribute. return obj - + def to_representation(self, obj): """ Serialize the object's class name. @@ -522,7 +522,7 @@ To indicate invalid data, we should raise a `serializers.ValidationError`, like The `.fail()` method is a shortcut for raising `ValidationError` that takes a message string from the `error_messages` dictionary. For example: default_error_messages = { - 'incorrect_type': 'Incorrect type. Expected a string, but got {input_type}', + 'incorrect_type': 'Incorrect type. Expected a string, but got %(input_type)s', 'incorrect_format': 'Incorrect format. Expected `rgb(#,#,#)`.', 'out_of_range': 'Value out of range. Must be between 0 and 255.' } diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 37adbe16f..38f94a14f 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ValidationError as DjangoValidationError @@ -6,7 +7,7 @@ from django.forms import ImageField as DjangoImageField from django.utils import six, timezone from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.encoding import is_protected_type, smart_text -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy, ugettext_lazy as _ from rest_framework import ISO_8601 from rest_framework.compat import ( EmailValidator, MinValueValidator, MaxValueValidator, @@ -147,7 +148,7 @@ class Field(object): default_error_messages = { 'required': _('This field is required.'), - 'null': _('This field may not be null.') + 'null': _('This field cannot be null.') } default_validators = [] default_empty_html = empty @@ -369,7 +370,7 @@ class Field(object): class_name = self.__class__.__name__ msg = MISSING_ERROR_MESSAGE.format(class_name=class_name, key=key) raise AssertionError(msg) - message_string = msg.format(**kwargs) + message_string = msg % kwargs raise ValidationError(message_string) @property @@ -429,7 +430,7 @@ class Field(object): class BooleanField(Field): default_error_messages = { - 'invalid': _('`{input}` is not a valid boolean.') + 'invalid': _('`%(input)s` is not a valid boolean.') } default_empty_html = False initial = False @@ -457,7 +458,7 @@ class BooleanField(Field): class NullBooleanField(Field): default_error_messages = { - 'invalid': _('`{input}` is not a valid boolean.') + 'invalid': _('`%(input)s` is not a valid boolean.') } initial = None TRUE_VALUES = set(('t', 'T', 'true', 'True', 'TRUE', '1', 1, True)) @@ -492,9 +493,15 @@ class NullBooleanField(Field): class CharField(Field): default_error_messages = { - 'blank': _('This field may not be blank.'), - 'max_length': _('Ensure this field has no more than {max_length} characters.'), - 'min_length': _('Ensure this field has no more than {min_length} characters.') + 'blank': _('This field cannot be blank.'), + 'max_length': ungettext_lazy( + 'Ensure this value has at most %(limit_value)d character (it has %(show_value)d).', + 'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).', + 'limit_value'), + 'min_length': ungettext_lazy( + 'Ensure this value has at least %(limit_value)d character (it has %(show_value)d).', + 'Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).', + 'limit_value') } initial = '' coerce_blank_to_null = False @@ -502,15 +509,9 @@ class CharField(Field): def __init__(self, **kwargs): self.allow_blank = kwargs.pop('allow_blank', False) - max_length = kwargs.pop('max_length', None) - min_length = kwargs.pop('min_length', None) + self.max_length = kwargs.pop('max_length', None) + self.min_length = kwargs.pop('min_length', None) super(CharField, self).__init__(**kwargs) - if max_length is not None: - message = self.error_messages['max_length'].format(max_length=max_length) - self.validators.append(MaxLengthValidator(max_length, message=message)) - if min_length is not None: - message = self.error_messages['min_length'].format(min_length=min_length) - self.validators.append(MinLengthValidator(min_length, message=message)) def run_validation(self, data=empty): # Test for the empty string here so that it does not get validated, @@ -520,6 +521,17 @@ class CharField(Field): if not self.allow_blank: self.fail('blank') return '' + + if self.max_length is not None: + params = {'limit_value': self.max_length, 'show_value': data} + message = self.error_messages['max_length'] % params + self.validators.append(MaxLengthValidator(self.max_length, message=message)) + + if self.min_length is not None: + params = {'limit_value': self.min_length, 'show_value': data} + message = self.error_messages['min_length'] % params + self.validators.append(MinLengthValidator(self.min_length, message=message)) + return super(CharField, self).run_validation(data) def to_internal_value(self, data): @@ -571,7 +583,7 @@ class SlugField(CharField): class URLField(CharField): default_error_messages = { - 'invalid': _("Enter a valid URL.") + 'invalid': _('Enter a valid URL.') } def __init__(self, **kwargs): @@ -585,8 +597,8 @@ class URLField(CharField): class IntegerField(Field): default_error_messages = { 'invalid': _('A valid integer is required.'), - 'max_value': _('Ensure this value is less than or equal to {max_value}.'), - 'min_value': _('Ensure this value is greater than or equal to {min_value}.'), + 'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), + 'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 'max_string_length': _('String value too large') } MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. @@ -596,10 +608,10 @@ class IntegerField(Field): min_value = kwargs.pop('min_value', None) super(IntegerField, self).__init__(**kwargs) if max_value is not None: - message = self.error_messages['max_value'].format(max_value=max_value) + message = self.error_messages['max_value'] % {'limit_value': max_value} self.validators.append(MaxValueValidator(max_value, message=message)) if min_value is not None: - message = self.error_messages['min_value'].format(min_value=min_value) + message = self.error_messages['min_value'] % {'limit_value': min_value} self.validators.append(MinValueValidator(min_value, message=message)) def to_internal_value(self, data): @@ -618,9 +630,9 @@ class IntegerField(Field): class FloatField(Field): default_error_messages = { - 'invalid': _("A valid number is required."), - 'max_value': _('Ensure this value is less than or equal to {max_value}.'), - 'min_value': _('Ensure this value is greater than or equal to {min_value}.'), + 'invalid': _('A valid number is required.'), + 'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), + 'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 'max_string_length': _('String value too large') } MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. @@ -630,10 +642,12 @@ class FloatField(Field): min_value = kwargs.pop('min_value', None) super(FloatField, self).__init__(**kwargs) if max_value is not None: - message = self.error_messages['max_value'].format(max_value=max_value) + params = {'limit_value': max_value} + message = self.error_messages['max_value'] % params self.validators.append(MaxValueValidator(max_value, message=message)) if min_value is not None: - message = self.error_messages['min_value'].format(min_value=min_value) + params = {'limit_value': min_value} + message = self.error_messages['min_value'] % params self.validators.append(MinValueValidator(min_value, message=message)) def to_internal_value(self, data): @@ -652,11 +666,20 @@ class FloatField(Field): class DecimalField(Field): default_error_messages = { 'invalid': _('A valid number is required.'), - 'max_value': _('Ensure this value is less than or equal to {max_value}.'), - 'min_value': _('Ensure this value is greater than or equal to {min_value}.'), - 'max_digits': _('Ensure that there are no more than {max_digits} digits in total.'), - 'max_decimal_places': _('Ensure that there are no more than {max_decimal_places} decimal places.'), - 'max_whole_digits': _('Ensure that there are no more than {max_whole_digits} digits before the decimal point.'), + 'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), + 'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), + 'max_digits': ungettext_lazy( + 'Ensure that there are no more than %(max)s digit in total.', + 'Ensure that there are no more than %(max)s digits in total.', + 'max'), + 'max_decimal_places': ungettext_lazy( + 'Ensure that there are no more than %(max)s decimal place.', + 'Ensure that there are no more than %(max)s decimal places.', + 'max'), + 'max_whole_digits': ungettext_lazy( + 'Ensure that there are no more than %(max)s digit before the decimal point.', + 'Ensure that there are no more than %(max)s digits before the decimal point.', + 'max'), 'max_string_length': _('String value too large') } MAX_STRING_LENGTH = 1000 # Guard against malicious string inputs. @@ -669,10 +692,12 @@ class DecimalField(Field): self.coerce_to_string = coerce_to_string if (coerce_to_string is not None) else self.coerce_to_string super(DecimalField, self).__init__(**kwargs) if max_value is not None: - message = self.error_messages['max_value'].format(max_value=max_value) + params = {'limit_value': max_value} + message = self.error_messages['max_value'] % params self.validators.append(MaxValueValidator(max_value, message=message)) if min_value is not None: - message = self.error_messages['min_value'].format(min_value=min_value) + params = {'limit_value': min_value} + message = self.error_messages['min_value'] % params self.validators.append(MinValueValidator(min_value, message=message)) def to_internal_value(self, data): @@ -713,11 +738,11 @@ class DecimalField(Field): whole_digits = digits - decimals if self.max_digits is not None and digits > self.max_digits: - self.fail('max_digits', max_digits=self.max_digits) + self.fail('max_digits', max=self.max_digits) if self.decimal_places is not None and decimals > self.decimal_places: - self.fail('max_decimal_places', max_decimal_places=self.decimal_places) + self.fail('max_decimal_places', max=self.decimal_places) if self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places): - self.fail('max_whole_digits', max_whole_digits=self.max_digits - self.decimal_places) + self.fail('max_whole_digits', max=self.max_digits - self.decimal_places) return value @@ -740,7 +765,7 @@ class DecimalField(Field): class DateTimeField(Field): 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)s'), 'date': _('Expected a datetime but got a date.'), } format = api_settings.DATETIME_FORMAT @@ -805,7 +830,7 @@ class DateTimeField(Field): class DateField(Field): 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)s'), 'datetime': _('Expected a date but got a datetime.'), } format = api_settings.DATE_FORMAT @@ -863,7 +888,7 @@ class DateField(Field): class TimeField(Field): 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)s'), } format = api_settings.TIME_FORMAT input_formats = api_settings.TIME_INPUT_FORMATS @@ -919,7 +944,7 @@ class TimeField(Field): class ChoiceField(Field): default_error_messages = { - 'invalid_choice': _('`{input}` is not a valid choice.') + 'invalid_choice': _('Value %(value)r is not a valid choice.') } def __init__(self, choices, **kwargs): @@ -948,7 +973,7 @@ class ChoiceField(Field): try: return self.choice_strings_to_values[six.text_type(data)] except KeyError: - self.fail('invalid_choice', input=data) + self.fail('invalid_choice', value=data) def to_representation(self, value): if value in ('', None): @@ -958,8 +983,8 @@ class ChoiceField(Field): class MultipleChoiceField(ChoiceField): default_error_messages = { - 'invalid_choice': _('`{input}` is not a valid choice.'), - 'not_a_list': _('Expected a list of items but got type `{input_type}`.') + 'invalid_choice': _('Value %(value)r is not a valid choice.'), + 'not_a_list': _('Expected a list of items but got type `%(input_type)s`.') } default_empty_html = [] @@ -989,11 +1014,14 @@ class MultipleChoiceField(ChoiceField): class FileField(Field): default_error_messages = { - 'required': _("No file was submitted."), - 'invalid': _("The submitted data was not a file. Check the encoding type on the form."), - 'no_name': _("No filename could be determined."), - 'empty': _("The submitted file is empty."), - 'max_length': _('Ensure this filename has at most {max_length} characters (it has {length}).'), + 'required': _('No file was submitted.'), + 'invalid': _('The submitted data was not a file. Check the encoding type on the form.'), + 'no_name': _('No filename could be determined.'), + 'empty': _('The submitted file is empty.'), + 'max_length': ungettext_lazy( + 'Ensure this filename has at most %(max)d character (it has %(length)d).', + 'Ensure this filename has at most %(max)d characters (it has %(length)d).', + 'max'), } use_url = api_settings.UPLOADED_FILES_USE_URL @@ -1016,7 +1044,7 @@ class FileField(Field): if not self.allow_empty_file and not file_size: self.fail('empty') if self.max_length and len(file_name) > self.max_length: - self.fail('max_length', max_length=self.max_length, length=len(file_name)) + self.fail('max_length', max=self.max_length, length=len(file_name)) return data @@ -1061,7 +1089,7 @@ class ListField(Field): child = None initial = [] default_error_messages = { - '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)s`') } def __init__(self, *args, **kwargs): @@ -1192,18 +1220,26 @@ class ModelField(Field): that do not have a serializer field to be mapped to. """ default_error_messages = { - 'max_length': _('Ensure this field has no more than {max_length} characters.'), + 'max_length': ungettext_lazy( + 'Ensure this value has at most %(limit_value)d character (it has %(show_value)d).', + 'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).', + 'limit_value'), } def __init__(self, model_field, **kwargs): self.model_field = model_field # The `max_length` option is supported by Django's base `Field` class, # so we'd better support it here. - max_length = kwargs.pop('max_length', None) + self.max_length = kwargs.pop('max_length', None) super(ModelField, self).__init__(**kwargs) - if max_length is not None: - message = self.error_messages['max_length'].format(max_length=max_length) - self.validators.append(MaxLengthValidator(max_length, message=message)) + + def run_validation(self, data=empty): + if self.max_length is not None: + params = {'limit_value': self.max_length, 'show_value': data} + message = self.error_messages['max_length'] % params + self.validators.append(MaxLengthValidator(self.max_length, message=message)) + + return super(ModelField, self).run_validation(data) def to_internal_value(self, data): rel = getattr(self.model_field, 'rel', None) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 178a8e2b0..a356b2747 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -116,8 +116,8 @@ class StringRelatedField(RelatedField): class PrimaryKeyRelatedField(RelatedField): default_error_messages = { 'required': 'This field is required.', - 'does_not_exist': "Invalid pk '{pk_value}' - object does not exist.", - 'incorrect_type': 'Incorrect type. Expected pk value, received {data_type}.', + 'does_not_exist': "Invalid pk '%(pk_value)s' - object does not exist.", + 'incorrect_type': 'Incorrect type. Expected pk value, received %(data_type)s.', } def to_internal_value(self, data): @@ -166,7 +166,7 @@ class HyperlinkedRelatedField(RelatedField): 'no_match': 'Invalid hyperlink - No URL match', 'incorrect_match': 'Invalid hyperlink - Incorrect URL match.', 'does_not_exist': 'Invalid hyperlink - Object does not exist.', - 'incorrect_type': 'Incorrect type. Expected URL string, received {data_type}.', + 'incorrect_type': 'Incorrect type. Expected URL string, received %(data_type)s.', } def __init__(self, view_name=None, **kwargs): @@ -293,7 +293,7 @@ class SlugRelatedField(RelatedField): """ default_error_messages = { - 'does_not_exist': _("Object with {slug_name}={value} does not exist."), + 'does_not_exist': _("Object with %(slug_name)s=%(value)s does not exist."), 'invalid': _('Invalid value.'), } diff --git a/tests/test_bound_fields.py b/tests/test_bound_fields.py index 469437e4b..c237d119f 100644 --- a/tests/test_bound_fields.py +++ b/tests/test_bound_fields.py @@ -39,7 +39,7 @@ class TestSimpleBoundField: serializer.is_valid() assert serializer['text'].value == 'x' * 1000 - assert serializer['text'].errors == ['Ensure this field has no more than 100 characters.'] + assert serializer['text'].errors == ['Ensure this value has at most 100 characters (it has 1000).'] assert serializer['text'].name == 'text' assert serializer['amount'].value is 123 assert serializer['amount'].errors is None diff --git a/tests/test_fields.py b/tests/test_fields.py index 135256320..9b8cf0bf6 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1,5 +1,7 @@ +# -*- coding: utf-8 -*- from decimal import Decimal from django.utils import timezone +from django.test.utils import override_settings from rest_framework import serializers import datetime import django @@ -22,6 +24,16 @@ class TestEmpty: field.run_validation() assert exc_info.value.detail == ['This field is required.'] + @override_settings(LANGUAGE_CODE='es') + def test_required_translated(self): + """ + By default '' is not a valid input. + """ + field = serializers.IntegerField() + with pytest.raises(serializers.ValidationError) as exc_info: + field.run_validation() + assert exc_info.value.detail == ['Este campo es obligatorio.'] + def test_not_required(self): """ If `required=False` then a field may be omitted from the input. @@ -37,7 +49,7 @@ class TestEmpty: field = serializers.IntegerField() with pytest.raises(serializers.ValidationError) as exc_info: field.run_validation(None) - assert exc_info.value.detail == ['This field may not be null.'] + assert exc_info.value.detail == ['This field cannot be null.'] def test_allow_null(self): """ @@ -54,7 +66,7 @@ class TestEmpty: field = serializers.CharField() with pytest.raises(serializers.ValidationError) as exc_info: field.run_validation('') - assert exc_info.value.detail == ['This field may not be blank.'] + assert exc_info.value.detail == ['This field cannot be blank.'] def test_allow_blank(self): """ @@ -62,7 +74,7 @@ class TestEmpty: """ field = serializers.CharField(allow_blank=True) output = field.run_validation('') - assert output is '' + assert str(output) is str('') def test_default(self): """ @@ -278,6 +290,24 @@ class FieldValues: for output_value, expected_output in get_items(self.outputs): assert self.field.to_representation(output_value) == expected_output + @override_settings(LANGUAGE_CODE='es') + def test_invalid_inputs_translated(self): + """ + Ensure that invalid values raise the expected validation localized error. + """ + es_invalid_inputs = get_items(getattr(self, 'es_invalid_inputs', {})) + + if not es_invalid_inputs: + return + + field_kwargs = getattr(self, 'field_kwargs', {}) + field = self.field.__class__(**field_kwargs) + + for input_value, expected_failure in es_invalid_inputs: + with pytest.raises(serializers.ValidationError) as exc_info: + field.run_validation(input_value) + assert exc_info.value.detail == expected_failure + # Boolean types... @@ -297,7 +327,11 @@ class TestBooleanField(FieldValues): } invalid_inputs = { 'foo': ['`foo` is not a valid boolean.'], - None: ['This field may not be null.'] + None: ['This field cannot be null.'] + } + es_invalid_inputs = { + 'foo': ['`foo` is not a valid boolean.'], + None: ['Este campo no puede ser nulo.'] } outputs = { 'true': True, @@ -351,7 +385,10 @@ class TestCharField(FieldValues): 'abc': 'abc' } invalid_inputs = { - '': ['This field may not be blank.'] + '': ['This field cannot be blank.'] + } + es_invalid_inputs = { + '': [u'Este campo no puede estar vacío.'] } outputs = { 1: '1', @@ -371,6 +408,9 @@ class TestEmailField(FieldValues): invalid_inputs = { 'examplecom': ['Enter a valid email address.'] } + es_invalid_inputs = { + 'examplecom': [u'Introduzca una dirección de correo electrónico válida.'] + } outputs = {} field = serializers.EmailField() @@ -386,7 +426,8 @@ class TestRegexField(FieldValues): 'A9': ["This value does not match the required pattern."] } outputs = {} - field = serializers.RegexField(regex='[a-z][0-9]') + field_kwargs = {'regex': '[a-z][0-9]'} + field = serializers.RegexField(**field_kwargs) class TestSlugField(FieldValues): @@ -399,6 +440,9 @@ class TestSlugField(FieldValues): invalid_inputs = { 'slug 99': ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."] } + es_invalid_inputs = { + 'slug 99': [u"Introduzca un 'slug' válido, consistente en letras, números, guiones bajos o medios."] + } outputs = {} field = serializers.SlugField() @@ -413,6 +457,9 @@ class TestURLField(FieldValues): invalid_inputs = { 'example.com': ['Enter a valid URL.'] } + es_invalid_inputs = { + 'example.com': [u'Introduzca una URL válida.'] + } outputs = {} field = serializers.URLField() @@ -434,6 +481,9 @@ class TestIntegerField(FieldValues): invalid_inputs = { 'abc': ['A valid integer is required.'] } + es_invalid_inputs = { + 'abc': ['A valid integer is required.'] + } outputs = { '1': 1, '0': 0, @@ -461,8 +511,18 @@ class TestMinMaxIntegerField(FieldValues): '0': ['Ensure this value is greater than or equal to 1.'], '4': ['Ensure this value is less than or equal to 3.'], } + es_invalid_inputs = { + 0: [u'Asegúrese de que este valor es mayor o igual a 1.'], + 4: [u'Asegúrese de que este valor es menor o igual a 3.'], + '0': [u'Asegúrese de que este valor es mayor o igual a 1.'], + '4': [u'Asegúrese de que este valor es menor o igual a 3.'], + } outputs = {} - field = serializers.IntegerField(min_value=1, max_value=3) + field_kwargs = { + 'min_value': 1, + 'max_value': 3, + } + field = serializers.IntegerField(**field_kwargs) class TestFloatField(FieldValues): @@ -480,6 +540,9 @@ class TestFloatField(FieldValues): invalid_inputs = { 'abc': ["A valid number is required."] } + es_invalid_inputs = { + 'abc': ['A valid number is required.'] + } outputs = { '1': 1.0, '0': 0.0, @@ -509,8 +572,15 @@ class TestMinMaxFloatField(FieldValues): '0.0': ['Ensure this value is greater than or equal to 1.'], '3.1': ['Ensure this value is less than or equal to 3.'], } + es_invalid_inputs = { + 0.9: [u'Asegúrese de que este valor es mayor o igual a 1.'], + 3.1: [u'Asegúrese de que este valor es menor o igual a 3.'], + '0.0': [u'Asegúrese de que este valor es mayor o igual a 1.'], + '3.1': [u'Asegúrese de que este valor es menor o igual a 3.'], + } outputs = {} - field = serializers.FloatField(min_value=1, max_value=3) + field_kwargs = {'min_value': 1, 'max_value': 3} + field = serializers.FloatField(**field_kwargs) class TestDecimalField(FieldValues): @@ -530,9 +600,17 @@ class TestDecimalField(FieldValues): (Decimal('Nan'), ["A valid number is required."]), (Decimal('Inf'), ["A valid number is required."]), ('12.345', ["Ensure that there are no more than 3 digits in total."]), - ('0.01', ["Ensure that there are no more than 1 decimal places."]), + ('0.01', ["Ensure that there are no more than 1 decimal place."]), (123, ["Ensure that there are no more than 2 digits before the decimal point."]) ) + es_invalid_inputs = ( + ('abc', ["A valid number is required."]), + (Decimal('Nan'), ["A valid number is required."]), + (Decimal('Inf'), ["A valid number is required."]), + ('12.345', [u'Asegúrese de que no hay más de 3 dígitos en total.']), + ('0.01', [u'Asegúrese de que no haya más de 1 dígito decimal.']), + (123, [u'Asegúrese de que no haya más de 2 dígitos antes del punto decimal.']) + ) outputs = { '1': '1.0', '0': '0.0', @@ -545,7 +623,8 @@ class TestDecimalField(FieldValues): Decimal('1.09'): '1.1', Decimal('0.04'): '0.0' } - field = serializers.DecimalField(max_digits=3, decimal_places=1) + field_kwargs = {'max_digits': 3, 'decimal_places': 1} + field = serializers.DecimalField(**field_kwargs) class TestMinMaxDecimalField(FieldValues): @@ -560,11 +639,16 @@ class TestMinMaxDecimalField(FieldValues): '9.9': ['Ensure this value is greater than or equal to 10.'], '20.1': ['Ensure this value is less than or equal to 20.'], } + es_invalid_inputs = { + '9.9': [u'Asegúrese de que este valor es mayor o igual a 10.'], + '20.1': [u'Asegúrese de que este valor es menor o igual a 20.'], + } outputs = {} - field = serializers.DecimalField( - max_digits=3, decimal_places=1, - min_value=10, max_value=20 - ) + field_kwargs = { + 'max_digits': 3, 'decimal_places': 1, + 'min_value': 10, 'max_value': 20 + } + field = serializers.DecimalField(**field_kwargs) class TestNoStringCoercionDecimalField(FieldValues): @@ -581,10 +665,11 @@ class TestNoStringCoercionDecimalField(FieldValues): Decimal('1.09'): Decimal('1.1'), Decimal('0.04'): Decimal('0.0'), } - field = serializers.DecimalField( - max_digits=3, decimal_places=1, - coerce_to_string=False - ) + field_kwargs = { + 'max_digits': 3, 'decimal_places': 1, + 'coerce_to_string': False + } + field = serializers.DecimalField(**field_kwargs) # Date & time serializers... @@ -619,7 +704,8 @@ class TestCustomInputFormatDateField(FieldValues): '2001-01-01': ['Date has wrong format. Use one of these formats instead: DD [Jan-Dec] YYYY'] } outputs = {} - field = serializers.DateField(input_formats=['%d %b %Y']) + field_kwargs = {'input_formats': ['%d %b %Y']} + field = serializers.DateField(**field_kwargs) class TestCustomOutputFormatDateField(FieldValues): @@ -790,19 +876,23 @@ class TestChoiceField(FieldValues): 'good': 'good', } invalid_inputs = { - 'amazing': ['`amazing` is not a valid choice.'] + 'amazing': ["Value 'amazing' is not a valid choice."] + } + es_invalid_inputs = { + 'amazing': [u"Valor 'amazing' no es una opción válida."] } outputs = { 'good': 'good', '': '' } - field = serializers.ChoiceField( - choices=[ + field_kwargs = { + 'choices': [ ('poor', 'Poor quality'), ('medium', 'Medium quality'), ('good', 'Good quality'), ] - ) + } + field = serializers.ChoiceField(**field_kwargs) class TestChoiceFieldWithType(FieldValues): @@ -815,20 +905,25 @@ class TestChoiceFieldWithType(FieldValues): 3: 3, } invalid_inputs = { - 5: ['`5` is not a valid choice.'], - 'abc': ['`abc` is not a valid choice.'] + 5: ['Value 5 is not a valid choice.'], + 'abc': ["Value 'abc' is not a valid choice."] + } + es_invalid_inputs = { + 5: [u'Valor 5 no es una opción válida.'], + 'abc': [u"Valor 'abc' no es una opción válida."] } outputs = { '1': 1, 1: 1 } - field = serializers.ChoiceField( - choices=[ + field_kwargs = { + 'choices': [ (1, 'Poor quality'), (2, 'Medium quality'), (3, 'Good quality'), ] - ) + } + field = serializers.ChoiceField(**field_kwargs) class TestChoiceFieldWithListChoices(FieldValues): @@ -842,12 +937,16 @@ class TestChoiceFieldWithListChoices(FieldValues): 'good': 'good', } invalid_inputs = { - 'awful': ['`awful` is not a valid choice.'] + 'awful': ["Value 'awful' is not a valid choice."] + } + es_invalid_inputs = { + 'awful': [u"Valor 'awful' no es una opción válida."] } outputs = { 'good': 'good' } - field = serializers.ChoiceField(choices=('poor', 'medium', 'good')) + field_kwargs = {'choices': ('poor', 'medium', 'good')} + field = serializers.ChoiceField(**field_kwargs) class TestMultipleChoiceField(FieldValues): @@ -861,18 +960,19 @@ class TestMultipleChoiceField(FieldValues): } invalid_inputs = { 'abc': ['Expected a list of items but got type `str`.'], - ('aircon', 'incorrect'): ['`incorrect` is not a valid choice.'] + ('aircon', 'incorrect'): ["Value 'incorrect' is not a valid choice."] } outputs = [ (['aircon', 'manual'], set(['aircon', 'manual'])) ] - field = serializers.MultipleChoiceField( - choices=[ + field_kwargs = { + 'choices': [ ('aircon', 'AirCon'), ('manual', 'Manual drive'), ('diesel', 'Diesel'), ] - ) + } + field = serializers.MultipleChoiceField(**field_kwargs) # File serializers... diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index b938e3857..24161f6a4 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -302,7 +302,7 @@ class HyperlinkedForeignKeyTests(TestCase): instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data, context={'request': request}) self.assertFalse(serializer.is_valid()) - self.assertEqual(serializer.errors, {'target': ['This field may not be null.']}) + self.assertEqual(serializer.errors, {'target': ['This field cannot be null.']}) class HyperlinkedNullableForeignKeyTests(TestCase): diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index e95a877e1..83cd6cd81 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -282,7 +282,7 @@ class PKForeignKeyTests(TestCase): instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data) self.assertFalse(serializer.is_valid()) - self.assertEqual(serializer.errors, {'target': ['This field may not be null.']}) + self.assertEqual(serializer.errors, {'target': ['This field cannot be null.']}) def test_foreign_key_with_empty(self): """ diff --git a/tests/test_relations_slug.py b/tests/test_relations_slug.py index 7bac90460..84c3704f8 100644 --- a/tests/test_relations_slug.py +++ b/tests/test_relations_slug.py @@ -160,7 +160,7 @@ class SlugForeignKeyTests(TestCase): instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data) self.assertFalse(serializer.is_valid()) - self.assertEqual(serializer.errors, {'target': ['This field may not be null.']}) + self.assertEqual(serializer.errors, {'target': ['This field cannot be null.']}) class SlugNullableForeignKeyTests(TestCase):