aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/api-guide/fields.md8
-rw-r--r--docs/api-guide/serializers.md19
-rw-r--r--docs/topics/release-notes.md6
-rw-r--r--rest_framework/fields.py16
-rw-r--r--rest_framework/serializers.py39
-rw-r--r--rest_framework/tests/test_write_only_fields.py42
6 files changed, 117 insertions, 13 deletions
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index 91866664..c136509b 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -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`
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 04439092..b05acfd5 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -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.
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index ca966d20..cd87c7b2 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -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.
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index f1de447c..258c0f6a 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -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`,
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index b4087e54..9f047b03 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -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):
diff --git a/rest_framework/tests/test_write_only_fields.py b/rest_framework/tests/test_write_only_fields.py
new file mode 100644
index 00000000..aabb18d6
--- /dev/null
+++ b/rest_framework/tests/test_write_only_fields.py
@@ -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'})