mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-03 12:00:12 +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.content = content
|
||||
self.created = created or datetime.datetime.now()
|
||||
|
||||
|
||||
comment = Comment(email='leila@example.com', content='foo bar')
|
||||
|
||||
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.created = attrs.get('created', instance.created)
|
||||
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.
|
||||
|
||||
|
@ -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.)
|
||||
|
||||
## 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 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 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
|
||||
|
||||
|
@ -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)
|
||||
created = serializers.DateTimeField()
|
||||
... # Various other fields
|
||||
|
||||
|
||||
def get_identity(self, data):
|
||||
"""
|
||||
This hook is required for bulk update.
|
||||
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,
|
||||
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.
|
||||
|
||||
## 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:
|
||||
|
||||
|
@ -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')
|
||||
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:
|
||||
|
||||
|
@ -387,12 +387,12 @@ You may wish to specify multiple fields as write-only. Instead of adding each f
|
|||
"""
|
||||
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.set_password(attrs['password'])
|
||||
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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
## 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
|
||||
|
||||
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):
|
||||
# Don't pass the 'fields' arg up to the superclass
|
||||
fields = kwargs.pop('fields', None)
|
||||
|
||||
|
||||
# Instantiate the superclass normally
|
||||
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
if fields:
|
||||
# Drop any fields that are not specified in the `fields` argument.
|
||||
allowed = set(fields)
|
||||
|
@ -540,7 +588,7 @@ This would then allow you to do the following:
|
|||
|
||||
## 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`.
|
||||
|
||||
|
|
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