diff options
Diffstat (limited to 'rest_framework/serializers.py')
| -rw-r--r-- | rest_framework/serializers.py | 123 |
1 files changed, 92 insertions, 31 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index cbb6b4df..91fbe176 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -16,7 +16,7 @@ from rest_framework.compat import get_concrete_model # This helps keep the seperation between model fields, form fields, and # serializer fields more explicit. - +from rest_framework.relations import * from rest_framework.fields import * @@ -24,7 +24,16 @@ class DictWithMetadata(dict): """ A dict-like object, that can have additional properties attached. """ - pass + def __getstate__(self): + """ + Used by pickle (e.g., caching). + Overriden to remove metadata from the dict, since it shouldn't be pickled + and may in some instances be unpickleable. + """ + # return an instance of the first dict in MRO that isn't a DictWithMetadata + for base in self.__class__.__mro__: + if not isinstance(base, DictWithMetadata) and isinstance(base, dict): + return base(self) class SortedDictWithMetadata(SortedDict, DictWithMetadata): @@ -93,7 +102,8 @@ class BaseSerializer(Field): _options_class = SerializerOptions _dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatibility with unsorted implementations. - def __init__(self, instance=None, data=None, files=None, context=None, partial=False, **kwargs): + def __init__(self, instance=None, data=None, files=None, + context=None, partial=False, **kwargs): super(BaseSerializer, self).__init__(**kwargs) self.opts = self._options_class(self.Meta) self.parent = None @@ -133,8 +143,6 @@ class BaseSerializer(Field): base_fields = copy.deepcopy(self.base_fields) for key, field in base_fields.items(): ret[key] = field - # Set up the field - field.initialize(parent=self, field_name=key) # Add in the default fields default_fields = self.get_default_fields() @@ -154,6 +162,9 @@ class BaseSerializer(Field): for key in self.opts.exclude: ret.pop(key, None) + for key, field in ret.items(): + field.initialize(parent=self, field_name=key) + return ret ##### @@ -186,6 +197,7 @@ class BaseSerializer(Field): ret.fields = {} for field_name, field in self.fields.items(): + field.initialize(parent=self, field_name=field_name) key = self.get_field_key(field_name) value = field.field_to_native(obj, field_name) ret[key] = value @@ -199,6 +211,7 @@ class BaseSerializer(Field): """ reverted_data = {} for field_name, field in self.fields.items(): + field.initialize(parent=self, field_name=field_name) try: field.field_from_native(data, files, field_name, reverted_data) except ValidationError as err: @@ -219,10 +232,18 @@ class BaseSerializer(Field): except ValidationError as err: self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) - try: - attrs = self.validate(attrs) - except ValidationError as err: - self._errors['non_field_errors'] = err.messages + # If there are already errors, we don't run .validate() because + # field-validation failed and thus `attrs` may not be complete. + # which in turn can cause inconsistent validation errors. + if not self._errors: + try: + attrs = self.validate(attrs) + except ValidationError as err: + if hasattr(err, 'message_dict'): + for field_name, error_messages in err.message_dict.items(): + self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) + elif hasattr(err, 'messages'): + self._errors['non_field_errors'] = err.messages return attrs @@ -274,15 +295,23 @@ class BaseSerializer(Field): Override default so that we can apply ModelSerializer as a nested field to relationships. """ - obj = getattr(obj, self.source or field_name) - - if is_simple_callable(obj): - obj = obj() + if self.source: + for component in self.source.split('.'): + obj = getattr(obj, component) + if is_simple_callable(obj): + obj = obj() + else: + obj = getattr(obj, field_name) + if is_simple_callable(obj): + obj = value() # If the object has an "all" method, assume it's a relationship if is_simple_callable(getattr(obj, 'all', None)): return [self.to_native(item) for item in obj.all()] + if obj is None: + return None + return self.to_native(obj) @property @@ -366,7 +395,6 @@ class ModelSerializer(Serializer): field = self.get_field(model_field) if field: - field.initialize(parent=self, field_name=model_field.name) ret[model_field.name] = field for field_name in self.opts.read_only_fields: @@ -398,10 +426,14 @@ class ModelSerializer(Serializer): """ # TODO: filter queryset using: # .using(db).complex_filter(self.rel.limit_choices_to) - queryset = model_field.rel.to._default_manager + kwargs = { + 'null': model_field.null, + 'queryset': model_field.rel.to._default_manager + } + if to_many: - return ManyPrimaryKeyRelatedField(queryset=queryset) - return PrimaryKeyRelatedField(queryset=queryset) + return ManyPrimaryKeyRelatedField(**kwargs) + return PrimaryKeyRelatedField(**kwargs) def get_field(self, model_field): """ @@ -411,7 +443,7 @@ class ModelSerializer(Serializer): kwargs['blank'] = model_field.blank - if model_field.null: + if model_field.null or model_field.blank: kwargs['required'] = False if model_field.has_default(): @@ -426,10 +458,6 @@ class ModelSerializer(Serializer): kwargs['choices'] = model_field.flatchoices return ChoiceField(**kwargs) - max_length = getattr(model_field, 'max_length', None) - if max_length: - kwargs['max_length'] = max_length - field_mapping = { models.FloatField: FloatField, models.IntegerField: IntegerField, @@ -453,28 +481,56 @@ class ModelSerializer(Serializer): except KeyError: return ModelField(model_field=model_field, **kwargs) + def get_validation_exclusions(self): + """ + Return a list of field names to exclude from model validation. + """ + cls = self.opts.model + opts = get_concrete_model(cls)._meta + exclusions = [field.name for field in opts.fields + opts.many_to_many] + for field_name, field in self.fields.items(): + if field_name in exclusions and not field.read_only: + exclusions.remove(field_name) + return exclusions + def restore_object(self, attrs, instance=None): """ Restore the model instance. """ self.m2m_data = {} + self.related_data = {} - if instance is not None: - for key, val in attrs.items(): - setattr(instance, key, val) - return instance + # Reverse fk relations + for (obj, model) in self.opts.model._meta.get_all_related_objects_with_model(): + field_name = obj.field.related_query_name() + if field_name in attrs: + self.related_data[field_name] = attrs.pop(field_name) - # Reverse relations + # Reverse m2m relations for (obj, model) in self.opts.model._meta.get_all_related_m2m_objects_with_model(): field_name = obj.field.related_query_name() if field_name in attrs: self.m2m_data[field_name] = attrs.pop(field_name) - # Forward relations + # Forward m2m relations for field in self.opts.model._meta.many_to_many: if field.name in attrs: self.m2m_data[field.name] = attrs.pop(field.name) - return self.opts.model(**attrs) + + if instance is not None: + for key, val in attrs.items(): + setattr(instance, key, val) + + else: + instance = self.opts.model(**attrs) + + try: + instance.full_clean(exclude=self.get_validation_exclusions()) + except ValidationError, err: + self._errors = err.message_dict + return None + + return instance def save(self, save_m2m=True): """ @@ -487,6 +543,11 @@ class ModelSerializer(Serializer): setattr(self.object, accessor_name, object_list) self.m2m_data = {} + if getattr(self, 'related_data', None): + for accessor_name, object_list in self.related_data.items(): + setattr(self.object, accessor_name, object_list) + self.related_data = {} + return self.object @@ -533,9 +594,9 @@ class HyperlinkedModelSerializer(ModelSerializer): # TODO: filter queryset using: # .using(db).complex_filter(self.rel.limit_choices_to) rel = model_field.rel.to - queryset = rel._default_manager kwargs = { - 'queryset': queryset, + 'null': model_field.null, + 'queryset': rel._default_manager, 'view_name': self._get_default_view_name(rel) } if to_many: |
