mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-06-30 18:33:21 +03:00
Added write_only and write_only_fields. Refs #1306
This commit is contained in:
parent
bc6c5df109
commit
85d74fc86a
|
@ -28,7 +28,13 @@ Defaults to the name of the field.
|
||||||
|
|
||||||
### `read_only`
|
### `read_only`
|
||||||
|
|
||||||
Set this to `True` to ensure that the field is used when serializing a representation, but is not used when updating an instance during deserialization.
|
Set this to `True` to ensure that the field is used when serializing a representation, but is not used when creating or updating an instance during deserialization.
|
||||||
|
|
||||||
|
Defaults to `False`
|
||||||
|
|
||||||
|
### `write_only`
|
||||||
|
|
||||||
|
Set this to `True` to ensure that the field may be used when updating or creating an instance, but is not included when serializing the representation.
|
||||||
|
|
||||||
Defaults to `False`
|
Defaults to `False`
|
||||||
|
|
||||||
|
|
|
@ -373,6 +373,25 @@ You may wish to specify multiple fields as read-only. Instead of adding each fi
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
class CreateUserSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ('email', 'username', 'password')
|
||||||
|
write_only_fields = ('password',) # Note: Password field is write-only
|
||||||
|
|
||||||
|
def restore_object(self, attrs, instance=None):
|
||||||
|
"""
|
||||||
|
Instantiate a new User instance.
|
||||||
|
"""
|
||||||
|
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.
|
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.
|
||||||
|
|
|
@ -40,8 +40,12 @@ You can determine your currently installed version using `pip freeze`:
|
||||||
|
|
||||||
## 2.3.x series
|
## 2.3.x series
|
||||||
|
|
||||||
### Master
|
### 2.3.11
|
||||||
|
|
||||||
|
**Date**: 14th January 2014
|
||||||
|
|
||||||
|
* Added `write_only` serializer field argument.
|
||||||
|
* Added `write_only_fields` option to `ModelSerializer` classes.
|
||||||
* JSON renderer now deals with objects that implement a dict-like interface.
|
* JSON renderer now deals with objects that implement a dict-like interface.
|
||||||
* Fix compatiblity with newer versions of `django-oauth-plus`.
|
* Fix compatiblity with newer versions of `django-oauth-plus`.
|
||||||
* Bugfix: Refine behavior that calls model manager `all()` across nested serializer relationships, preventing erronous behavior with some non-ORM objects, and preventing unneccessary queryset re-evaluations.
|
* Bugfix: Refine behavior that calls model manager `all()` across nested serializer relationships, preventing erronous behavior with some non-ORM objects, and preventing unneccessary queryset re-evaluations.
|
||||||
|
|
|
@ -114,6 +114,10 @@ def strip_multiple_choice_msg(help_text):
|
||||||
return help_text.replace(multiple_choice_msg, '')
|
return help_text.replace(multiple_choice_msg, '')
|
||||||
|
|
||||||
|
|
||||||
|
class IgnoreFieldException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Field(object):
|
class Field(object):
|
||||||
read_only = True
|
read_only = True
|
||||||
creation_counter = 0
|
creation_counter = 0
|
||||||
|
@ -246,6 +250,7 @@ class WritableField(Field):
|
||||||
"""
|
"""
|
||||||
Base for read/write fields.
|
Base for read/write fields.
|
||||||
"""
|
"""
|
||||||
|
write_only = False
|
||||||
default_validators = []
|
default_validators = []
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'required': _('This field is required.'),
|
'required': _('This field is required.'),
|
||||||
|
@ -255,7 +260,7 @@ class WritableField(Field):
|
||||||
default = None
|
default = None
|
||||||
|
|
||||||
def __init__(self, source=None, label=None, help_text=None,
|
def __init__(self, source=None, label=None, help_text=None,
|
||||||
read_only=False, required=None,
|
read_only=False, write_only=False, required=None,
|
||||||
validators=[], error_messages=None, widget=None,
|
validators=[], error_messages=None, widget=None,
|
||||||
default=None, blank=None):
|
default=None, blank=None):
|
||||||
|
|
||||||
|
@ -269,6 +274,10 @@ class WritableField(Field):
|
||||||
super(WritableField, self).__init__(source=source, label=label, help_text=help_text)
|
super(WritableField, self).__init__(source=source, label=label, help_text=help_text)
|
||||||
|
|
||||||
self.read_only = read_only
|
self.read_only = read_only
|
||||||
|
self.write_only = write_only
|
||||||
|
|
||||||
|
assert not (read_only and write_only), "Cannot set read_only=True and write_only=True"
|
||||||
|
|
||||||
if required is None:
|
if required is None:
|
||||||
self.required = not(read_only)
|
self.required = not(read_only)
|
||||||
else:
|
else:
|
||||||
|
@ -318,6 +327,11 @@ class WritableField(Field):
|
||||||
if errors:
|
if errors:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
|
|
||||||
|
def field_to_native(self, obj, field_name):
|
||||||
|
if self.write_only:
|
||||||
|
raise IgnoreFieldException()
|
||||||
|
return super(WritableField, self).field_to_native(obj, field_name)
|
||||||
|
|
||||||
def field_from_native(self, data, files, field_name, into):
|
def field_from_native(self, data, files, field_name, into):
|
||||||
"""
|
"""
|
||||||
Given a dictionary and a field name, updates the dictionary `into`,
|
Given a dictionary and a field name, updates the dictionary `into`,
|
||||||
|
|
|
@ -344,7 +344,10 @@ class BaseSerializer(WritableField):
|
||||||
continue
|
continue
|
||||||
field.initialize(parent=self, field_name=field_name)
|
field.initialize(parent=self, field_name=field_name)
|
||||||
key = self.get_field_key(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 IgnoreFieldException:
|
||||||
|
continue
|
||||||
method = getattr(self, 'transform_%s' % field_name, None)
|
method = getattr(self, 'transform_%s' % field_name, None)
|
||||||
if callable(method):
|
if callable(method):
|
||||||
value = method(obj, value)
|
value = method(obj, value)
|
||||||
|
@ -383,6 +386,9 @@ class BaseSerializer(WritableField):
|
||||||
Override default so that the serializer can be used as a nested field
|
Override default so that the serializer can be used as a nested field
|
||||||
across relationships.
|
across relationships.
|
||||||
"""
|
"""
|
||||||
|
if self.write_only:
|
||||||
|
raise IgnoreFieldException()
|
||||||
|
|
||||||
if self.source == '*':
|
if self.source == '*':
|
||||||
return self.to_native(obj)
|
return self.to_native(obj)
|
||||||
|
|
||||||
|
@ -615,6 +621,7 @@ class ModelSerializerOptions(SerializerOptions):
|
||||||
super(ModelSerializerOptions, self).__init__(meta)
|
super(ModelSerializerOptions, self).__init__(meta)
|
||||||
self.model = getattr(meta, 'model', None)
|
self.model = getattr(meta, 'model', None)
|
||||||
self.read_only_fields = getattr(meta, 'read_only_fields', ())
|
self.read_only_fields = getattr(meta, 'read_only_fields', ())
|
||||||
|
self.write_only_fields = getattr(meta, 'write_only_fields', ())
|
||||||
|
|
||||||
|
|
||||||
class ModelSerializer(Serializer):
|
class ModelSerializer(Serializer):
|
||||||
|
@ -754,17 +761,29 @@ class ModelSerializer(Serializer):
|
||||||
# Add the `read_only` flag to any fields that have bee specified
|
# Add the `read_only` flag to any fields that have bee specified
|
||||||
# in the `read_only_fields` option
|
# in the `read_only_fields` option
|
||||||
for field_name in self.opts.read_only_fields:
|
for field_name in self.opts.read_only_fields:
|
||||||
assert field_name not in self.base_fields.keys(), \
|
assert field_name not in self.base_fields.keys(), (
|
||||||
"field '%s' on serializer '%s' specified in " \
|
"field '%s' on serializer '%s' specified in "
|
||||||
"`read_only_fields`, but also added " \
|
"`read_only_fields`, but also added "
|
||||||
"as an explicit field. Remove it from `read_only_fields`." % \
|
"as an explicit field. Remove it from `read_only_fields`." %
|
||||||
(field_name, self.__class__.__name__)
|
(field_name, self.__class__.__name__))
|
||||||
assert field_name in ret, \
|
assert field_name in ret, (
|
||||||
"Non-existant field '%s' specified in `read_only_fields` " \
|
"Non-existant field '%s' specified in `read_only_fields` "
|
||||||
"on serializer '%s'." % \
|
"on serializer '%s'." %
|
||||||
(field_name, self.__class__.__name__)
|
(field_name, self.__class__.__name__))
|
||||||
ret[field_name].read_only = True
|
ret[field_name].read_only = True
|
||||||
|
|
||||||
|
for field_name in self.opts.write_only_fields:
|
||||||
|
assert field_name not in self.base_fields.keys(), (
|
||||||
|
"field '%s' on serializer '%s' specified in "
|
||||||
|
"`write_only_fields`, but also added "
|
||||||
|
"as an explicit field. Remove it from `write_only_fields`." %
|
||||||
|
(field_name, self.__class__.__name__))
|
||||||
|
assert field_name in ret, (
|
||||||
|
"Non-existant field '%s' specified in `write_only_fields` "
|
||||||
|
"on serializer '%s'." %
|
||||||
|
(field_name, self.__class__.__name__))
|
||||||
|
ret[field_name].write_only = True
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_pk_field(self, model_field):
|
def get_pk_field(self, model_field):
|
||||||
|
|
42
rest_framework/tests/test_write_only_fields.py
Normal file
42
rest_framework/tests/test_write_only_fields.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
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 WriteOnlyFieldTests(TestCase):
|
||||||
|
def test_write_only_fields(self):
|
||||||
|
class ExampleSerializer(serializers.Serializer):
|
||||||
|
email = serializers.EmailField()
|
||||||
|
password = serializers.CharField(write_only=True)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'email': 'foo@example.com',
|
||||||
|
'password': '123'
|
||||||
|
}
|
||||||
|
serializer = ExampleSerializer(data=data)
|
||||||
|
self.assertTrue(serializer.is_valid())
|
||||||
|
self.assertEquals(serializer.object, data)
|
||||||
|
self.assertEquals(serializer.data, {'email': 'foo@example.com'})
|
||||||
|
|
||||||
|
def test_write_only_fields_meta(self):
|
||||||
|
class ExampleSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ExampleModel
|
||||||
|
fields = ('email', 'password')
|
||||||
|
write_only_fields = ('password',)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'email': 'foo@example.com',
|
||||||
|
'password': '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'})
|
Loading…
Reference in New Issue
Block a user