mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-03 12:00:12 +03:00
Merge 3a11802032
into abe14c06f7
This commit is contained in:
commit
824a347682
30
docs/api-guide/serializers.md
Normal file → Executable file
30
docs/api-guide/serializers.md
Normal file → Executable file
|
@ -405,6 +405,36 @@ You can add extra fields to a `ModelSerializer` or override the default fields b
|
|||
|
||||
Extra fields can correspond to any property or callable on the model.
|
||||
|
||||
## Specifying non native (extra) fields
|
||||
|
||||
You can add extra fields to a `ModelSerializer` which do not correspond to any particular property on the model on which you can do custom processing with the `non_native_fields` Meta option.
|
||||
|
||||
A common example of this case is to be able to specify a `password_confirmation` field:
|
||||
|
||||
class UserSerializer(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 = User
|
||||
fields = ('email', 'password', 'password_confirmation',)
|
||||
write_only_fields = ('password',)
|
||||
non_native_fields = ('password_confirmation',)
|
||||
|
||||
In this case the `password_confirmation` field will be shown in the browsable API form but if a POST request is done
|
||||
it won't be automatically saved into the database but will be available for custom processing, which in this case is just a simple check to compare the two password fields.
|
||||
|
||||
The peculiarity of `non_native_fields` is that the fields specified in that list will be visible in the browsable API form and in the OPTIONS http response of the view.
|
||||
|
||||
## Relational fields
|
||||
|
||||
When serializing model instances, there are a number of different ways you might choose to represent relationships. The default representation for `ModelSerializer` is to use the primary keys of the related instances.
|
||||
|
|
27
rest_framework/serializers.py
Normal file → Executable file
27
rest_framework/serializers.py
Normal file → Executable file
|
@ -346,7 +346,16 @@ 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 as e:
|
||||
# non_native_fields check is done only in ModelSerializer
|
||||
if field_name in getattr(self.opts, 'non_native_fields', []):
|
||||
continue
|
||||
else:
|
||||
raise e
|
||||
|
||||
method = getattr(self, 'transform_%s' % field_name, None)
|
||||
if callable(method):
|
||||
value = method(obj, value)
|
||||
|
@ -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
|
||||
|
||||
|
@ -897,6 +907,15 @@ class ModelSerializer(Serializer):
|
|||
and not isinstance(field, Serializer):
|
||||
exclusions.remove(field_name)
|
||||
return exclusions
|
||||
|
||||
def field_to_native(self, obj, field_name):
|
||||
"""
|
||||
Add support to non_native_fields
|
||||
"""
|
||||
if field_name in self.opts.non_native_fields:
|
||||
return None
|
||||
|
||||
return super(ModelSerializer, self).field_to_native(obj, field_name)
|
||||
|
||||
def full_clean(self, instance):
|
||||
"""
|
||||
|
@ -923,6 +942,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()
|
||||
|
|
81
rest_framework/tests/test_non_native_fields.py
Executable file
81
rest_framework/tests/test_non_native_fields.py
Executable file
|
@ -0,0 +1,81 @@
|
|||
from django.db import models
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework import generics
|
||||
from rest_framework.compat import patterns, url
|
||||
|
||||
|
||||
class NonNativeExampleModel(models.Model):
|
||||
email = models.EmailField(max_length=100)
|
||||
password = models.CharField(max_length=100)
|
||||
|
||||
|
||||
class NonNativeExampleSerializer(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 = NonNativeExampleModel
|
||||
fields = ('email', 'password', 'password_confirmation',)
|
||||
write_only_fields = ('password',)
|
||||
non_native_fields = ('password_confirmation',)
|
||||
|
||||
|
||||
class NonNativeExampleView(generics.ListCreateAPIView):
|
||||
"""
|
||||
NonNativeExampleView
|
||||
"""
|
||||
model = NonNativeExampleModel
|
||||
serializer_class = NonNativeExampleSerializer
|
||||
|
||||
example_view = NonNativeExampleView.as_view()
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^example$', example_view),
|
||||
)
|
||||
|
||||
|
||||
class NonNativeFieldTests(TestCase):
|
||||
urls = 'rest_framework.tests.test_non_native_fields'
|
||||
|
||||
def test_non_native_fields(self):
|
||||
data = {
|
||||
'email': 'foo@example.com',
|
||||
'password': '123',
|
||||
'password_confirmation': '123',
|
||||
}
|
||||
serializer = NonNativeExampleSerializer(data=data)
|
||||
self.assertTrue(serializer.is_valid())
|
||||
self.assertTrue(isinstance(serializer.object, NonNativeExampleModel))
|
||||
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 = NonNativeExampleSerializer(data=data)
|
||||
self.assertFalse(serializer.is_valid())
|
||||
self.assertEquals(len(serializer.errors), 1)
|
||||
self.assertEquals(serializer.errors['password_confirmation'],
|
||||
['Password confirmation mismatch'])
|
||||
|
||||
def test_non_native_fields_displayed_in_html_version(self):
|
||||
"""
|
||||
Ensure password_confirmation field is shown in the browsable API form
|
||||
"""
|
||||
response = self.client.get('/example', HTTP_ACCEPT='text/html')
|
||||
self.assertContains(response, 'for="password"')
|
||||
self.assertContains(response, 'for="password_confirmation"')
|
Loading…
Reference in New Issue
Block a user