From e224061189a6a5ea2c063f3820239eed6c3a88fb Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Fri, 9 Nov 2012 17:01:20 +0000
Subject: [PATCH] Support for `read_only_fields` on `ModelSerializer` classes

---
 docs/api-guide/serializers.md      |  9 +++++++++
 docs/topics/release-notes.md       |  4 ++++
 rest_framework/serializers.py      |  7 +++++++
 rest_framework/tests/serializer.py | 19 +++++++++++++++++--
 4 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 0cdae1ce3..a95891449 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -248,6 +248,15 @@ The default `ModelSerializer` uses primary keys for relationships, but you can a
 
 The `depth` option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.
 
+## Specifying which fields should be read-only 
+
+You may wish to specify multiple fields as read-only. Instead of adding each field explicitely with the `read_only=True` attribute, you may use the `read_only_fields` Meta option, like so:
+
+    class AccountSerializer(serializers.ModelSerializer):
+        class Meta:
+            model = Account
+            read_only_fields = ('created', 'modified')
+
 ## Customising the default fields
 
 You can create customized subclasses of `ModelSerializer` that use a different set of default fields for the representation, by overriding various `get_<field_type>_field` methods.
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 670332e60..35e8a8b35 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -4,6 +4,10 @@
 >
 > &mdash; Eric S. Raymond, [The Cathedral and the Bazaar][cite].
 
+## Master
+
+* Support for `read_only_fields` on `ModelSerializer` classes.
+
 ## 2.1.2
 
 **Date**: 9th Nov 2012
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 95145d580..329b38f2d 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -321,6 +321,7 @@ class ModelSerializerOptions(SerializerOptions):
     def __init__(self, meta):
         super(ModelSerializerOptions, self).__init__(meta)
         self.model = getattr(meta, 'model', None)
+        self.read_only_fields = getattr(meta, 'read_only_fields', ())
 
 
 class ModelSerializer(Serializer):
@@ -369,6 +370,12 @@ class ModelSerializer(Serializer):
                 field.initialize(parent=self, field_name=model_field.name)
                 ret[model_field.name] = field
 
+        for field_name in self.opts.read_only_fields:
+            assert field_name in ret, \
+                "read_only_fields on '%s' included invalid item '%s'" % \
+                (self.__class__.__name__, field_name)
+            ret[field_name].read_only = True
+
         return ret
 
     def get_pk_field(self, model_field):
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 8d1de4298..059593a90 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -51,6 +51,7 @@ class PersonSerializer(serializers.ModelSerializer):
     class Meta:
         model = Person
         fields = ('name', 'age', 'info')
+        read_only_fields = ('age',)
 
 
 class BasicTests(TestCase):
@@ -107,7 +108,8 @@ class BasicTests(TestCase):
         self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!')
 
     def test_model_fields_as_expected(self):
-        """ Make sure that the fields returned are the same as defined
+        """
+        Make sure that the fields returned are the same as defined
         in the Meta data
         """
         serializer = PersonSerializer(self.person)
@@ -115,12 +117,25 @@ class BasicTests(TestCase):
                           set(['name', 'age', 'info']))
 
     def test_field_with_dictionary(self):
-        """ Make sure that dictionaries from fields are left intact
+        """
+        Make sure that dictionaries from fields are left intact
         """
         serializer = PersonSerializer(self.person)
         expected = self.person_data
         self.assertEquals(serializer.data['info'], expected)
 
+    def test_read_only_fields(self):
+        """
+        Attempting to update fields set as read_only should have no effect.
+        """
+
+        serializer = PersonSerializer(self.person, data={'name': 'dwight', 'age': 99})
+        self.assertEquals(serializer.is_valid(), True)
+        instance = serializer.save()
+        self.assertEquals(serializer.errors, {})
+        # Assert age is unchanged (35)
+        self.assertEquals(instance.age, self.person_data['age'])
+
 
 class ValidationTests(TestCase):
     def setUp(self):