diff options
| author | Tom Christie | 2014-01-14 11:25:44 +0000 | 
|---|---|---|
| committer | Tom Christie | 2014-01-14 11:25:44 +0000 | 
| commit | 85d74fc86a934309359a437dd487193013055977 (patch) | |
| tree | 6c7ab1a100f61c7102d728ac6cd7dd788ed739fd | |
| parent | bc6c5df109a35bf76be662a47d9c88a2a3b82351 (diff) | |
| download | django-rest-framework-85d74fc86a934309359a437dd487193013055977.tar.bz2 | |
Added write_only and write_only_fields.  Refs #1306
| -rw-r--r-- | docs/api-guide/fields.md | 8 | ||||
| -rw-r--r-- | docs/api-guide/serializers.md | 19 | ||||
| -rw-r--r-- | docs/topics/release-notes.md | 6 | ||||
| -rw-r--r-- | rest_framework/fields.py | 16 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 39 | ||||
| -rw-r--r-- | rest_framework/tests/test_write_only_fields.py | 42 | 
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'}) | 
