diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/serializers.py | 80 | ||||
| -rw-r--r-- | rest_framework/validators.py | 38 |
2 files changed, 101 insertions, 17 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 080b958d..09ad376a 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -23,6 +23,7 @@ from rest_framework.utils.field_mapping import ( get_relation_kwargs, get_nested_relation_kwargs, ClassLookupDict ) +from rest_framework.validators import UniqueTogetherValidator import copy import inspect @@ -95,7 +96,7 @@ class BaseSerializer(Field): def is_valid(self, raise_exception=False): if not hasattr(self, '_validated_data'): try: - self._validated_data = self.to_internal_value(self._initial_data) + self._validated_data = self.run_validation(self._initial_data) except ValidationError as exc: self._validated_data = {} self._errors = exc.message_dict @@ -223,15 +224,43 @@ class Serializer(BaseSerializer): return html.parse_html_dict(dictionary, prefix=self.field_name) return dictionary.get(self.field_name, empty) - def to_internal_value(self, data): + def run_validation(self, data=empty): """ - Dict of native values <- Dict of primitive datatypes. + We override the default `run_validation`, because the validation + performed by validators and the `.validate()` method should + be coerced into an error dictionary with a 'non_fields_error' key. """ + if data is empty: + if getattr(self.root, 'partial', False): + raise SkipField() + if self.required: + self.fail('required') + return self.get_default() + + if data is None: + if not self.allow_null: + self.fail('null') + return None + if not isinstance(data, dict): raise ValidationError({ api_settings.NON_FIELD_ERRORS_KEY: ['Invalid data'] }) + value = self.to_internal_value(data) + try: + self.run_validators(value) + self.validate(value) + except ValidationError as exc: + raise ValidationError({ + api_settings.NON_FIELD_ERRORS_KEY: exc.messages + }) + return value + + def to_internal_value(self, data): + """ + Dict of native values <- Dict of primitive datatypes. + """ ret = {} errors = {} fields = [field for field in self.fields.values() if not field.read_only] @@ -253,12 +282,7 @@ class Serializer(BaseSerializer): if errors: raise ValidationError(errors) - try: - return self.validate(ret) - except ValidationError as exc: - raise ValidationError({ - api_settings.NON_FIELD_ERRORS_KEY: exc.messages - }) + return ret def to_representation(self, instance): """ @@ -355,6 +379,14 @@ class ModelSerializer(Serializer): }) _related_class = PrimaryKeyRelatedField + def __init__(self, *args, **kwargs): + super(ModelSerializer, self).__init__(*args, **kwargs) + if 'validators' not in kwargs: + validators = self.get_unique_together_validators() + if validators: + self.validators.extend(validators) + self._kwargs['validators'] = validators + def create(self, attrs): ModelClass = self.Meta.model @@ -381,6 +413,36 @@ class ModelSerializer(Serializer): setattr(obj, attr, value) obj.save() + def get_unique_together_validators(self): + field_names = set([ + field.source for field in self.fields.values() + if (field.source != '*') and ('.' not in field.source) + ]) + + validators = [] + model_class = self.Meta.model + + for unique_together in model_class._meta.unique_together: + if field_names.issuperset(set(unique_together)): + validator = UniqueTogetherValidator( + queryset=model_class._default_manager, + fields=unique_together + ) + validator.serializer_field = self + validators.append(validator) + + for parent_class in model_class._meta.parents.keys(): + for unique_together in parent_class._meta.unique_together: + if field_names.issuperset(set(unique_together)): + validator = UniqueTogetherValidator( + queryset=parent_class._default_manager, + fields=unique_together + ) + validator.serializer_field = self + validators.append(validator) + + return validators + def _get_base_fields(self): declared_fields = copy.deepcopy(self._declared_fields) diff --git a/rest_framework/validators.py b/rest_framework/validators.py index f5fbeb3c..20de4b42 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -1,18 +1,26 @@ +""" +We perform uniqueness checks explicitly on the serializer class, rather +the using Django's `.full_clean()`. + +This gives us better seperation of concerns, allows us to use single-step +object creation, and makes it possible to switch between using the implicit +`ModelSerializer` class and an equivelent explicit `Serializer` class. +""" from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ +from rest_framework.utils.representation import smart_repr class UniqueValidator: # Validators with `requires_context` will have the field instance # passed to them when the field is instantiated. requires_context = True + message = _('This field must be unique.') def __init__(self, queryset): self.queryset = queryset self.serializer_field = None - def get_queryset(self): - return self.queryset.all() - def __call__(self, value): field = self.serializer_field @@ -24,15 +32,22 @@ class UniqueValidator: # Ensure uniqueness. filter_kwargs = {field_name: value} - queryset = self.get_queryset().filter(**filter_kwargs) + queryset = self.queryset.filter(**filter_kwargs) if instance: queryset = queryset.exclude(pk=instance.pk) if queryset.exists(): - raise ValidationError('This field must be unique.') + raise ValidationError(self.message) + + def __repr__(self): + return '<%s(queryset=%s)>' % ( + self.__class__.__name__, + smart_repr(self.queryset) + ) class UniqueTogetherValidator: requires_context = True + message = _('The fields {field_names} must make a unique set.') def __init__(self, queryset, fields): self.queryset = queryset @@ -49,9 +64,16 @@ class UniqueTogetherValidator: filter_kwargs = dict([ (field_name, value[field_name]) for field_name in self.fields ]) - queryset = self.get_queryset().filter(**filter_kwargs) + queryset = self.queryset.filter(**filter_kwargs) if instance: queryset = queryset.exclude(pk=instance.pk) if queryset.exists(): - field_names = ' and '.join(self.fields) - raise ValidationError('The fields %s must make a unique set.' % field_names) + field_names = ', '.join(self.fields) + raise ValidationError(self.message.format(field_names=field_names)) + + def __repr__(self): + return '<%s(queryset=%s, fields=%s)>' % ( + self.__class__.__name__, + smart_repr(self.queryset), + smart_repr(self.fields) + ) |
