diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5c726dfcd..b17f71af8 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -346,7 +346,13 @@ class BaseSerializer(WritableField): continue field.initialize(parent=self, field_name=field_name) key = self.get_field_key(field_name) - value = field.field_to_native(obj, field_name) + try: + value = field.field_to_native(obj, field_name) + except AttributeError: + if field_name in self.opts.non_native_fields: + continue + else: + raise method = getattr(self, 'transform_%s' % field_name, None) if callable(method): value = method(obj, value) @@ -386,6 +392,9 @@ class BaseSerializer(WritableField): Override default so that the serializer can be used as a nested field across relationships. """ + if field_name in self.opts.non_native_fields: + return None + if self.write_only: return None @@ -622,6 +631,7 @@ class ModelSerializerOptions(SerializerOptions): self.model = getattr(meta, 'model', None) self.read_only_fields = getattr(meta, 'read_only_fields', ()) self.write_only_fields = getattr(meta, 'write_only_fields', ()) + self.non_native_fields = getattr(meta, 'non_native_fields', ()) class ModelSerializer(Serializer): @@ -782,7 +792,7 @@ class ModelSerializer(Serializer): "Non-existant field '%s' specified in `write_only_fields` " "on serializer '%s'." % (field_name, self.__class__.__name__)) - ret[field_name].write_only = True + ret[field_name].write_only = True return ret @@ -923,6 +933,10 @@ class ModelSerializer(Serializer): nested_forward_relations = {} meta = self.opts.model._meta + for field_name in self.opts.non_native_fields: + if field_name in attrs: + attrs.pop(field_name) + # Reverse fk or one-to-one relations for (obj, model) in meta.get_all_related_objects_with_model(): field_name = obj.get_accessor_name() diff --git a/rest_framework/tests/test_non_native_fields.py b/rest_framework/tests/test_non_native_fields.py new file mode 100644 index 000000000..917b11213 --- /dev/null +++ b/rest_framework/tests/test_non_native_fields.py @@ -0,0 +1,51 @@ +from django.db import models +from django.test import TestCase +from rest_framework import serializers + + +class ExampleModel(models.Model): + email = models.EmailField(max_length=100) + password = models.CharField(max_length=100) + + +class ExampleSerializer(serializers.ModelSerializer): + password_confirmation = serializers.CharField() + def validate_password_confirmation(self, attrs, source): + password_confirmation = attrs[source] + password = attrs['password'] + if password_confirmation != password: + raise serializers.ValidationError('Password confirmation mismatch') + attrs.pop(source) + return attrs + class Meta: + model = ExampleModel + fields = ('email', 'password', 'password_confirmation',) + write_only_fields = ('password',) + non_native_fields = ('password_confirmation',) + + +class NonNativeFieldTests(TestCase): + def test_non_native_fields(self): + data = { + 'email': 'foo@example.com', + 'password': '123', + 'password_confirmation': '123', + } + serializer = ExampleSerializer(data=data) + self.assertTrue(serializer.is_valid()) + self.assertTrue(isinstance(serializer.object, ExampleModel)) + self.assertEquals(serializer.object.email, data['email']) + self.assertEquals(serializer.object.password, data['password']) + self.assertEquals(serializer.data, {'email': 'foo@example.com'}) + + def test_non_native_fields_validation_error(self): + data = { + 'email': 'foo@example.com', + 'password': '123', + 'password_confirmation': 'abc', + } + serializer = ExampleSerializer(data=data) + self.assertFalse(serializer.is_valid()) + self.assertEquals(len(serializer.errors), 1) + self.assertEquals(serializer.errors['password_confirmation'], + ['Password confirmation mismatch'])