mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-05 21:10:13 +03:00
Merge f5d223f782
into 2a27674a79
This commit is contained in:
commit
e0b1d68c7b
|
@ -21,7 +21,7 @@ Let's start by creating a simple object we can use for example purposes:
|
||||||
self.email = email
|
self.email = email
|
||||||
self.content = content
|
self.content = content
|
||||||
self.created = created or datetime.datetime.now()
|
self.created = created or datetime.datetime.now()
|
||||||
|
|
||||||
comment = Comment(email='leila@example.com', content='foo bar')
|
comment = Comment(email='leila@example.com', content='foo bar')
|
||||||
|
|
||||||
We'll declare a serializer that we can use to serialize and deserialize `Comment` objects.
|
We'll declare a serializer that we can use to serialize and deserialize `Comment` objects.
|
||||||
|
@ -45,7 +45,7 @@ Declaring a serializer looks very similar to declaring a form:
|
||||||
instance.content = attrs.get('content', instance.content)
|
instance.content = attrs.get('content', instance.content)
|
||||||
instance.created = attrs.get('created', instance.created)
|
instance.created = attrs.get('created', instance.created)
|
||||||
return instance
|
return instance
|
||||||
return Comment(**attrs)
|
return Comment(**attrs)
|
||||||
|
|
||||||
The first part of serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data.
|
The first part of serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data.
|
||||||
|
|
||||||
|
@ -83,8 +83,8 @@ If you need to customize the serialized value of a particular field, you can do
|
||||||
These methods are essentially the reverse of `validate_<fieldname>` (see *Validation* below.)
|
These methods are essentially the reverse of `validate_<fieldname>` (see *Validation* below.)
|
||||||
|
|
||||||
## Deserializing objects
|
## Deserializing objects
|
||||||
|
|
||||||
Deserialization is similar. First we parse a stream into Python native datatypes...
|
Deserialization is similar. First we parse a stream into Python native datatypes...
|
||||||
|
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from rest_framework.parsers import JSONParser
|
from rest_framework.parsers import JSONParser
|
||||||
|
@ -174,7 +174,7 @@ To save the deserialized objects created by a serializer, call the `.save()` met
|
||||||
|
|
||||||
The default behavior of the method is to simply call `.save()` on the deserialized object instance. You can override the default save behaviour by overriding the `.save_object(obj)` method on the serializer class.
|
The default behavior of the method is to simply call `.save()` on the deserialized object instance. You can override the default save behaviour by overriding the `.save_object(obj)` method on the serializer class.
|
||||||
|
|
||||||
The generic views provided by REST framework call the `.save()` method when updating or creating entities.
|
The generic views provided by REST framework call the `.save()` method when updating or creating entities.
|
||||||
|
|
||||||
## Dealing with nested objects
|
## Dealing with nested objects
|
||||||
|
|
||||||
|
@ -288,12 +288,12 @@ By default the serializer class will use the `id` key on the incoming data to de
|
||||||
slug = serializers.CharField(max_length=100)
|
slug = serializers.CharField(max_length=100)
|
||||||
created = serializers.DateTimeField()
|
created = serializers.DateTimeField()
|
||||||
... # Various other fields
|
... # Various other fields
|
||||||
|
|
||||||
def get_identity(self, data):
|
def get_identity(self, data):
|
||||||
"""
|
"""
|
||||||
This hook is required for bulk update.
|
This hook is required for bulk update.
|
||||||
We need to override the default, to use the slug as the identity.
|
We need to override the default, to use the slug as the identity.
|
||||||
|
|
||||||
Note that the data has not yet been validated at this point,
|
Note that the data has not yet been validated at this point,
|
||||||
so we need to deal gracefully with incorrect datatypes.
|
so we need to deal gracefully with incorrect datatypes.
|
||||||
"""
|
"""
|
||||||
|
@ -361,7 +361,7 @@ The `depth` option should be set to an integer value that indicates the depth of
|
||||||
|
|
||||||
If you want to customize the way the serialization is done (e.g. using `allow_add_remove`) you'll need to define the field yourself.
|
If you want to customize the way the serialization is done (e.g. using `allow_add_remove`) you'll need to define the field yourself.
|
||||||
|
|
||||||
## Specifying which fields should be read-only
|
## Specifying which fields should be read-only
|
||||||
|
|
||||||
You may wish to specify multiple fields as read-only. Instead of adding each field explicitly with the `read_only=True` attribute, you may use the `read_only_fields` Meta option, like so:
|
You may wish to specify multiple fields as read-only. Instead of adding each field explicitly with the `read_only=True` attribute, you may use the `read_only_fields` Meta option, like so:
|
||||||
|
|
||||||
|
@ -371,9 +371,9 @@ You may wish to specify multiple fields as read-only. Instead of adding each fi
|
||||||
fields = ('id', 'account_name', 'users', 'created')
|
fields = ('id', 'account_name', 'users', 'created')
|
||||||
read_only_fields = ('account_name',)
|
read_only_fields = ('account_name',)
|
||||||
|
|
||||||
Model fields which have `editable=False` set, and `AutoField` fields will be set to read-only by default, and do not need to be added to the `read_only_fields` option.
|
Model fields which have `editable=False` set, and `AutoField` fields will be set to read-only by default, and do not need to be added to the `read_only_fields` option.
|
||||||
|
|
||||||
## Specifying which fields should be write-only
|
## Specifying which fields should be write-only
|
||||||
|
|
||||||
You may wish to specify multiple fields as write-only. Instead of adding each field explicitly with the `write_only=True` attribute, you may use the `write_only_fields` Meta option, like so:
|
You may wish to specify multiple fields as write-only. Instead of adding each field explicitly with the `write_only=True` attribute, you may use the `write_only_fields` Meta option, like so:
|
||||||
|
|
||||||
|
@ -387,12 +387,12 @@ You may wish to specify multiple fields as write-only. Instead of adding each f
|
||||||
"""
|
"""
|
||||||
Instantiate a new User instance.
|
Instantiate a new User instance.
|
||||||
"""
|
"""
|
||||||
assert instance is None, 'Cannot update users with CreateUserSerializer'
|
assert instance is None, 'Cannot update users with CreateUserSerializer'
|
||||||
user = User(email=attrs['email'], username=attrs['username'])
|
user = User(email=attrs['email'], username=attrs['username'])
|
||||||
user.set_password(attrs['password'])
|
user.set_password(attrs['password'])
|
||||||
return user
|
return user
|
||||||
|
|
||||||
## Specifying fields explicitly
|
## Specifying fields explicitly
|
||||||
|
|
||||||
You can add extra fields to a `ModelSerializer` or override the default fields by declaring fields on the class, just as you would for a `Serializer` class.
|
You can add extra fields to a `ModelSerializer` or override the default fields by declaring fields on the class, just as you would for a `Serializer` class.
|
||||||
|
|
||||||
|
@ -405,6 +405,54 @@ 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.
|
Extra fields can correspond to any property or callable on the model.
|
||||||
|
|
||||||
|
## Specifying non-native validation only fields
|
||||||
|
|
||||||
|
You can add extra non-native `validation only` fields to a `ModelSerializer` provided that you meet the following two conditions:
|
||||||
|
|
||||||
|
**A)** The extra `validation only` fields are removed from the `attrs` parameter prior to invoking the `restore_object()` method on the `parent` serializer class.
|
||||||
|
|
||||||
|
**B)** The extra `validation only` fields are removed from the `fields` attribute prior to invoking the `to_native()` method on the `parent` serializer class when `obj` is None.
|
||||||
|
|
||||||
|
# Example of overriding restore_object() and to_native() attributes
|
||||||
|
|
||||||
|
class CreateUserSerializer(serializers.ModelSerializer):
|
||||||
|
password_confirmation = serializers.CharField()
|
||||||
|
accept_our_terms_and_conditions = serializers.BooleanField()
|
||||||
|
|
||||||
|
custom_messages = {
|
||||||
|
'password_mismatch': 'The two password fields did not match.',
|
||||||
|
'terms_conditions': 'You must accept our terms and conditions.',
|
||||||
|
}
|
||||||
|
|
||||||
|
def validate_password_confirmation(self, attrs, source):
|
||||||
|
password_confirmation = attrs[source]
|
||||||
|
password = attrs['password']
|
||||||
|
if password_confirmation != password:
|
||||||
|
raise serializers.ValidationError(self.custom_messages['password_mismatch'])
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def validate_accept_our_terms_and_conditions(self, attrs, source):
|
||||||
|
accept_our_terms_and_conditions = attrs[source]
|
||||||
|
if not accept_our_terms_and_conditions:
|
||||||
|
raise serializers.ValidationError(self.custom_messages['terms_conditions'])
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ('username', 'email', 'password', 'password_confirmation', 'accept_our_terms_and_conditions',)
|
||||||
|
write_only_fields = ('password',)
|
||||||
|
|
||||||
|
def restore_object(self, attrs, instance=None):
|
||||||
|
for attr in ('password_confirmation', 'accept_our_terms_and_conditions'):
|
||||||
|
attrs.pop(attr)
|
||||||
|
return super(CreateUserSerializer, self).restore_object(attrs, instance)
|
||||||
|
|
||||||
|
def to_native(self, obj):
|
||||||
|
if obj is not None:
|
||||||
|
for field in ('password_confirmation', 'accept_our_terms_and_conditions'):
|
||||||
|
self.fields.pop(field)
|
||||||
|
return super(CreateUserSerializer, self).to_native(obj)
|
||||||
|
|
||||||
## Relational fields
|
## 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.
|
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.
|
||||||
|
@ -514,10 +562,10 @@ For example, if you wanted to be able to set which fields should be used by a se
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Don't pass the 'fields' arg up to the superclass
|
# Don't pass the 'fields' arg up to the superclass
|
||||||
fields = kwargs.pop('fields', None)
|
fields = kwargs.pop('fields', None)
|
||||||
|
|
||||||
# Instantiate the superclass normally
|
# Instantiate the superclass normally
|
||||||
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
|
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
if fields:
|
if fields:
|
||||||
# Drop any fields that are not specified in the `fields` argument.
|
# Drop any fields that are not specified in the `fields` argument.
|
||||||
allowed = set(fields)
|
allowed = set(fields)
|
||||||
|
@ -540,7 +588,7 @@ This would then allow you to do the following:
|
||||||
|
|
||||||
## Customising the default fields
|
## Customising the default fields
|
||||||
|
|
||||||
The `field_mapping` attribute is a dictionary that maps model classes to serializer classes. Overriding the attribute will let you set a different set of default serializer classes.
|
The `field_mapping` attribute is a dictionary that maps model classes to serializer classes. Overriding the attribute will let you set a different set of default serializer classes.
|
||||||
|
|
||||||
For more advanced customization than simply changing the default serializer class you can override various `get_<field_type>_field` methods. Doing so will allow you to customize the arguments that each serializer field is initialized with. Each of these methods may either return a field or serializer instance, or `None`.
|
For more advanced customization than simply changing the default serializer class you can override various `get_<field_type>_field` methods. Doing so will allow you to customize the arguments that each serializer field is initialized with. Each of these methods may either return a field or serializer instance, or `None`.
|
||||||
|
|
||||||
|
|
143
rest_framework/tests/test_validation_only_fields.py
Normal file
143
rest_framework/tests/test_validation_only_fields.py
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework import generics
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.compat import patterns
|
||||||
|
from rest_framework.compat import url
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationOnlyFieldsExampleModel(models.Model):
|
||||||
|
email = models.EmailField(max_length=100)
|
||||||
|
password = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationOnlyFieldsExampleSerializer(serializers.ModelSerializer):
|
||||||
|
password_confirmation = serializers.CharField()
|
||||||
|
accept_our_terms_and_conditions = serializers.BooleanField()
|
||||||
|
|
||||||
|
custom_messages = {
|
||||||
|
'password_mismatch': 'Password confirmation failed.',
|
||||||
|
'terms_condition': 'You must accept our terms and conditions.',
|
||||||
|
}
|
||||||
|
|
||||||
|
def validate_password_confirmation(self, attrs, source):
|
||||||
|
password_confirmation = attrs[source]
|
||||||
|
password = attrs['password']
|
||||||
|
if password_confirmation != password:
|
||||||
|
raise serializers.ValidationError(self.custom_messages['password_mismatch'])
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def validate_accept_our_terms_and_conditions(self, attrs, source):
|
||||||
|
accept_our_terms_and_conditions = attrs[source]
|
||||||
|
if not accept_our_terms_and_conditions:
|
||||||
|
raise serializers.ValidationError(self.custom_messages['terms_condition'])
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ValidationOnlyFieldsExampleModel
|
||||||
|
fields = ('email', 'password', 'password_confirmation', 'accept_our_terms_and_conditions',)
|
||||||
|
write_only_fields = ('password',)
|
||||||
|
|
||||||
|
def restore_object(self, attrs, instance=None):
|
||||||
|
for attr in ('password_confirmation', 'accept_our_terms_and_conditions'):
|
||||||
|
attrs.pop(attr)
|
||||||
|
return super(ValidationOnlyFieldsExampleSerializer, self).restore_object(attrs, instance)
|
||||||
|
|
||||||
|
def to_native(self, obj):
|
||||||
|
if obj is not None:
|
||||||
|
for field in ('password_confirmation', 'accept_our_terms_and_conditions'):
|
||||||
|
self.fields.pop(field)
|
||||||
|
return super(ValidationOnlyFieldsExampleSerializer, self).to_native(obj)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationOnlyFieldsExampleView(generics.ListCreateAPIView):
|
||||||
|
"""
|
||||||
|
ValidationOnlyFieldsExampleView
|
||||||
|
"""
|
||||||
|
model = ValidationOnlyFieldsExampleModel
|
||||||
|
serializer_class = ValidationOnlyFieldsExampleSerializer
|
||||||
|
|
||||||
|
validation_only_fields_test_view = ValidationOnlyFieldsExampleView.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(
|
||||||
|
r'^validation/only/fields/test$',
|
||||||
|
validation_only_fields_test_view,
|
||||||
|
name='validation_only_fields_test'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationOnlyFieldsTests(TestCase):
|
||||||
|
urls = 'rest_framework.tests.test_validation_only_fields'
|
||||||
|
|
||||||
|
def test_validation_fields_only_not_included_in_data(self):
|
||||||
|
data = {
|
||||||
|
'email': 'foo@example.com',
|
||||||
|
'password': '1234',
|
||||||
|
'password_confirmation': '1234',
|
||||||
|
'accept_our_terms_and_conditions': True,
|
||||||
|
}
|
||||||
|
serializer = ValidationOnlyFieldsExampleSerializer(data=data)
|
||||||
|
self.assertTrue(serializer.is_valid())
|
||||||
|
self.assertTrue(isinstance(serializer.object, ValidationOnlyFieldsExampleModel))
|
||||||
|
self.assertEquals(serializer.object.email, data['email'])
|
||||||
|
self.assertEquals(serializer.object.password, data['password'])
|
||||||
|
self.assertFalse(hasattr(serializer.object, 'password_confirmation'))
|
||||||
|
self.assertEquals(serializer.data.get('email'), data['email'])
|
||||||
|
self.assertEquals(serializer.data.get('password'), None)
|
||||||
|
self.assertEquals(serializer.data.get('password_confirmation'), None)
|
||||||
|
|
||||||
|
def test_validation_only_raises_proper_validation_error(self):
|
||||||
|
data = {
|
||||||
|
'email': 'foo@example.com',
|
||||||
|
'password': '1234',
|
||||||
|
'password_confirmation': 'ABCD', # wrong password
|
||||||
|
'accept_our_terms_and_conditions': True,
|
||||||
|
}
|
||||||
|
serializer = ValidationOnlyFieldsExampleSerializer(data=data)
|
||||||
|
self.assertFalse(serializer.is_valid())
|
||||||
|
self.assertEquals(len(serializer.errors), 1)
|
||||||
|
self.assertEquals(serializer.errors['password_confirmation'][0],
|
||||||
|
ValidationOnlyFieldsExampleSerializer.custom_messages['password_mismatch'])
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'email': 'foo@example.com',
|
||||||
|
'password': '1234',
|
||||||
|
'password_confirmation': 'ABCD', # wrong password
|
||||||
|
'accept_our_terms_and_conditions': False,
|
||||||
|
}
|
||||||
|
serializer = ValidationOnlyFieldsExampleSerializer(data=data)
|
||||||
|
self.assertFalse(serializer.is_valid())
|
||||||
|
self.assertEquals(len(serializer.errors), 2)
|
||||||
|
self.assertEquals(serializer.errors['password_confirmation'][0],
|
||||||
|
ValidationOnlyFieldsExampleSerializer.custom_messages['password_mismatch'])
|
||||||
|
self.assertEquals(serializer.errors['accept_our_terms_and_conditions'][0],
|
||||||
|
ValidationOnlyFieldsExampleSerializer.custom_messages['terms_condition'])
|
||||||
|
|
||||||
|
def test_validation_only_fields_included_in_browser_api_forms(self):
|
||||||
|
url = reverse('validation_only_fields_test')
|
||||||
|
resp = self.client.get(url, HTTP_ACCEPT='text/html')
|
||||||
|
self.assertContains(resp, 'for="email"')
|
||||||
|
self.assertContains(resp, 'for="password"')
|
||||||
|
self.assertContains(resp, 'for="password_confirmation"')
|
||||||
|
self.assertContains(resp, 'for="accept_our_terms_and_conditions"')
|
||||||
|
|
||||||
|
def test_validation_only_fields_not_included_in_reponse(self):
|
||||||
|
url = reverse('validation_only_fields_test')
|
||||||
|
data = {
|
||||||
|
'email': 'foo@example.com',
|
||||||
|
'password': '1234',
|
||||||
|
'password_confirmation': '1234',
|
||||||
|
'accept_our_terms_and_conditions': True,
|
||||||
|
}
|
||||||
|
resp = self.client.post(url, data=data)
|
||||||
|
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
|
||||||
|
self.assertEquals(resp.data.get('email'), data['email'])
|
||||||
|
self.assertEquals(resp.data.get('password'), None)
|
||||||
|
self.assertEquals(resp.data.get('password_confirmation'), None)
|
||||||
|
self.assertEquals(resp.data.get('accept_our_terms_and_conditions'), None)
|
Loading…
Reference in New Issue
Block a user