Added write_only and write_only_fields. Refs #1306

This commit is contained in:
Tom Christie 2014-01-14 11:25:44 +00:00
parent bc6c5df109
commit 85d74fc86a
6 changed files with 117 additions and 13 deletions

View File

@ -28,7 +28,13 @@ Defaults to the name of the field.
### `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`

View File

@ -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.
## 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
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.

View File

@ -40,8 +40,12 @@ You can determine your currently installed version using `pip freeze`:
## 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.
* 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.

View File

@ -114,6 +114,10 @@ def strip_multiple_choice_msg(help_text):
return help_text.replace(multiple_choice_msg, '')
class IgnoreFieldException(Exception):
pass
class Field(object):
read_only = True
creation_counter = 0
@ -246,6 +250,7 @@ class WritableField(Field):
"""
Base for read/write fields.
"""
write_only = False
default_validators = []
default_error_messages = {
'required': _('This field is required.'),
@ -255,7 +260,7 @@ class WritableField(Field):
default = 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,
default=None, blank=None):
@ -269,6 +274,10 @@ class WritableField(Field):
super(WritableField, self).__init__(source=source, label=label, help_text=help_text)
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:
self.required = not(read_only)
else:
@ -318,6 +327,11 @@ class WritableField(Field):
if 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):
"""
Given a dictionary and a field name, updates the dictionary `into`,

View File

@ -344,7 +344,10 @@ 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 IgnoreFieldException:
continue
method = getattr(self, 'transform_%s' % field_name, None)
if callable(method):
value = method(obj, value)
@ -383,6 +386,9 @@ class BaseSerializer(WritableField):
Override default so that the serializer can be used as a nested field
across relationships.
"""
if self.write_only:
raise IgnoreFieldException()
if self.source == '*':
return self.to_native(obj)
@ -615,6 +621,7 @@ class ModelSerializerOptions(SerializerOptions):
super(ModelSerializerOptions, self).__init__(meta)
self.model = getattr(meta, 'model', None)
self.read_only_fields = getattr(meta, 'read_only_fields', ())
self.write_only_fields = getattr(meta, 'write_only_fields', ())
class ModelSerializer(Serializer):
@ -754,17 +761,29 @@ class ModelSerializer(Serializer):
# Add the `read_only` flag to any fields that have bee specified
# in the `read_only_fields` option
for field_name in self.opts.read_only_fields:
assert field_name not in self.base_fields.keys(), \
"field '%s' on serializer '%s' specified in " \
"`read_only_fields`, but also added " \
"as an explicit field. Remove it from `read_only_fields`." % \
(field_name, self.__class__.__name__)
assert field_name in ret, \
"Non-existant field '%s' specified in `read_only_fields` " \
"on serializer '%s'." % \
(field_name, self.__class__.__name__)
assert field_name not in self.base_fields.keys(), (
"field '%s' on serializer '%s' specified in "
"`read_only_fields`, but also added "
"as an explicit field. Remove it from `read_only_fields`." %
(field_name, self.__class__.__name__))
assert field_name in ret, (
"Non-existant field '%s' specified in `read_only_fields` "
"on serializer '%s'." %
(field_name, self.__class__.__name__))
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
def get_pk_field(self, model_field):

View 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'})