From ca5b99486d15e7392754178ab0948de2a60763a3 Mon Sep 17 00:00:00 2001
From: Marko Tibold <marko@tibold.nl>
Date: Thu, 22 Nov 2012 22:36:37 +0100
Subject: [PATCH 01/56] Added _post_clean() behaviour by adding a
 .perform_model_validation() method. Fixed some tests that were failing due to
 extra strict validation.

---
 rest_framework/serializers.py      | 57 +++++++++++++++++++++++++++---
 rest_framework/tests/models.py     |  4 +--
 rest_framework/tests/serializer.py |  2 +-
 3 files changed, 56 insertions(+), 7 deletions(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 53dcec163..6d5b4cb53 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -428,10 +428,6 @@ class ModelSerializer(Serializer):
             kwargs['choices'] = model_field.flatchoices
             return ChoiceField(**kwargs)
 
-        max_length = getattr(model_field, 'max_length', None)
-        if max_length:
-            kwargs['max_length'] = max_length
-
         field_mapping = {
             models.FloatField: FloatField,
             models.IntegerField: IntegerField,
@@ -455,6 +451,59 @@ class ModelSerializer(Serializer):
         except KeyError:
             return ModelField(model_field=model_field, **kwargs)
 
+    def from_native(self, data, files):
+        restored_object = super(ModelSerializer, self).from_native(data, files)
+
+        if restored_object is None:
+            return
+
+        self.perform_model_validation(restored_object)
+        return restored_object
+
+    def perform_model_validation(self, restored_object):
+
+  #      if hasattr(restored_object, '__iter__'):  # Iterables are not model instances
+  #          return restored_object
+        #self._errors[field_name] = list(err.messages)
+
+#        opts = self._meta
+        # Update the model instance with self.cleaned_data.
+#        instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
+
+#        exclude = self._get_validation_exclusions()
+
+        # Foreign Keys being used to represent inline relationships
+        # are excluded from basic field value validation. This is for two
+        # reasons: firstly, the value may not be supplied (#12507; the
+        # case of providing new values to the admin); secondly the
+        # object being referred to may not yet fully exist (#12749).
+        # However, these fields *must* be included in uniqueness checks,
+        # so this can't be part of _get_validation_exclusions().
+#        for f_name, field in self.fields.items():
+#            if isinstance(field, InlineForeignKeyField):
+#                exclude.append(f_name)
+
+        # Clean the model instance's fields.
+        try:
+            restored_object.clean_fields()  # exclude=exclude)
+        except ValidationError as e:
+            for field_name, error_messages in e.message_dict.items():
+                self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages)
+
+        # Call the model instance's clean method.
+        try:
+            restored_object.clean()
+        except ValidationError as e:
+            for field_name, error_messages in e.message_dict.items():
+                self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages)
+
+        # Validate uniqueness if needed.
+        # exclude = self._get_validation_exclusions()
+#        try:
+#            restored_object.validate_unique()  # exclude=exclude)
+#        except ValidationError as e:
+#            model_errors.append(e.message_dict)
+
     def restore_object(self, attrs, instance=None):
         """
         Restore the model instance.
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
index c35861c6c..9a59e8411 100644
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -61,7 +61,7 @@ class BasicModel(RESTFrameworkModel):
 
 class SlugBasedModel(RESTFrameworkModel):
     text = models.CharField(max_length=100)
-    slug = models.SlugField(max_length=32)
+    slug = models.SlugField(max_length=32, blank=True)
 
 
 class DefaultValueModel(RESTFrameworkModel):
@@ -159,7 +159,7 @@ class Person(RESTFrameworkModel):
 
 # Model for issue #324
 class BlankFieldModel(RESTFrameworkModel):
-    title = models.CharField(max_length=100, blank=True)
+    title = models.CharField(max_length=100, blank=True, null=True)
 
 
 # Model for issue #380
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 61a05da18..5751e8940 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -169,7 +169,7 @@ class ValidationTests(TestCase):
             'content': 'x' * 1001,
             'created': datetime.datetime(2012, 1, 1)
         }
-        self.actionitem = ActionItem('Some to do item',
+        self.actionitem = ActionItem(title='Some to do item',
         )
 
     def test_create(self):

From bd8c742df2cc72896fa975196fdf56961e89cd94 Mon Sep 17 00:00:00 2001
From: Marko Tibold <marko@tibold.nl>
Date: Thu, 22 Nov 2012 23:39:16 +0100
Subject: [PATCH 02/56] Cleanup.

---
 rest_framework/serializers.py | 40 ++---------------------------------
 1 file changed, 2 insertions(+), 38 deletions(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 6d5b4cb53..bb15faa8a 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -461,49 +461,13 @@ class ModelSerializer(Serializer):
         return restored_object
 
     def perform_model_validation(self, restored_object):
-
-  #      if hasattr(restored_object, '__iter__'):  # Iterables are not model instances
-  #          return restored_object
-        #self._errors[field_name] = list(err.messages)
-
-#        opts = self._meta
-        # Update the model instance with self.cleaned_data.
-#        instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
-
-#        exclude = self._get_validation_exclusions()
-
-        # Foreign Keys being used to represent inline relationships
-        # are excluded from basic field value validation. This is for two
-        # reasons: firstly, the value may not be supplied (#12507; the
-        # case of providing new values to the admin); secondly the
-        # object being referred to may not yet fully exist (#12749).
-        # However, these fields *must* be included in uniqueness checks,
-        # so this can't be part of _get_validation_exclusions().
-#        for f_name, field in self.fields.items():
-#            if isinstance(field, InlineForeignKeyField):
-#                exclude.append(f_name)
-
-        # Clean the model instance's fields.
         try:
-            restored_object.clean_fields()  # exclude=exclude)
+            # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique()
+            restored_object.full_clean(exclude=list(self.opts.exclude))
         except ValidationError as e:
             for field_name, error_messages in e.message_dict.items():
                 self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages)
 
-        # Call the model instance's clean method.
-        try:
-            restored_object.clean()
-        except ValidationError as e:
-            for field_name, error_messages in e.message_dict.items():
-                self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages)
-
-        # Validate uniqueness if needed.
-        # exclude = self._get_validation_exclusions()
-#        try:
-#            restored_object.validate_unique()  # exclude=exclude)
-#        except ValidationError as e:
-#            model_errors.append(e.message_dict)
-
     def restore_object(self, attrs, instance=None):
         """
         Restore the model instance.

From 3f47f6cea9d178a57855e3b90208601b7e28a80f Mon Sep 17 00:00:00 2001
From: Marko Tibold <marko@tibold.nl>
Date: Thu, 22 Nov 2012 23:50:42 +0100
Subject: [PATCH 03/56] Added a validate_unique test.

---
 rest_framework/tests/serializer.py | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 5751e8940..0baf0e89d 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -1,7 +1,7 @@
 import datetime
 from django.test import TestCase
 from rest_framework import serializers
-from rest_framework.tests.models import (ActionItem, Anchor, BasicModel,
+from rest_framework.tests.models import (Album, ActionItem, Anchor, BasicModel,
     BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel,
     ManyToManyModel, Person, ReadOnlyManyToManyModel)
 
@@ -48,7 +48,7 @@ class BookSerializer(serializers.ModelSerializer):
 
 
 class ActionItemSerializer(serializers.ModelSerializer):
-    
+
     class Meta:
         model = ActionItem
 
@@ -62,6 +62,12 @@ class PersonSerializer(serializers.ModelSerializer):
         read_only_fields = ('age',)
 
 
+class AlbumsSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = Album
+
+
 class BasicTests(TestCase):
     def setUp(self):
         self.comment = Comment(
@@ -276,6 +282,16 @@ class ValidationTests(TestCase):
         self.assertEquals(serializer.is_valid(), False)
         self.assertEquals(serializer.errors, {'info': [u'Ensure this value has at most 12 characters (it has 13).']})
 
+    def test_validate_unique(self):
+        """
+        Just check if serializers.ModelSerializer.perform_model_validation() handles unique checks via .full_clean()
+        """
+        serializer = AlbumsSerializer(data={'title': 'a'})
+        serializer.is_valid()
+        serializer.save()
+        second_serializer = AlbumsSerializer(data={'title': 'a'})
+        self.assertFalse(second_serializer.is_valid())
+
 
 class RegexValidationTest(TestCase):
     def test_create_failed(self):

From e7666014a85d65e204b40e1f54911e654f974932 Mon Sep 17 00:00:00 2001
From: Marko Tibold <marko@tibold.nl>
Date: Mon, 26 Nov 2012 23:39:49 +0100
Subject: [PATCH 04/56] Added an assertion to the tests that checks the
 '.errors' value for the unique-test

---
 rest_framework/tests/serializer.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 0baf0e89d..bdf72a91b 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -291,6 +291,7 @@ class ValidationTests(TestCase):
         serializer.save()
         second_serializer = AlbumsSerializer(data={'title': 'a'})
         self.assertFalse(second_serializer.is_valid())
+        self.assertEqual(second_serializer.errors,  {'title': [u'Album with this Title already exists.']})
 
 
 class RegexValidationTest(TestCase):

From f104f7434052bedf6dd970806ff54b73489b339b Mon Sep 17 00:00:00 2001
From: Marko Tibold <marko@tibold.nl>
Date: Tue, 27 Nov 2012 23:21:12 +0100
Subject: [PATCH 05/56] Moved model validation from .perform_validation() to
 .validate()

---
 rest_framework/serializers.py | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index bb15faa8a..5046c7b1e 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -221,10 +221,17 @@ class BaseSerializer(Field):
             except ValidationError as err:
                 self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages)
 
-        try:
-            attrs = self.validate(attrs)
-        except ValidationError as err:
-            self._errors['non_field_errors'] = err.messages
+        # We don't run .validate() because field-validation failed and thus `attrs` may not be complete.
+        # which in turn can cause inconsistent validation errors.
+        if not self._errors:
+            try:
+                attrs = self.validate(attrs)
+            except ValidationError as err:
+                if hasattr(err, 'message_dict'):
+                    for field_name, error_messages in err.message_dict.items():
+                        self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages)
+                elif hasattr(err, 'messages'):
+                    self._errors['non_field_errors'] = err.messages
 
         return attrs
 
@@ -451,22 +458,15 @@ class ModelSerializer(Serializer):
         except KeyError:
             return ModelField(model_field=model_field, **kwargs)
 
-    def from_native(self, data, files):
-        restored_object = super(ModelSerializer, self).from_native(data, files)
-
-        if restored_object is None:
-            return
-
+    def validate(self, attrs):
+        copied_attrs = copy.deepcopy(attrs)
+        restored_object = self.restore_object(copied_attrs, instance=getattr(self, 'object', None))
         self.perform_model_validation(restored_object)
-        return restored_object
+        return attrs
 
     def perform_model_validation(self, restored_object):
-        try:
-            # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique()
-            restored_object.full_clean(exclude=list(self.opts.exclude))
-        except ValidationError as e:
-            for field_name, error_messages in e.message_dict.items():
-                self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages)
+        # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique()
+        restored_object.full_clean(exclude=list(self.opts.exclude))
 
     def restore_object(self, attrs, instance=None):
         """

From 899f96ae9186e68009dba5d54246232d34457354 Mon Sep 17 00:00:00 2001
From: Marko Tibold <marko@tibold.nl>
Date: Tue, 27 Nov 2012 23:49:27 +0100
Subject: [PATCH 06/56] Added a get_excluded_fieldnames() method. Model
 validation now excludes fields not listed in Meta fields (if set).

---
 rest_framework/serializers.py | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 5046c7b1e..775a8a1e2 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -119,6 +119,17 @@ class BaseSerializer(Field):
         """
         return {}
 
+    def get_excluded_fieldnames(self):
+        """
+        Returns the fieldnames that should not be validated.
+        """
+        excluded_fields = list(self.opts.exclude)
+        for field in self.fields.keys() + self.default_fields.keys():
+            if self.opts.fields:
+                if field not in self.opts.fields + self.opts.exclude:
+                    excluded_fields.append(field)
+        return excluded_fields
+
     def get_fields(self):
         """
         Returns the complete set of fields for the object as a dict.
@@ -466,7 +477,7 @@ class ModelSerializer(Serializer):
 
     def perform_model_validation(self, restored_object):
         # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique()
-        restored_object.full_clean(exclude=list(self.opts.exclude))
+        restored_object.full_clean(exclude=list(self.get_excluded_fieldnames()))
 
     def restore_object(self, attrs, instance=None):
         """

From e311b763e193b41c6a679ddbcf813702691145a0 Mon Sep 17 00:00:00 2001
From: Pavel Savchenko <pasha.savchenko@gmail.com>
Date: Fri, 30 Nov 2012 01:34:46 +0200
Subject: [PATCH 07/56] add traverse_related feature + tests (fixes issue#461)

---
 rest_framework/serializers.py      | 14 +++++++---
 rest_framework/tests/models.py     | 25 +++++++++---------
 rest_framework/tests/serializer.py | 41 ++++++++++++++++++++++++++++++
 3 files changed, 65 insertions(+), 15 deletions(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 4519ab053..e63f47835 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -272,10 +272,18 @@ class BaseSerializer(Field):
         Override default so that we can apply ModelSerializer as a nested
         field to relationships.
         """
-        obj = getattr(obj, self.source or field_name)
 
-        if is_simple_callable(obj):
-            obj = obj()
+        if self.source:
+            value = obj
+            for component in self.source.split('.'):
+                value = getattr(value, component)
+                if is_simple_callable(value):
+                    value = value()
+            obj = value
+        else:
+            value = getattr(obj, field_name)
+            if is_simple_callable(value):
+                obj = value()
 
         # If the object has an "all" method, assume it's a relationship
         if is_simple_callable(getattr(obj, 'all', None)):
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
index c35861c6c..76435df84 100644
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -124,8 +124,21 @@ class ActionItem(RESTFrameworkModel):
 
 
 # Models for reverse relations
+class Person(RESTFrameworkModel):
+    name = models.CharField(max_length=10)
+    age = models.IntegerField(null=True, blank=True)
+
+    @property
+    def info(self):
+        return {
+            'name': self.name,
+            'age': self.age,
+        }
+
+
 class BlogPost(RESTFrameworkModel):
     title = models.CharField(max_length=100)
+    writer = models.ForeignKey(Person, null=True, blank=True)
 
     def get_first_comment(self):
         return self.blogpostcomment_set.all()[0]
@@ -145,18 +158,6 @@ class Photo(RESTFrameworkModel):
     album = models.ForeignKey(Album)
 
 
-class Person(RESTFrameworkModel):
-    name = models.CharField(max_length=10)
-    age = models.IntegerField(null=True, blank=True)
-
-    @property
-    def info(self):
-        return {
-            'name': self.name,
-            'age': self.age,
-        }
-
-
 # Model for issue #324
 class BlankFieldModel(RESTFrameworkModel):
     title = models.CharField(max_length=100, blank=True)
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 61a05da18..b16f2772f 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -560,6 +560,47 @@ class ManyRelatedTests(TestCase):
         self.assertEqual(serializer.data, expected)
 
 
+class RelatedTraversalTest(TestCase):
+    def test_nested_traversal(self):
+        user = Person.objects.create(name="django")
+        post = BlogPost.objects.create(title="Test blog post", writer=user)
+        post.blogpostcomment_set.create(text="I love this blog post")
+
+        from rest_framework.tests.models import BlogPostComment
+
+        class PersonSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = Person
+                fields = ("name", "age")
+
+        class BlogPostCommentSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = BlogPostComment
+                fields = ("text", "post_owner")
+
+            text = serializers.CharField()
+            post_owner = PersonSerializer(source='blog_post.writer')
+
+        class BlogPostSerializer(serializers.Serializer):
+            title = serializers.CharField()
+            comments = BlogPostCommentSerializer(source='blogpostcomment_set')
+
+        serializer = BlogPostSerializer(instance=post)
+
+        expected = {
+            'title': 'Test blog post',
+            'comments': [{
+                'text': 'I hate this blog post',
+                'post_owner': {
+                    "name": "django",
+                    "age": None
+                }
+            }]
+        }
+
+        self.assertEqual(serializer.data, expected)
+
+
 class SerializerMethodFieldTests(TestCase):
     def setUp(self):
 

From 1c1bd3fc5d7e65ae8c16e9946be87956c96a1723 Mon Sep 17 00:00:00 2001
From: Pavel Savchenko <pasha.savchenko@gmail.com>
Date: Fri, 30 Nov 2012 01:37:21 +0200
Subject: [PATCH 08/56] fix test response

---
 rest_framework/tests/serializer.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index b16f2772f..26a7d6bfd 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -588,11 +588,11 @@ class RelatedTraversalTest(TestCase):
         serializer = BlogPostSerializer(instance=post)
 
         expected = {
-            'title': 'Test blog post',
+            'title': u'Test blog post',
             'comments': [{
-                'text': 'I hate this blog post',
+                'text': u'I love this blog post',
                 'post_owner': {
-                    "name": "django",
+                    "name": u"django",
                     "age": None
                 }
             }]

From e044fa089b5ccdcc3557a65c106fad0f44f1b7b8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Gro=C3=9F?= <stephan@minddust.com>
Date: Tue, 4 Dec 2012 09:40:23 +0100
Subject: [PATCH 09/56] fixed #469 - RegexField <--> BrowsableAPI Bug

---
 docs/topics/release-notes.md | 4 ++++
 rest_framework/fields.py     | 1 +
 rest_framework/renderers.py  | 3 +++
 3 files changed, 8 insertions(+)

diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 867b138bf..c2fe3f64d 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
+
+* Bugfix: Fix `RegexField` to work with `BrowsableAPIRenderer`
+
 ## 2.1.6
 
 **Date**: 23rd Nov 2012
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 482a3d485..ea0667f50 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -817,6 +817,7 @@ class EmailField(CharField):
 
 class RegexField(CharField):
     type_name = 'RegexField'
+    form_field_class = forms.RegexField
 
     def __init__(self, regex, max_length=None, min_length=None, *args, **kwargs):
         super(RegexField, self).__init__(max_length, min_length, *args, **kwargs)
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 25a32baae..1220bca10 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -320,6 +320,9 @@ class BrowsableAPIRenderer(BaseRenderer):
             if getattr(v, 'choices', None) is not None:
                 kwargs['choices'] = v.choices
 
+            if getattr(v, 'regex', None) is not None:
+                kwargs['regex'] = v.regex
+
             if getattr(v, 'widget', None):
                 widget = copy.deepcopy(v.widget)
                 kwargs['widget'] = widget

From 3867d9deb18d132ec5e0325370c77c2cf9aa0215 Mon Sep 17 00:00:00 2001
From: Michael Richards <miker@miker-laptop.TRAC>
Date: Tue, 4 Dec 2012 11:07:31 -0800
Subject: [PATCH 10/56] Added support for 'true'/'false' as valid boolean data

---
 rest_framework/fields.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 482a3d485..ff39fac48 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -693,9 +693,9 @@ class BooleanField(WritableField):
     default = False
 
     def from_native(self, value):
-        if value in ('t', 'True', '1'):
+        if value in ('true', 't', 'True', '1'):
             return True
-        if value in ('f', 'False', '0'):
+        if value in ('false', 'f', 'False', '0'):
             return False
         return bool(value)
 

From fc6dbb45e023a5e5e6c92bd434b93350c4fbb8d3 Mon Sep 17 00:00:00 2001
From: Reinout van Rees <reinout@vanrees.org>
Date: Wed, 5 Dec 2012 12:20:03 +0100
Subject: [PATCH 11/56] Fixed wording.

---
 docs/tutorial/quickstart.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index 9a36a2b0d..74084541d 100644
--- a/docs/tutorial/quickstart.md
+++ b/docs/tutorial/quickstart.md
@@ -137,7 +137,7 @@ We'd also like to set a few global settings.  We'd like to turn on pagination, a
         'PAGINATE_BY': 10
     }
 
-Okay, that's us done.
+Okay, we're done.
 
 ---
 

From 3417c4631d0680aee14f7b06435d00c25ce5b464 Mon Sep 17 00:00:00 2001
From: Reinout van Rees <reinout@vanrees.org>
Date: Wed, 5 Dec 2012 12:31:38 +0100
Subject: [PATCH 12/56] Fixed typos and fixed wording.

Some singular/plural fixes.
Fixed some 'serialise->serialize' kind of UK/US differences. The 'z' seems more common in the rest of the docs, so that's what I used.
Removed a half-finished-sentence left dangling somewhere.
---
 docs/tutorial/1-serialization.md | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index ba64f2aa7..e61fb9469 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -14,7 +14,7 @@ The tutorial is fairly in-depth, so you should probably get a cookie and a cup o
 
 ## Setting up a new environment
 
-Before we do anything else we'll create a new virtual environment, using [virtualenv].  This will make sure our package configuration is keep nicely isolated from any other projects we're working on.
+Before we do anything else we'll create a new virtual environment, using [virtualenv].  This will make sure our package configuration is kept nicely isolated from any other projects we're working on.
 
     :::bash
     mkdir ~/env
@@ -39,7 +39,6 @@ To get started, let's create a new project to work with.
     cd tutorial
 
 Once that's done we can create an app that we'll use to create a simple Web API.
-We're going to create a project that 
 
     python manage.py startapp snippets
 
@@ -64,7 +63,7 @@ We'll also need to add our new `snippets` app and the `rest_framework` app to `I
         'snippets'
     )
 
-We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet views.
+We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet app's URLs.
 
     urlpatterns = patterns('',
         url(r'^', include('snippets.urls')),
@@ -105,7 +104,7 @@ Don't forget to sync the database for the first time.
 
 ## Creating a Serializer class
 
-The first thing we need to get started on our Web API is provide a way of serializing and deserializing the snippet instances into representations such as `json`.  We can do this by declaring serializers that work very similarly to Django's forms.  Create a file in the `snippets` directory named `serializers.py` and add the following.
+The first thing we need to get started on our Web API is provide a way of serializing and deserializing the snippet instances into representations such as `json`.  We can do this by declaring serializers that work very similar to Django's forms.  Create a file in the `snippets` directory named `serializers.py` and add the following.
 
     from django.forms import widgets
     from rest_framework import serializers
@@ -146,7 +145,7 @@ We can actually also save ourselves some time by using the `ModelSerializer` cla
 
 ## Working with Serializers
 
-Before we go any further we'll familiarise ourselves with using our new Serializer class.  Let's drop into the Django shell.
+Before we go any further we'll familiarize ourselves with using our new Serializer class.  Let's drop into the Django shell.
 
     python manage.py shell
 
@@ -166,7 +165,7 @@ We've now got a few snippet instances to play with.  Let's take a look at serial
     serializer.data
     # {'pk': 1, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
 
-At this point we've translated the model instance into python native datatypes.  To finalise the serialization process we render the data into `json`.
+At this point we've translated the model instance into python native datatypes.  To finalize the serialization process we render the data into `json`.
 
     content = JSONRenderer().render(serializer.data)
     content
@@ -292,7 +291,7 @@ Finally we need to wire these views up. Create the `snippets/urls.py` file:
         url(r'^snippets/(?P<pk>[0-9]+)/$', 'snippet_detail')
     )
 
-It's worth noting that there's a couple of edge cases we're not dealing with properly at the moment.  If we send malformed `json`, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response.  Still, this'll do for now.
+It's worth noting that there are a couple of edge cases we're not dealing with properly at the moment.  If we send malformed `json`, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response.  Still, this'll do for now.
 
 ## Testing our first attempt at a Web API
 
@@ -304,7 +303,7 @@ It's worth noting that there's a couple of edge cases we're not dealing with pro
 
 We're doing okay so far, we've got a serialization API that feels pretty similar to Django's Forms API, and some regular Django views.
 
-Our API views don't do anything particularly special at the moment, beyond serve `json` responses, and there's some error handling edge cases we'd still like to clean up, but it's a functioning Web API.
+Our API views don't do anything particularly special at the moment, beyond serving `json` responses, and there are some error handling edge cases we'd still like to clean up, but it's a functioning Web API.
 
 We'll see how we can start to improve things in [part 2 of the tutorial][tut-2].
 

From 3868241f6acda96fbd08cc81211b09ffbc2f38b3 Mon Sep 17 00:00:00 2001
From: Marko Tibold <marko@tibold.nl>
Date: Wed, 5 Dec 2012 15:09:06 +0100
Subject: [PATCH 13/56] Update docs/api-guide/permissions.md

@permission_classes takes a tuple or list.
---
 docs/api-guide/permissions.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md
index 1a746fb64..fce68f6db 100644
--- a/docs/api-guide/permissions.md
+++ b/docs/api-guide/permissions.md
@@ -53,7 +53,7 @@ You can also set the authentication policy on a per-view basis, using the `APIVi
 Or, if you're using the `@api_view` decorator with function based views.
 
     @api_view('GET')
-    @permission_classes(IsAuthenticated)
+    @permission_classes((IsAuthenticated, ))
     def example_view(request, format=None):
         content = {
             'status': 'request was permitted'

From cb4e85721717517c9afd86c5e5e027ba19885b27 Mon Sep 17 00:00:00 2001
From: Reinout van Rees <reinout@vanrees.org>
Date: Wed, 5 Dec 2012 16:04:52 +0100
Subject: [PATCH 14/56] Textual fixes.

Added a sentence introducing the second view.
Fix one or two additional sentences.
---
 docs/tutorial/2-requests-and-responses.md | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md
index 187effb9d..08cf91cdb 100644
--- a/docs/tutorial/2-requests-and-responses.md
+++ b/docs/tutorial/2-requests-and-responses.md
@@ -66,6 +66,8 @@ We don't need our `JSONResponse` class anymore, so go ahead and delete that.  On
 
 Our instance view is an improvement over the previous example.  It's a little more concise, and the code now feels very similar to if we were working with the Forms API.  We're also using named status codes, which makes the response meanings more obvious.
 
+Here is the view for an individual snippet.
+
     @api_view(['GET', 'PUT', 'DELETE'])
     def snippet_detail(request, pk):
         """
@@ -92,7 +94,7 @@ Our instance view is an improvement over the previous example.  It's a little mo
             snippet.delete()
             return Response(status=status.HTTP_204_NO_CONTENT)
 
-This should all feel very familiar - there's not a lot different to working with regular Django views.
+This should all feel very familiar - it is not a lot different from working with regular Django views.
 
 Notice that we're no longer explicitly tying our requests or responses to a given content type.  `request.DATA` can handle incoming `json` requests, but it can also handle `yaml` and other formats.  Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us.
 
@@ -128,7 +130,7 @@ Go ahead and test the API from the command line, as we did in [tutorial part 1][
 
 **TODO: Describe using accept headers, content-type headers, and format suffixed URLs**
 
-Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/snippets/][devserver]."
+Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/snippets/][devserver].
 
 ### Browsability
 

From ee184b86292d347ba747ee4a438f17e4fc613947 Mon Sep 17 00:00:00 2001
From: Reinout van Rees <reinout@vanrees.org>
Date: Wed, 5 Dec 2012 16:08:13 +0100
Subject: [PATCH 15/56] Small textual fixes.

---
 docs/tutorial/3-class-based-views.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md
index d87d20464..a3a18060f 100644
--- a/docs/tutorial/3-class-based-views.md
+++ b/docs/tutorial/3-class-based-views.md
@@ -102,7 +102,7 @@ Let's take a look at how we can compose our views by using the mixin classes.
         def post(self, request, *args, **kwargs):
             return self.create(request, *args, **kwargs)
 
-We'll take a moment to examine exactly what's happening here - We're building our view using `MultipleObjectAPIView`, and adding in `ListModelMixin` and `CreateModelMixin`.
+We'll take a moment to examine exactly what's happening here. We're building our view using `MultipleObjectAPIView`, and adding in `ListModelMixin` and `CreateModelMixin`.
 
 The base class provides the core functionality, and the mixin classes provide the `.list()` and `.create()` actions.  We're then explicitly binding the `get` and `post` methods to the appropriate actions.  Simple enough stuff so far.
 
@@ -142,7 +142,7 @@ Using the mixin classes we've rewritten the views to use slightly less code than
         model = Snippet
         serializer_class = SnippetSerializer
 
-Wow, that's pretty concise.  We've got a huge amount for free, and our code looks like good, clean, idiomatic Django.
+Wow, that's pretty concise.  We've gotten a huge amount for free, and our code looks like good, clean, idiomatic Django.
 
 Next we'll move onto [part 4 of the tutorial][tut-4], where we'll take a look at how we can deal with authentication and permissions for our API.
 

From 3f39828788d856bb7923bfb3acf801e571597e55 Mon Sep 17 00:00:00 2001
From: Reinout van Rees <reinout@vanrees.org>
Date: Wed, 5 Dec 2012 16:16:46 +0100
Subject: [PATCH 16/56] Small textual fixes.

---
 docs/tutorial/4-authentication-and-permissions.md | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md
index f85250bea..9576a7f0b 100644
--- a/docs/tutorial/4-authentication-and-permissions.md
+++ b/docs/tutorial/4-authentication-and-permissions.md
@@ -61,7 +61,7 @@ Now that we've got some users to work with, we'd better add representations of t
             model = User
             fields = ('id', 'username', 'snippets')
 
-Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we've needed to add an explicit field for it.
+Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we needed to add an explicit field for it.
 
 We'll also add a couple of views.  We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class based views.
 
@@ -92,9 +92,7 @@ On **both** the `SnippetList` and `SnippetDetail` view classes, add the followin
 
 ## Updating our serializer
 
-Now that snippets are associated with the user that created them, let's update our SnippetSerializer to reflect that.
-
-Add the following field to the serializer definition:
+Now that snippets are associated with the user that created them, let's update our `SnippetSerializer` to reflect that. Add the following field to the serializer definition:
 
     owner = serializers.Field(source='owner.username')
 
@@ -108,7 +106,7 @@ The field we've added is the untyped `Field` class, in contrast to the other typ
 
 ## Adding required permissions to views
 
-Now that code snippets are associated with users we want to make sure that only authenticated users are able to create, update and delete code snippets.
+Now that code snippets are associated with users, we want to make sure that only authenticated users are able to create, update and delete code snippets.
 
 REST framework includes a number of permission classes that we can use to restrict who can access a given view.  In this case the one we're looking for is `IsAuthenticatedOrReadOnly`, which will ensure that authenticated requests get read-write access, and unauthenticated requests get read-only access.
 

From 7a110a3006b47e61c12dd5ec9e62b278d1b17298 Mon Sep 17 00:00:00 2001
From: Reinout van Rees <reinout@vanrees.org>
Date: Wed, 5 Dec 2012 16:24:41 +0100
Subject: [PATCH 17/56] Two typo fixes.

Plural/singular fix.
Typo fixed.
---
 docs/tutorial/5-relationships-and-hyperlinked-apis.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
index 98c45b821..b5d378751 100644
--- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md
+++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
@@ -25,7 +25,7 @@ Notice that we're using REST framework's `reverse` function in order to return f
 
 The other obvious thing that's still missing from our pastebin API is the code highlighting endpoints.
 
-Unlike all our other API endpoints, we don't want to use JSON, but instead just present an HTML representation.  There are two style of HTML renderer provided by REST framework, one for dealing with HTML rendered using templates, the other for dealing with pre-rendered HTML.  The second renderer is the one we'd like to use for this endpoint.
+Unlike all our other API endpoints, we don't want to use JSON, but instead just present an HTML representation.  There are two styles of HTML renderer provided by REST framework, one for dealing with HTML rendered using templates, the other for dealing with pre-rendered HTML.  The second renderer is the one we'd like to use for this endpoint.
 
 The other thing we need to consider when creating the code highlight view is that there's no existing concrete generic view that we can use.  We're not returning an object instance, but instead a property of an object instance.
 
@@ -151,7 +151,7 @@ We could also customize the pagination style if we needed too, but in this case
 
 If we open a browser and navigate to the browseable API, you'll find that you can now work your way around the API simply by following links.
 
-You'll also be able to see the 'highlight' links on the snippet instances, that will take you to the hightlighted code HTML representations.
+You'll also be able to see the 'highlight' links on the snippet instances, that will take you to the highlighted code HTML representations.
 
 We've now got a complete pastebin Web API, which is fully web browseable, and comes complete with authentication, per-object permissions, and multiple renderer formats.
 

From 705c7ad09db65c6ea6fb69bbd417cb7a45f6e3b9 Mon Sep 17 00:00:00 2001
From: Ben Roberts <ben@nutrislice.com>
Date: Wed, 5 Dec 2012 17:43:47 -0700
Subject: [PATCH 18/56] added tests and fix for unpickleable metadata in
 SortedDictWithMetadata

---
 rest_framework/serializers.py      | 12 +++++++-
 rest_framework/tests/serializer.py | 47 ++++++++++++++++++++++++++++--
 2 files changed, 56 insertions(+), 3 deletions(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 4519ab053..fcc0744a3 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -22,7 +22,16 @@ class DictWithMetadata(dict):
     """
     A dict-like object, that can have additional properties attached.
     """
-    pass
+
+    def __getstate__(self):
+        """  Used by pickle (e.g., caching).
+             Overriden to remove metadata from the dict, since it shouldn't be pickled
+             and may in some instances be unpickleable.
+        """
+        # return an instance of the first dict in MRO that isn't a DictWithMetadata
+        for base in self.__class__.__mro__:
+            if not isinstance(base, DictWithMetadata) and isinstance(base, dict):
+                return base(self)
 
 
 class SortedDictWithMetadata(SortedDict, DictWithMetadata):
@@ -32,6 +41,7 @@ class SortedDictWithMetadata(SortedDict, DictWithMetadata):
     pass
 
 
+
 def _is_protected_type(obj):
     """
     True if the object is a native datatype that does not need to
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 61a05da18..af182917f 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -1,9 +1,9 @@
-import datetime
+import datetime, pickle
 from django.test import TestCase
 from rest_framework import serializers
 from rest_framework.tests.models import (ActionItem, Anchor, BasicModel,
     BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel,
-    ManyToManyModel, Person, ReadOnlyManyToManyModel)
+    ManyToManyModel, Person, ReadOnlyManyToManyModel, BlogPostComment)
 
 
 class SubComment(object):
@@ -641,3 +641,46 @@ class BlankFieldTests(TestCase):
         """
         serializer = self.not_blank_model_serializer_class(data=self.data)
         self.assertEquals(serializer.is_valid(), False)
+
+
+#class PersonGroup(object):
+#    name = "group"
+#    persons = [Person(name="joe"), Person(name="job")]
+#
+#class PersonGroupSerializer(serializers.Serializer):
+#    name = serializers.CharField()
+#    persons = PersonSerializer()
+#
+#class BlogPostSerializer(serializers.ModelSerializer):
+#    class Meta:
+#        model = BlogPost
+#
+#
+#class BlogPostCommentSerializer(serializers.ModelSerializer):
+#    class Meta:
+#        model = BlogPostComment
+#        fields = ('text', 'blog_post')
+#
+#    blog_post = BlogPostSerializer()
+#
+
+#test for issue #460
+class SerializerPickleTests(TestCase):
+    """ Test pickleability of the output of Serializers
+    """
+    def test_pickle_simple_model_serializer_data(self):
+        """ Test simple serializer
+        """
+        pickle.dumps(PersonSerializer(Person(name="Methusela", age=969)).data)
+
+
+    def test_pickle_inner_serializer(self):
+        """ Test pickling a serializer whose resulting .data (a SortedDictWithMetadata) will
+         have unpickleable meta data--in order to make sure metadata doesn't get pulled into the pickle.
+         See DictWithMetadata.__getstate__
+        """
+        class InnerPersonSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = Person
+                fields = ('name', 'age')
+        pickle.dumps(InnerPersonSerializer(Person(name="Noah", age=950)).data)

From 7f28a784146b9ba6ab303e79597f85a0f8b1e76e Mon Sep 17 00:00:00 2001
From: Ben Roberts <ben@nutrislice.com>
Date: Wed, 5 Dec 2012 17:54:21 -0700
Subject: [PATCH 19/56] cleaned up last commit

---
 rest_framework/tests/serializer.py | 23 +----------------------
 1 file changed, 1 insertion(+), 22 deletions(-)

diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index af182917f..9cedb54b4 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -3,7 +3,7 @@ from django.test import TestCase
 from rest_framework import serializers
 from rest_framework.tests.models import (ActionItem, Anchor, BasicModel,
     BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel,
-    ManyToManyModel, Person, ReadOnlyManyToManyModel, BlogPostComment)
+    ManyToManyModel, Person, ReadOnlyManyToManyModel)
 
 
 class SubComment(object):
@@ -643,27 +643,6 @@ class BlankFieldTests(TestCase):
         self.assertEquals(serializer.is_valid(), False)
 
 
-#class PersonGroup(object):
-#    name = "group"
-#    persons = [Person(name="joe"), Person(name="job")]
-#
-#class PersonGroupSerializer(serializers.Serializer):
-#    name = serializers.CharField()
-#    persons = PersonSerializer()
-#
-#class BlogPostSerializer(serializers.ModelSerializer):
-#    class Meta:
-#        model = BlogPost
-#
-#
-#class BlogPostCommentSerializer(serializers.ModelSerializer):
-#    class Meta:
-#        model = BlogPostComment
-#        fields = ('text', 'blog_post')
-#
-#    blog_post = BlogPostSerializer()
-#
-
 #test for issue #460
 class SerializerPickleTests(TestCase):
     """ Test pickleability of the output of Serializers

From cb7d9ea5c9843ffa99db4400670a11c3651520cc Mon Sep 17 00:00:00 2001
From: Ben Roberts <ben@nutrislice.com>
Date: Thu, 6 Dec 2012 12:45:50 -0700
Subject: [PATCH 20/56] cleaned up white space & docstring styling

---
 rest_framework/serializers.py      |  9 ++++-----
 rest_framework/tests/serializer.py | 14 ++++++++------
 2 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index fcc0744a3..51e0b6647 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -22,11 +22,11 @@ class DictWithMetadata(dict):
     """
     A dict-like object, that can have additional properties attached.
     """
-
     def __getstate__(self):
-        """  Used by pickle (e.g., caching).
-             Overriden to remove metadata from the dict, since it shouldn't be pickled
-             and may in some instances be unpickleable.
+        """
+        Used by pickle (e.g., caching).
+        Overriden to remove metadata from the dict, since it shouldn't be pickled
+        and may in some instances be unpickleable.
         """
         # return an instance of the first dict in MRO that isn't a DictWithMetadata
         for base in self.__class__.__mro__:
@@ -41,7 +41,6 @@ class SortedDictWithMetadata(SortedDict, DictWithMetadata):
     pass
 
 
-
 def _is_protected_type(obj):
     """
     True if the object is a native datatype that does not need to
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 9cedb54b4..31dd36994 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -645,18 +645,20 @@ class BlankFieldTests(TestCase):
 
 #test for issue #460
 class SerializerPickleTests(TestCase):
-    """ Test pickleability of the output of Serializers
+    """
+    Test pickleability of the output of Serializers
     """
     def test_pickle_simple_model_serializer_data(self):
-        """ Test simple serializer
+        """
+        Test simple serializer
         """
         pickle.dumps(PersonSerializer(Person(name="Methusela", age=969)).data)
 
-
     def test_pickle_inner_serializer(self):
-        """ Test pickling a serializer whose resulting .data (a SortedDictWithMetadata) will
-         have unpickleable meta data--in order to make sure metadata doesn't get pulled into the pickle.
-         See DictWithMetadata.__getstate__
+        """
+        Test pickling a serializer whose resulting .data (a SortedDictWithMetadata) will
+        have unpickleable meta data--in order to make sure metadata doesn't get pulled into the pickle.
+        See DictWithMetadata.__getstate__
         """
         class InnerPersonSerializer(serializers.ModelSerializer):
             class Meta:

From 2938bc13b12ec73084c21e629bdde4a20a1de0cb Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Thu, 6 Dec 2012 16:30:22 -0400
Subject: [PATCH 21/56] Added @reinout for the copy fixes. Thanks!

---
 docs/topics/credits.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index f2f09c0e8..a2e0a23fc 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -71,6 +71,7 @@ The following people have helped make REST framework great.
 * Fabian Buechler - [fabianbuechler]
 * Mark Hughes - [mhsparks]
 * Michael van de Waeter - [mvdwaeter]
+* Reinout van Rees - [reinout]
 
 Many thanks to everyone who's contributed to the project.
 
@@ -177,3 +178,5 @@ To contact the author directly:
 [fabianbuechler]: https://github.com/fabianbuechler
 [mhsparks]: https://github.com/mhsparks
 [mvdwaeter]: https://github.com/mvdwaeter
+[reinout]: https://github.com/reinout
+

From 6a5f4f2a90ab19a8586a9d762c9b2618e8db5c30 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Thu, 6 Dec 2012 22:38:20 +0000
Subject: [PATCH 22/56] Added @justanotherbody.  Thanks!

---
 docs/topics/credits.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index a2e0a23fc..9e59d678f 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -72,6 +72,7 @@ The following people have helped make REST framework great.
 * Mark Hughes - [mhsparks]
 * Michael van de Waeter - [mvdwaeter]
 * Reinout van Rees - [reinout]
+* Michael Richards - [justanotherbody]
 
 Many thanks to everyone who's contributed to the project.
 
@@ -179,4 +180,4 @@ To contact the author directly:
 [mhsparks]: https://github.com/mhsparks
 [mvdwaeter]: https://github.com/mvdwaeter
 [reinout]: https://github.com/reinout
-
+[justanotherbody]: https://github.com/justanotherbody

From 26cfa023263576258e53fe23bc92e437398ff15f Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Thu, 6 Dec 2012 22:56:23 +0000
Subject: [PATCH 23/56] Added @roberts81.  Thanks!

---
 docs/topics/credits.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index 9e59d678f..dfa1ee0f8 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -73,6 +73,7 @@ The following people have helped make REST framework great.
 * Michael van de Waeter - [mvdwaeter]
 * Reinout van Rees - [reinout]
 * Michael Richards - [justanotherbody]
+* Ben Roberts - [roberts81]
 
 Many thanks to everyone who's contributed to the project.
 
@@ -181,3 +182,4 @@ To contact the author directly:
 [mvdwaeter]: https://github.com/mvdwaeter
 [reinout]: https://github.com/reinout
 [justanotherbody]: https://github.com/justanotherbody
+[roberts81]: https://github.com/roberts81

From 919aff329ee1bd214831095e4d96af71795ed572 Mon Sep 17 00:00:00 2001
From: Marko Tibold <marko@tibold.nl>
Date: Fri, 7 Dec 2012 00:08:27 +0100
Subject: [PATCH 24/56] Fix AttributeError caused by accessing a non-existing
 default_keys attribute.

---
 rest_framework/serializers.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 984f3ac57..43bfda83b 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -123,7 +123,7 @@ class BaseSerializer(Field):
         Returns the fieldnames that should not be validated.
         """
         excluded_fields = list(self.opts.exclude)
-        for field in self.fields.keys() + self.default_fields.keys():
+        for field in self.fields.keys() + self.get_default_fields().keys():
             if self.opts.fields:
                 if field not in self.opts.fields + self.opts.exclude:
                     excluded_fields.append(field)

From 303bc7cf95033d2560668bf6f4d97f05f1268967 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Fri, 7 Dec 2012 21:32:39 +0000
Subject: [PATCH 25/56] Support nullable FKs, with blank=True

---
 rest_framework/fields.py                      |  8 ++-
 rest_framework/serializers.py                 | 14 +++--
 .../tests/hyperlinkedserializers.py           | 25 ++++++---
 rest_framework/tests/pk_relations.py          | 53 +++++++++++++++++--
 4 files changed, 85 insertions(+), 15 deletions(-)

diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index c28a9695a..bffc0fb05 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -350,7 +350,13 @@ class RelatedField(WritableField):
             return
 
         value = data.get(field_name)
-        into[(self.source or field_name)] = self.from_native(value)
+
+        if value is None and not self.blank:
+            raise ValidationError('Value may not be null')
+        elif value is None and self.blank:
+            into[(self.source or field_name)] = None
+        else:
+            into[(self.source or field_name)] = self.from_native(value)
 
 
 class ManyRelatedMixin(object):
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 5edd46f5c..13c41a4bd 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -431,10 +431,14 @@ class ModelSerializer(Serializer):
         """
         # TODO: filter queryset using:
         # .using(db).complex_filter(self.rel.limit_choices_to)
-        queryset = model_field.rel.to._default_manager
+        kwargs = {
+            'blank': model_field.blank,
+            'queryset': model_field.rel.to._default_manager
+        }
+
         if to_many:
-            return ManyPrimaryKeyRelatedField(queryset=queryset)
-        return PrimaryKeyRelatedField(queryset=queryset)
+            return ManyPrimaryKeyRelatedField(**kwargs)
+        return PrimaryKeyRelatedField(**kwargs)
 
     def get_field(self, model_field):
         """
@@ -572,9 +576,9 @@ class HyperlinkedModelSerializer(ModelSerializer):
         # TODO: filter queryset using:
         # .using(db).complex_filter(self.rel.limit_choices_to)
         rel = model_field.rel.to
-        queryset = rel._default_manager
         kwargs = {
-            'queryset': queryset,
+            'blank': model_field.blank,
+            'queryset': rel._default_manager,
             'view_name': self._get_default_view_name(rel)
         }
         if to_many:
diff --git a/rest_framework/tests/hyperlinkedserializers.py b/rest_framework/tests/hyperlinkedserializers.py
index d7effce70..24bf61bf8 100644
--- a/rest_framework/tests/hyperlinkedserializers.py
+++ b/rest_framework/tests/hyperlinkedserializers.py
@@ -1,6 +1,7 @@
 from django.conf.urls.defaults import patterns, url
 from django.test import TestCase
 from django.test.client import RequestFactory
+from django.utils import simplejson as json
 from rest_framework import generics, status, serializers
 from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel
 
@@ -54,10 +55,12 @@ class BlogPostCommentListCreate(generics.ListCreateAPIView):
     model = BlogPostComment
     serializer_class = BlogPostCommentSerializer
 
+
 class BlogPostCommentDetail(generics.RetrieveAPIView):
     model = BlogPostComment
     serializer_class = BlogPostCommentSerializer
 
+
 class BlogPostDetail(generics.RetrieveAPIView):
     model = BlogPost
 
@@ -71,7 +74,7 @@ class AlbumDetail(generics.RetrieveAPIView):
     model = Album
 
 
-class OptionalRelationDetail(generics.RetrieveAPIView):
+class OptionalRelationDetail(generics.RetrieveUpdateDestroyAPIView):
     model = OptionalRelationModel
     model_serializer_class = serializers.HyperlinkedModelSerializer
 
@@ -162,7 +165,7 @@ class TestManyToManyHyperlinkedView(TestCase):
         GET requests to ListCreateAPIView should return list of objects.
         """
         request = factory.get('/manytomany/')
-        response = self.list_view(request).render()
+        response = self.list_view(request)
         self.assertEquals(response.status_code, status.HTTP_200_OK)
         self.assertEquals(response.data, self.data)
 
@@ -171,7 +174,7 @@ class TestManyToManyHyperlinkedView(TestCase):
         GET requests to ListCreateAPIView should return list of objects.
         """
         request = factory.get('/manytomany/1/')
-        response = self.detail_view(request, pk=1).render()
+        response = self.detail_view(request, pk=1)
         self.assertEquals(response.status_code, status.HTTP_200_OK)
         self.assertEquals(response.data, self.data[0])
 
@@ -194,7 +197,7 @@ class TestCreateWithForeignKeys(TestCase):
         }
 
         request = factory.post('/comments/', data=data)
-        response = self.create_view(request).render()
+        response = self.create_view(request)
         self.assertEqual(response.status_code, status.HTTP_201_CREATED)
         self.assertEqual(response['Location'], 'http://testserver/comments/1/')
         self.assertEqual(self.post.blogpostcomment_set.count(), 1)
@@ -219,7 +222,7 @@ class TestCreateWithForeignKeysAndCustomSlug(TestCase):
         }
 
         request = factory.post('/photos/', data=data)
-        response = self.list_create_view(request).render()
+        response = self.list_create_view(request)
         self.assertEqual(response.status_code, status.HTTP_201_CREATED)
         self.assertNotIn('Location', response, msg='Location should only be included if there is a "url" field on the serializer')
         self.assertEqual(self.post.photo_set.count(), 1)
@@ -244,6 +247,16 @@ class TestOptionalRelationHyperlinkedView(TestCase):
         for non existing relations.
         """
         request = factory.get('/optionalrelationmodel-detail/1')
-        response = self.detail_view(request, pk=1).render()
+        response = self.detail_view(request, pk=1)
         self.assertEquals(response.status_code, status.HTTP_200_OK)
         self.assertEquals(response.data, self.data)
+
+    def test_put_detail_view(self):
+        """
+        PUT requests to RetrieveUpdateDestroyAPIView with optional relations
+        should accept None for non existing relations.
+        """
+        response = self.client.put('/optionalrelation/1/',
+                                   data=json.dumps(self.data),
+                                   content_type='application/json')
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
diff --git a/rest_framework/tests/pk_relations.py b/rest_framework/tests/pk_relations.py
index 3dcc76f97..53245d945 100644
--- a/rest_framework/tests/pk_relations.py
+++ b/rest_framework/tests/pk_relations.py
@@ -49,9 +49,22 @@ class ForeignKeySourceSerializer(serializers.ModelSerializer):
         model = ForeignKeySource
 
 
+# Nullable ForeignKey
+
+class NullableForeignKeySource(models.Model):
+    name = models.CharField(max_length=100)
+    target = models.ForeignKey(ForeignKeyTarget, null=True, blank=True,
+                               related_name='nullable_sources')
+
+
+class NullableForeignKeySourceSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = NullableForeignKeySource
+
+
 # TODO: Add test that .data cannot be accessed prior to .is_valid
 
-class PrimaryKeyManyToManyTests(TestCase):
+class PKManyToManyTests(TestCase):
     def setUp(self):
         for idx in range(1, 4):
             target = ManyToManyTarget(name='target-%d' % idx)
@@ -137,7 +150,7 @@ class PrimaryKeyManyToManyTests(TestCase):
         self.assertEquals(serializer.data, expected)
 
 
-class PrimaryKeyForeignKeyTests(TestCase):
+class PKForeignKeyTests(TestCase):
     def setUp(self):
         target = ForeignKeyTarget(name='target-1')
         target.save()
@@ -174,7 +187,7 @@ class PrimaryKeyForeignKeyTests(TestCase):
         self.assertEquals(serializer.data, data)
         serializer.save()
 
-        # # Ensure source 1 is updated, and everything else is as expected
+        # Ensure source 1 is updated, and everything else is as expected
         queryset = ForeignKeySource.objects.all()
         serializer = ForeignKeySourceSerializer(queryset)
         expected = [
@@ -184,6 +197,40 @@ class PrimaryKeyForeignKeyTests(TestCase):
         ]
         self.assertEquals(serializer.data, expected)
 
+    def test_foreign_key_update_with_invalid_null(self):
+        data = {'id': 1, 'name': u'source-1', 'target': None}
+        instance = ForeignKeySource.objects.get(pk=1)
+        serializer = ForeignKeySourceSerializer(instance, data=data)
+        self.assertFalse(serializer.is_valid())
+        self.assertEquals(serializer.errors, {'target': [u'Value may not be null']})
+
+
+class PKNullableForeignKeyTests(TestCase):
+    def setUp(self):
+        target = ForeignKeyTarget(name='target-1')
+        target.save()
+        for idx in range(1, 4):
+            source = NullableForeignKeySource(name='source-%d' % idx, target=target)
+            source.save()
+
+    def test_foreign_key_update_with_valid_null(self):
+        data = {'id': 1, 'name': u'source-1', 'target': None}
+        instance = NullableForeignKeySource.objects.get(pk=1)
+        serializer = NullableForeignKeySourceSerializer(instance, data=data)
+        self.assertTrue(serializer.is_valid())
+        self.assertEquals(serializer.data, data)
+        serializer.save()
+
+        # Ensure source 1 is updated, and everything else is as expected
+        queryset = NullableForeignKeySource.objects.all()
+        serializer = NullableForeignKeySourceSerializer(queryset)
+        expected = [
+            {'id': 1, 'name': u'source-1', 'target': None},
+            {'id': 2, 'name': u'source-2', 'target': 1},
+            {'id': 3, 'name': u'source-3', 'target': 1}
+        ]
+        self.assertEquals(serializer.data, expected)
+
     # reverse foreign keys MUST be read_only
     # In the general case they do not provide .remove() or .clear()
     # and cannot be arbitrarily set.

From c911d54ae3769243fe6c74c29b5d16c7ac6efa10 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Fri, 7 Dec 2012 22:25:16 +0000
Subject: [PATCH 26/56] Reverted #458

When incorrect parameters are supplied to the obtain auth token view
400 *is* the correct response.
---
 rest_framework/authtoken/serializers.py |  2 +-
 rest_framework/authtoken/views.py       |  5 +++--
 rest_framework/tests/authentication.py  | 16 ++++++++--------
 3 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py
index a5ed6e6d7..60a3740e7 100644
--- a/rest_framework/authtoken/serializers.py
+++ b/rest_framework/authtoken/serializers.py
@@ -1,6 +1,7 @@
 from django.contrib.auth import authenticate
 from rest_framework import serializers
 
+
 class AuthTokenSerializer(serializers.Serializer):
     username = serializers.CharField()
     password = serializers.CharField()
@@ -21,4 +22,3 @@ class AuthTokenSerializer(serializers.Serializer):
                 raise serializers.ValidationError('Unable to login with provided credentials.')
         else:
             raise serializers.ValidationError('Must include "username" and "password"')
-
diff --git a/rest_framework/authtoken/views.py b/rest_framework/authtoken/views.py
index cfaacbe9a..d318c7233 100644
--- a/rest_framework/authtoken/views.py
+++ b/rest_framework/authtoken/views.py
@@ -6,11 +6,12 @@ from rest_framework.response import Response
 from rest_framework.authtoken.models import Token
 from rest_framework.authtoken.serializers import AuthTokenSerializer
 
+
 class ObtainAuthToken(APIView):
     throttle_classes = ()
     permission_classes = ()
     parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
-    renderer_classes = (renderers.JSONRenderer,) 
+    renderer_classes = (renderers.JSONRenderer,)
     model = Token
 
     def post(self, request):
@@ -18,7 +19,7 @@ class ObtainAuthToken(APIView):
         if serializer.is_valid():
             token, created = Token.objects.get_or_create(user=serializer.object['user'])
             return Response({'token': token.key})
-        return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
+        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
 
 
 obtain_auth_token = ObtainAuthToken.as_view()
diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py
index 802bc6c1f..d498ae3e0 100644
--- a/rest_framework/tests/authentication.py
+++ b/rest_framework/tests/authentication.py
@@ -1,4 +1,4 @@
-from django.conf.urls.defaults import patterns, include
+from django.conf.urls.defaults import patterns
 from django.contrib.auth.models import User
 from django.test import Client, TestCase
 
@@ -27,7 +27,7 @@ MockView.authentication_classes += (TokenAuthentication,)
 
 urlpatterns = patterns('',
     (r'^$', MockView.as_view()),
-    (r'^auth-token/', 'rest_framework.authtoken.views.obtain_auth_token'),
+    (r'^auth-token/$', 'rest_framework.authtoken.views.obtain_auth_token'),
 )
 
 
@@ -157,7 +157,7 @@ class TokenAuthTests(TestCase):
     def test_token_login_json(self):
         """Ensure token login view using JSON POST works."""
         client = Client(enforce_csrf_checks=True)
-        response = client.post('/auth-token/login/', 
+        response = client.post('/auth-token/',
                                json.dumps({'username': self.username, 'password': self.password}), 'application/json')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(json.loads(response.content)['token'], self.key)
@@ -165,21 +165,21 @@ class TokenAuthTests(TestCase):
     def test_token_login_json_bad_creds(self):
         """Ensure token login view using JSON POST fails if bad credentials are used."""
         client = Client(enforce_csrf_checks=True)
-        response = client.post('/auth-token/login/', 
+        response = client.post('/auth-token/',
                                json.dumps({'username': self.username, 'password': "badpass"}), 'application/json')
-        self.assertEqual(response.status_code, 401)
+        self.assertEqual(response.status_code, 400)
 
     def test_token_login_json_missing_fields(self):
         """Ensure token login view using JSON POST fails if missing fields."""
         client = Client(enforce_csrf_checks=True)
-        response = client.post('/auth-token/login/', 
+        response = client.post('/auth-token/',
                                json.dumps({'username': self.username}), 'application/json')
-        self.assertEqual(response.status_code, 401)
+        self.assertEqual(response.status_code, 400)
 
     def test_token_login_form(self):
         """Ensure token login view using form POST works."""
         client = Client(enforce_csrf_checks=True)
-        response = client.post('/auth-token/login/', 
+        response = client.post('/auth-token/',
                                {'username': self.username, 'password': self.password})
         self.assertEqual(response.status_code, 200)
         self.assertEqual(json.loads(response.content)['token'], self.key)

From 21f7dcf7c6da2e47f3b14d10018dfe6d0d060449 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Fri, 7 Dec 2012 22:25:28 +0000
Subject: [PATCH 27/56] Added release notes

---
 docs/topics/release-notes.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index c2fe3f64d..91cbb2fc4 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -6,6 +6,12 @@
 
 ## Master
 
+* Serializers now properly support nullable Foreign Keys.
+* Serializer validation now includes model field validation, such as uniqueness constraints.
+* Support 'true' and 'false' string values for BooleanField.
+* Added pickle support for serialized data.
+* Support `source='dotted.notation'` style for nested serializers.
+* Make `Request.user` settable.
 * Bugfix: Fix `RegexField` to work with `BrowsableAPIRenderer`
 
 ## 2.1.6

From b170973993cb269e2a061ab592d272ec9b67c86f Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Fri, 7 Dec 2012 22:36:30 +0000
Subject: [PATCH 28/56] Version 2.1.7

---
 README.md                    | 12 ++++++++++++
 docs/topics/release-notes.md |  4 +++-
 rest_framework/__init__.py   |  2 +-
 3 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index f646f957f..a9ea12427 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,18 @@ To run the tests.
 
 # Changelog
 
+## 2.1.7
+
+**Date**: 7th Dec 2012
+
+* Serializers now properly support nullable Foreign Keys.
+* Serializer validation now includes model field validation, such as uniqueness constraints.
+* Support 'true' and 'false' string values for BooleanField.
+* Added pickle support for serialized data.
+* Support `source='dotted.notation'` style for nested serializers.
+* Make `Request.user` settable.
+* Bugfix: Fix `RegexField` to work with `BrowsableAPIRenderer`
+
 ## 2.1.6
 
 **Date**: 23rd Nov 2012
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 91cbb2fc4..5b371d01e 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -4,7 +4,9 @@
 >
 > &mdash; Eric S. Raymond, [The Cathedral and the Bazaar][cite].
 
-## Master
+## 2.1.7
+
+**Date**: 7th Dec 2012
 
 * Serializers now properly support nullable Foreign Keys.
 * Serializer validation now includes model field validation, such as uniqueness constraints.
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 48cebbc5e..da2c5d5cb 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -1,3 +1,3 @@
-__version__ = '2.1.6'
+__version__ = '2.1.7'
 
 VERSION = __version__  # synonym

From c1be29418b7cd8f2b44a7c3273cc79f024fa8c45 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Fri, 7 Dec 2012 23:58:20 +0000
Subject: [PATCH 29/56] Add link to json+hal hypermedia format.

---
 docs/topics/rest-hypermedia-hateoas.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/docs/topics/rest-hypermedia-hateoas.md b/docs/topics/rest-hypermedia-hateoas.md
index d76468922..10ab9dfe7 100644
--- a/docs/topics/rest-hypermedia-hateoas.md
+++ b/docs/topics/rest-hypermedia-hateoas.md
@@ -32,7 +32,7 @@ REST framework also includes [serialization] and [parser]/[renderer] components
 
 ## What REST framework doesn't provide.
 
-What REST framework doesn't do is give you is machine readable hypermedia formats such as [Collection+JSON][collection] or HTML [microformats] by default, or the ability to auto-magically create fully HATEOAS style APIs that include hypermedia-based form descriptions and semantically labelled hyperlinks.  Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope.
+What REST framework doesn't do is give you is machine readable hypermedia formats such as [HAL][hal], [Collection+JSON][collection] or HTML [microformats] by default, or the ability to auto-magically create fully HATEOAS style APIs that include hypermedia-based form descriptions and semantically labelled hyperlinks.  Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope.
 
 [cite]: http://vimeo.com/channels/restfest/page:2
 [dissertation]: http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
@@ -44,6 +44,7 @@ What REST framework doesn't do is give you is machine readable hypermedia format
 [readinglist]: http://blog.steveklabnik.com/posts/2012-02-27-hypermedia-api-reading-list
 [maturitymodel]: http://martinfowler.com/articles/richardsonMaturityModel.html
 
+[hal]: http://stateless.co/hal_specification.html
 [collection]: http://www.amundsen.com/media-types/collection/
 [microformats]: http://microformats.org/wiki/Main_Page
 [serialization]: ../api-guide/serializers.md

From f72be7b8faf4489c904bb6df97d51274872315bb Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Sat, 8 Dec 2012 12:29:35 +0000
Subject: [PATCH 30/56] Add test for m2m create

---
 rest_framework/tests/pk_relations.py | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/rest_framework/tests/pk_relations.py b/rest_framework/tests/pk_relations.py
index 53245d945..3fb3a07b9 100644
--- a/rest_framework/tests/pk_relations.py
+++ b/rest_framework/tests/pk_relations.py
@@ -130,6 +130,25 @@ class PKManyToManyTests(TestCase):
         ]
         self.assertEquals(serializer.data, expected)
 
+    def test_many_to_many_create(self):
+        data = {'id': 4, 'name': u'source-4', 'targets': [1, 3]}
+        serializer = ManyToManySourceSerializer(data=data)
+        self.assertTrue(serializer.is_valid())
+        obj = serializer.save()
+        self.assertEquals(serializer.data, data)
+        self.assertEqual(obj.name, u'source-4')
+
+        # Ensure source 4 is added, and everything else is as expected
+        queryset = ManyToManySource.objects.all()
+        serializer = ManyToManySourceSerializer(queryset)
+        expected = [
+            {'id': 1, 'name': u'source-1', 'targets': [1]},
+            {'id': 2, 'name': u'source-2', 'targets': [1, 2]},
+            {'id': 3, 'name': u'source-3', 'targets': [1, 2, 3]},
+            {'id': 4, 'name': u'source-4', 'targets': [1, 3]},
+        ]
+        self.assertEquals(serializer.data, expected)
+
     def test_reverse_many_to_many_create(self):
         data = {'id': 4, 'name': u'target-4', 'sources': [1, 3]}
         serializer = ManyToManyTargetSerializer(data=data)

From 936fdfb78e6987ce18812fbe3e17e2af8822704e Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Sat, 8 Dec 2012 12:48:27 +0000
Subject: [PATCH 31/56] More tests for nullable FKs

---
 rest_framework/tests/pk_relations.py | 66 ++++++++++++++++++++++++++++
 1 file changed, 66 insertions(+)

diff --git a/rest_framework/tests/pk_relations.py b/rest_framework/tests/pk_relations.py
index 3fb3a07b9..e3360939f 100644
--- a/rest_framework/tests/pk_relations.py
+++ b/rest_framework/tests/pk_relations.py
@@ -232,6 +232,49 @@ class PKNullableForeignKeyTests(TestCase):
             source = NullableForeignKeySource(name='source-%d' % idx, target=target)
             source.save()
 
+    def test_foreign_key_create_with_valid_null(self):
+        data = {'id': 4, 'name': u'source-4', 'target': None}
+        serializer = NullableForeignKeySourceSerializer(data=data)
+        self.assertTrue(serializer.is_valid())
+        obj = serializer.save()
+        self.assertEquals(serializer.data, data)
+        self.assertEqual(obj.name, u'source-4')
+
+        # Ensure source 4 is created, and everything else is as expected
+        queryset = NullableForeignKeySource.objects.all()
+        serializer = NullableForeignKeySourceSerializer(queryset)
+        expected = [
+            {'id': 1, 'name': u'source-1', 'target': 1},
+            {'id': 2, 'name': u'source-2', 'target': 1},
+            {'id': 3, 'name': u'source-3', 'target': 1},
+            {'id': 4, 'name': u'source-4', 'target': None}
+        ]
+        self.assertEquals(serializer.data, expected)
+
+    def test_foreign_key_create_with_valid_emptystring(self):
+        """
+        The emptystring should be interpreted as null in the context
+        of relationships.
+        """
+        data = {'id': 4, 'name': u'source-4', 'target': ''}
+        expected_data = {'id': 4, 'name': u'source-4', 'target': None}
+        serializer = NullableForeignKeySourceSerializer(data=data)
+        self.assertTrue(serializer.is_valid())
+        obj = serializer.save()
+        self.assertEquals(serializer.data, expected_data)
+        self.assertEqual(obj.name, u'source-4')
+
+        # Ensure source 4 is created, and everything else is as expected
+        queryset = NullableForeignKeySource.objects.all()
+        serializer = NullableForeignKeySourceSerializer(queryset)
+        expected = [
+            {'id': 1, 'name': u'source-1', 'target': 1},
+            {'id': 2, 'name': u'source-2', 'target': 1},
+            {'id': 3, 'name': u'source-3', 'target': 1},
+            {'id': 4, 'name': u'source-4', 'target': None}
+        ]
+        self.assertEquals(serializer.data, expected)
+
     def test_foreign_key_update_with_valid_null(self):
         data = {'id': 1, 'name': u'source-1', 'target': None}
         instance = NullableForeignKeySource.objects.get(pk=1)
@@ -250,6 +293,29 @@ class PKNullableForeignKeyTests(TestCase):
         ]
         self.assertEquals(serializer.data, expected)
 
+    def test_foreign_key_update_with_valid_emptystring(self):
+        """
+        The emptystring should be interpreted as null in the context
+        of relationships.
+        """
+        data = {'id': 1, 'name': u'source-1', 'target': ''}
+        expected_data = {'id': 1, 'name': u'source-1', 'target': None}
+        instance = NullableForeignKeySource.objects.get(pk=1)
+        serializer = NullableForeignKeySourceSerializer(instance, data=data)
+        self.assertTrue(serializer.is_valid())
+        self.assertEquals(serializer.data, expected_data)
+        serializer.save()
+
+        # Ensure source 1 is updated, and everything else is as expected
+        queryset = NullableForeignKeySource.objects.all()
+        serializer = NullableForeignKeySourceSerializer(queryset)
+        expected = [
+            {'id': 1, 'name': u'source-1', 'target': None},
+            {'id': 2, 'name': u'source-2', 'target': 1},
+            {'id': 3, 'name': u'source-3', 'target': 1}
+        ]
+        self.assertEquals(serializer.data, expected)
+
     # reverse foreign keys MUST be read_only
     # In the general case they do not provide .remove() or .clear()
     # and cannot be arbitrarily set.

From 733f03fba35cb13ad53723b0b15d439e40da32ad Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Sat, 8 Dec 2012 12:48:38 +0000
Subject: [PATCH 32/56] Fix for emptystring as nullable FK

---
 rest_framework/fields.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index bffc0fb05..c5726ff08 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -351,9 +351,9 @@ class RelatedField(WritableField):
 
         value = data.get(field_name)
 
-        if value is None and not self.blank:
+        if value in (None, '') and not self.blank:
             raise ValidationError('Value may not be null')
-        elif value is None and self.blank:
+        elif value in (None, '') and self.blank:
             into[(self.source or field_name)] = None
         else:
             into[(self.source or field_name)] = self.from_native(value)

From ac2720afcba7be5e937b5f0360a470701861baaa Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Sat, 8 Dec 2012 13:00:49 +0000
Subject: [PATCH 33/56] Add null related field option

---
 rest_framework/fields.py      | 5 +++--
 rest_framework/serializers.py | 4 ++--
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index c5726ff08..285ec9be1 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -269,6 +269,7 @@ class RelatedField(WritableField):
 
     def __init__(self, *args, **kwargs):
         self.queryset = kwargs.pop('queryset', None)
+        self.null = kwargs.pop('null', False)
         super(RelatedField, self).__init__(*args, **kwargs)
         self.read_only = kwargs.pop('read_only', self.default_read_only)
 
@@ -351,9 +352,9 @@ class RelatedField(WritableField):
 
         value = data.get(field_name)
 
-        if value in (None, '') and not self.blank:
+        if value in (None, '') and not self.null:
             raise ValidationError('Value may not be null')
-        elif value in (None, '') and self.blank:
+        elif value in (None, '') and self.null:
             into[(self.source or field_name)] = None
         else:
             into[(self.source or field_name)] = self.from_native(value)
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 13c41a4bd..7eab98606 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -432,7 +432,7 @@ class ModelSerializer(Serializer):
         # TODO: filter queryset using:
         # .using(db).complex_filter(self.rel.limit_choices_to)
         kwargs = {
-            'blank': model_field.blank,
+            'null': model_field.null,
             'queryset': model_field.rel.to._default_manager
         }
 
@@ -577,7 +577,7 @@ class HyperlinkedModelSerializer(ModelSerializer):
         # .using(db).complex_filter(self.rel.limit_choices_to)
         rel = model_field.rel.to
         kwargs = {
-            'blank': model_field.blank,
+            'null': model_field.null,
             'queryset': rel._default_manager,
             'view_name': self._get_default_view_name(rel)
         }

From ff01ae3571298b9da67f9b9583f0cb264676ed2b Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Sat, 8 Dec 2012 13:01:03 +0000
Subject: [PATCH 34/56] Version 2.1.8

---
 README.md                    | 7 +++++++
 docs/api-guide/fields.md     | 3 +++
 docs/topics/release-notes.md | 9 ++++++++-
 rest_framework/__init__.py   | 2 +-
 4 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index a9ea12427..a4c56103a 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,13 @@ To run the tests.
 
 # Changelog
 
+## 2.1.8
+
+**Date**: 8th Dec 2012
+
+* Fix for creating nullable Foreign Keys with `''` as well as `None`.
+* Added `null=<bool>` related field option.
+
 ## 2.1.7
 
 **Date**: 7th Dec 2012
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index 1d4c34cb9..50a09701e 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -293,6 +293,7 @@ By default these fields are read-write, although you can change this behaviour u
 **Arguments**:
 
 * `queryset` - By default `ModelSerializer` classes will use the default queryset for the relationship.  `Serializer` classes must either set a queryset explicitly, or set `read_only=True`.
+* `null` - If set to `True`, the field will accept values of `None` or the emptystring for nullable relationships.
 
 ## SlugRelatedField / ManySlugRelatedField
 
@@ -304,6 +305,7 @@ By default these fields read-write, although you can change this behaviour using
 
 * `slug_field` - The field on the target that should be used to represent it.  This should be a field that uniquely identifies any given instance.  For example, `username`.
 * `queryset` - By default `ModelSerializer` classes will use the default queryset for the relationship.  `Serializer` classes must either set a queryset explicitly, or set `read_only=True`.
+* `null` - If set to `True`, the field will accept values of `None` or the emptystring for nullable relationships.
 
 ## HyperlinkedRelatedField / ManyHyperlinkedRelatedField
 
@@ -319,6 +321,7 @@ By default, `HyperlinkedRelatedField` is read-write, although you can change thi
 * `slug_field` - The field on the target that should be used for the lookup. Default is `'slug'`.
 * `pk_url_kwarg` - The named url parameter for the pk field lookup. Default is `pk`.
 * `slug_url_kwarg` - The named url parameter for the slug field lookup. Default is to use the same value as given for `slug_field`.
+* `null` - If set to `True`, the field will accept values of `None` or the emptystring for nullable relationships.
 
 ## HyperLinkedIdentityField
 
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 5b371d01e..46eb1494d 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -4,6 +4,13 @@
 >
 > &mdash; Eric S. Raymond, [The Cathedral and the Bazaar][cite].
 
+## 2.1.8
+
+**Date**: 8th Dec 2012
+
+* Fix for creating nullable Foreign Keys with `''` as well as `None`.
+* Added `null=<bool>` related field option.
+
 ## 2.1.7
 
 **Date**: 7th Dec 2012
@@ -14,7 +21,7 @@
 * Added pickle support for serialized data.
 * Support `source='dotted.notation'` style for nested serializers.
 * Make `Request.user` settable.
-* Bugfix: Fix `RegexField` to work with `BrowsableAPIRenderer`
+* Bugfix: Fix `RegexField` to work with `BrowsableAPIRenderer`.
 
 ## 2.1.6
 
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index da2c5d5cb..02a606754 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -1,3 +1,3 @@
-__version__ = '2.1.7'
+__version__ = '2.1.8'
 
 VERSION = __version__  # synonym

From d0935d1fbb87711b0ffda8655c44ede29ee4208a Mon Sep 17 00:00:00 2001
From: Marko Tibold <marko@tibold.nl>
Date: Mon, 10 Dec 2012 23:10:04 +0100
Subject: [PATCH 35/56] get_excluded_fieldnames() should respect Meta options'
 ability to be either a tuple or list. Fixes #490. Refactored `if
 self.opt.fields` out of the for loop. Updated and cleaned up the
 validation-tests.

---
 rest_framework/serializers.py      | 6 +++---
 rest_framework/tests/serializer.py | 5 ++++-
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 7eab98606..c3f260c77 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -132,9 +132,9 @@ class BaseSerializer(Field):
         Returns the fieldnames that should not be validated.
         """
         excluded_fields = list(self.opts.exclude)
-        for field in self.fields.keys() + self.get_default_fields().keys():
-            if self.opts.fields:
-                if field not in self.opts.fields + self.opts.exclude:
+        if self.opts.fields:
+            for field in self.fields.keys() + self.get_default_fields().keys():
+                if field not in list(self.opts.fields) + excluded_fields:
                     excluded_fields.append(field)
         return excluded_fields
 
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 455fa270b..a16f6abd8 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -66,6 +66,7 @@ class AlbumsSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = Album
+        fields = ['title']  # lists are also valid options
 
 
 class BasicTests(TestCase):
@@ -282,9 +283,11 @@ class ValidationTests(TestCase):
         self.assertEquals(serializer.is_valid(), False)
         self.assertEquals(serializer.errors, {'info': [u'Ensure this value has at most 12 characters (it has 13).']})
 
+
+class ModelValidationTests(TestCase):
     def test_validate_unique(self):
         """
-        Just check if serializers.ModelSerializer.perform_model_validation() handles unique checks via .full_clean()
+        Just check if serializers.ModelSerializer handles unique checks via .full_clean()
         """
         serializer = AlbumsSerializer(data={'title': 'a'})
         serializer.is_valid()

From 1815cdd24732e8102ccdf7d28cb5f0cc400c7eaf Mon Sep 17 00:00:00 2001
From: Venkat <venkat@ridecell.com>
Date: Mon, 10 Dec 2012 17:46:21 -0800
Subject: [PATCH 36/56] Making sure the assert does not fail  when
 required=False, read_only=True

---
 rest_framework/fields.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 285ec9be1..827459738 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -133,7 +133,7 @@ class WritableField(Field):
         if required is None:
             self.required = not(read_only)
         else:
-            assert not read_only, "Cannot set required=True and read_only=True"
+            assert not (read_only and required), "Cannot set required=True and read_only=True"
             self.required = required
 
         messages = {}

From 80adaecc4307ba802fcb7e45b2f178d2102a41e9 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Tue, 11 Dec 2012 09:04:47 +0000
Subject: [PATCH 37/56] Added @annacoder.  Thanks!

---
 docs/topics/credits.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index dfa1ee0f8..dc98dccba 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -74,6 +74,7 @@ The following people have helped make REST framework great.
 * Reinout van Rees - [reinout]
 * Michael Richards - [justanotherbody]
 * Ben Roberts - [roberts81]
+* Venkata Subramanian Mahalingam - [annacoder]
 
 Many thanks to everyone who's contributed to the project.
 
@@ -183,3 +184,4 @@ To contact the author directly:
 [reinout]: https://github.com/reinout
 [justanotherbody]: https://github.com/justanotherbody
 [roberts81]: https://github.com/roberts81
+[annacoder]: https://github.com/annacoder

From 80f15c598afe138df4170ceb2198484889511d0c Mon Sep 17 00:00:00 2001
From: George Kappel <gkappel@magicfiling.com>
Date: Tue, 11 Dec 2012 09:14:52 -0600
Subject: [PATCH 38/56] Added depth test

---
 rest_framework/runtests/runtests.py |  5 +++++
 rest_framework/tests/serializer.py  | 21 +++++++++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/rest_framework/runtests/runtests.py b/rest_framework/runtests/runtests.py
index 1bd0a5fc8..729ef26ac 100755
--- a/rest_framework/runtests/runtests.py
+++ b/rest_framework/runtests/runtests.py
@@ -5,6 +5,11 @@
 # http://code.djangoproject.com/svn/django/trunk/tests/runtests.py
 import os
 import sys
+"""
+Need to fix sys path so following works without specifically messing with PYTHONPATH
+python ./rest_framework/runtests/runtests.py
+"""
+sys.path.append(os.path.join(os.path.dirname(__file__), "../.."))  
 os.environ['DJANGO_SETTINGS_MODULE'] = 'rest_framework.runtests.settings'
 
 from django.conf import settings
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index a16f6abd8..f80762f0f 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -726,3 +726,24 @@ class SerializerPickleTests(TestCase):
                 model = Person
                 fields = ('name', 'age')
         pickle.dumps(InnerPersonSerializer(Person(name="Noah", age=950)).data)
+
+class DepthTest(TestCase):
+    def test_depth(self):
+        user = Person.objects.create(name="django",age=1)
+        post = BlogPost.objects.create(title="Test blog post", writer=user)
+
+        class PersonSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = Person
+                fields = ("name", "age")
+
+        class BlogPostSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = BlogPost
+                depth = 1
+
+        serializer = BlogPostSerializer(instance=post)
+        expected = {'id': 1, 'title': u'Test blog post',
+                    'writer': {'id': 1, 'name': u'django', 'age':1}}
+
+        self.assertEqual(serializer.data, expected)

From 17b77fc446df29e7708c210eade8369c7babc466 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Tue, 11 Dec 2012 21:07:11 +0000
Subject: [PATCH 39/56] Added @gkrappel.  Thank you!

---
 docs/topics/credits.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index dc98dccba..674c83783 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -75,6 +75,7 @@ The following people have helped make REST framework great.
 * Michael Richards - [justanotherbody]
 * Ben Roberts - [roberts81]
 * Venkata Subramanian Mahalingam - [annacoder]
+* George Kappel - [gkappel]
 
 Many thanks to everyone who's contributed to the project.
 
@@ -185,3 +186,4 @@ To contact the author directly:
 [justanotherbody]: https://github.com/justanotherbody
 [roberts81]: https://github.com/roberts81
 [annacoder]: https://github.com/annacoder
+[gkappel]: https://github.com/gkappel

From 405822330958c5432dde56b07a61b223c03ca4c7 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Tue, 11 Dec 2012 21:07:25 +0000
Subject: [PATCH 40/56] Fix broken nested fields

---
 rest_framework/compat.py           | 10 ++++++++
 rest_framework/fields.py           |  7 ++----
 rest_framework/serializers.py      | 38 +++++++++++++++---------------
 rest_framework/tests/serializer.py | 37 +++++++++++++++++++++--------
 4 files changed, 58 insertions(+), 34 deletions(-)

diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index 09b763681..d4901437d 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -19,6 +19,16 @@ except ImportError:
     import StringIO
 
 
+# Try to import PIL in either of the two ways it can end up installed.
+try:
+    from PIL import Image
+except ImportError:
+    try:
+        import Image
+    except ImportError:
+        Image = None
+
+
 def get_concrete_model(model_cls):
     try:
         return model_cls._meta.concrete_model
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 827459738..75ce1b9f9 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -1056,11 +1056,8 @@ class ImageField(FileField):
         if f is None:
             return None
 
-        # Try to import PIL in either of the two ways it can end up installed.
-        try:
-            from PIL import Image
-        except ImportError:
-            import Image
+        from compat import Image
+        assert Image is not None, 'PIL must be installed for ImageField support'
 
         # We need to get a file object for PIL. We might have a path or we might
         # have to read the data into memory.
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index c3f260c77..ebeb43e83 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -237,7 +237,8 @@ class BaseSerializer(Field):
             except ValidationError as err:
                 self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages)
 
-        # We don't run .validate() because field-validation failed and thus `attrs` may not be complete.
+        # If there are already errors, we don't run .validate() because
+        # field-validation failed and thus `attrs` may not be complete.
         # which in turn can cause inconsistent validation errors.
         if not self._errors:
             try:
@@ -299,17 +300,14 @@ class BaseSerializer(Field):
         Override default so that we can apply ModelSerializer as a nested
         field to relationships.
         """
-
         if self.source:
-            value = obj
             for component in self.source.split('.'):
-                value = getattr(value, component)
-                if is_simple_callable(value):
-                    value = value()
-            obj = value
+                obj = getattr(obj, component)
+                if is_simple_callable(obj):
+                    obj = obj()
         else:
-            value = getattr(obj, field_name)
-            if is_simple_callable(value):
+            obj = getattr(obj, field_name)
+            if is_simple_callable(obj):
                 obj = value()
 
         # If the object has an "all" method, assume it's a relationship
@@ -486,15 +484,10 @@ class ModelSerializer(Serializer):
         except KeyError:
             return ModelField(model_field=model_field, **kwargs)
 
-    def validate(self, attrs):
-        copied_attrs = copy.deepcopy(attrs)
-        restored_object = self.restore_object(copied_attrs, instance=getattr(self, 'object', None))
-        self.perform_model_validation(restored_object)
-        return attrs
-
-    def perform_model_validation(self, restored_object):
-        # Call Django's full_clean() which in turn calls: Model.clean_fields(), Model.clean(), Model.validat_unique()
-        restored_object.full_clean(exclude=list(self.get_excluded_fieldnames()))
+    # def validate(self, attrs):
+    #     restored_object = self.restore_object(attrs, instance=getattr(self, 'object', None))
+    #     restored_object.full_clean(exclude=list(self.get_excluded_fieldnames()))
+    #     return attrs
 
     def restore_object(self, attrs, instance=None):
         """
@@ -517,7 +510,14 @@ class ModelSerializer(Serializer):
         for field in self.opts.model._meta.many_to_many:
             if field.name in attrs:
                 self.m2m_data[field.name] = attrs.pop(field.name)
-        return self.opts.model(**attrs)
+
+        instance = self.opts.model(**attrs)
+        try:
+            instance.full_clean(exclude=list(self.get_excluded_fieldnames()))
+        except ValidationError, err:
+            self._errors = err.message_dict
+            return None
+        return instance
 
     def save(self, save_m2m=True):
         """
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index f80762f0f..50a5f5a48 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -1,4 +1,5 @@
-import datetime, pickle
+import datetime
+import pickle
 from django.test import TestCase
 from rest_framework import serializers
 from rest_framework.tests.models import (Album, ActionItem, Anchor, BasicModel,
@@ -727,15 +728,11 @@ class SerializerPickleTests(TestCase):
                 fields = ('name', 'age')
         pickle.dumps(InnerPersonSerializer(Person(name="Noah", age=950)).data)
 
-class DepthTest(TestCase):
-    def test_depth(self):
-        user = Person.objects.create(name="django",age=1)
-        post = BlogPost.objects.create(title="Test blog post", writer=user)
 
-        class PersonSerializer(serializers.ModelSerializer):
-            class Meta:
-                model = Person
-                fields = ("name", "age")
+class DepthTest(TestCase):
+    def test_implicit_nesting(self):
+        writer = Person.objects.create(name="django", age=1)
+        post = BlogPost.objects.create(title="Test blog post", writer=writer)
 
         class BlogPostSerializer(serializers.ModelSerializer):
             class Meta:
@@ -744,6 +741,26 @@ class DepthTest(TestCase):
 
         serializer = BlogPostSerializer(instance=post)
         expected = {'id': 1, 'title': u'Test blog post',
-                    'writer': {'id': 1, 'name': u'django', 'age':1}}
+                    'writer': {'id': 1, 'name': u'django', 'age': 1}}
+
+        self.assertEqual(serializer.data, expected)
+
+    def test_explicit_nesting(self):
+        writer = Person.objects.create(name="django", age=1)
+        post = BlogPost.objects.create(title="Test blog post", writer=writer)
+
+        class PersonSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = Person
+
+        class BlogPostSerializer(serializers.ModelSerializer):
+            writer = PersonSerializer()
+
+            class Meta:
+                model = BlogPost
+
+        serializer = BlogPostSerializer(instance=post)
+        expected = {'id': 1, 'title': u'Test blog post',
+                    'writer': {'id': 1, 'name': u'django', 'age': 1}}
 
         self.assertEqual(serializer.data, expected)

From 0824761f471ee5130af980acc9fdbb2758a3a92a Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Tue, 11 Dec 2012 21:07:48 +0000
Subject: [PATCH 41/56] Version 2.1.9

---
 README.md                    | 8 ++++++++
 docs/topics/release-notes.md | 8 ++++++++
 rest_framework/__init__.py   | 2 +-
 3 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index a4c56103a..1bc9628f0 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,14 @@ To run the tests.
 
 # Changelog
 
+## 2.1.9
+
+**Date**: 11th Dec 2012
+
+* Bugfix: Fix broken nested serialization.
+* Bugfix: Fix `Meta.fields` only working as tuple not as list.
+* Bugfix: Edge case if unnecessarily specifying `required=False` on read only field.
+
 ## 2.1.8
 
 **Date**: 8th Dec 2012
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 46eb1494d..4f83cfd8f 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -4,6 +4,14 @@
 >
 > &mdash; Eric S. Raymond, [The Cathedral and the Bazaar][cite].
 
+## 2.1.9
+
+**Date**: 11th Dec 2012
+
+* Bugfix: Fix broken nested serialization.
+* Bugfix: Fix `Meta.fields` only working as tuple not as list.
+* Bugfix: Edge case if unnecessarily specifying `required=False` on read only field.
+
 ## 2.1.8
 
 **Date**: 8th Dec 2012
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 02a606754..83a6f3022 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -1,3 +1,3 @@
-__version__ = '2.1.8'
+__version__ = '2.1.9'
 
 VERSION = __version__  # synonym

From 85bf4164ddef2ad6d2f58457d6621cb807ab4d29 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Tue, 11 Dec 2012 22:09:04 +0000
Subject: [PATCH 42/56] Drop left over code

---
 rest_framework/serializers.py | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index ebeb43e83..5465d7b72 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -484,11 +484,6 @@ class ModelSerializer(Serializer):
         except KeyError:
             return ModelField(model_field=model_field, **kwargs)
 
-    # def validate(self, attrs):
-    #     restored_object = self.restore_object(attrs, instance=getattr(self, 'object', None))
-    #     restored_object.full_clean(exclude=list(self.get_excluded_fieldnames()))
-    #     return attrs
-
     def restore_object(self, attrs, instance=None):
         """
         Restore the model instance.

From 9188d487c3a0465b2a3e0d1c47f76c3df844b7d0 Mon Sep 17 00:00:00 2001
From: Colin Murtaugh <cmurtaugh@gmail.com>
Date: Tue, 11 Dec 2012 17:26:08 -0500
Subject: [PATCH 43/56] Replaced SingleObjectBaseView with SingleObjectAPIView

---
 docs/tutorial/3-class-based-views.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md
index a3a18060f..b115b0229 100644
--- a/docs/tutorial/3-class-based-views.md
+++ b/docs/tutorial/3-class-based-views.md
@@ -109,7 +109,7 @@ The base class provides the core functionality, and the mixin classes provide th
     class SnippetDetail(mixins.RetrieveModelMixin,
                         mixins.UpdateModelMixin,
                         mixins.DestroyModelMixin,
-                        generics.SingleObjectBaseView):
+                        generics.SingleObjectAPIView):
         model = Snippet
         serializer_class = SnippetSerializer
 
@@ -122,7 +122,7 @@ The base class provides the core functionality, and the mixin classes provide th
         def delete(self, request, *args, **kwargs):
             return self.destroy(request, *args, **kwargs)
 
-Pretty similar.  This time we're using the `SingleObjectBaseView` class to provide the core functionality, and adding in mixins to provide the `.retrieve()`, `.update()` and `.destroy()` actions.
+Pretty similar.  This time we're using the `SingleObjectAPIView` class to provide the core functionality, and adding in mixins to provide the `.retrieve()`, `.update()` and `.destroy()` actions.
 
 ## Using generic class based views
 

From 628e3bf001ca71da48a6f3c7bbdf209f2e20b223 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Gro=C3=9F?= <stephan@minddust.com>
Date: Wed, 12 Dec 2012 08:59:19 +0100
Subject: [PATCH 44/56] Added @cmurtaugh. Thanks!

---
 docs/topics/credits.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index 674c83783..cdf57f7ec 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -76,6 +76,7 @@ The following people have helped make REST framework great.
 * Ben Roberts - [roberts81]
 * Venkata Subramanian Mahalingam - [annacoder]
 * George Kappel - [gkappel]
+* Colin Murtaugh - [cmurtaugh]
 
 Many thanks to everyone who's contributed to the project.
 
@@ -187,3 +188,4 @@ To contact the author directly:
 [roberts81]: https://github.com/roberts81
 [annacoder]: https://github.com/annacoder
 [gkappel]: https://github.com/gkappel
+[cmurtaugh]: https://github.com/cmurtaugh

From 497da7fc699b9e88c966e37bc48739865336683d Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Wed, 12 Dec 2012 20:44:55 +0000
Subject: [PATCH 45/56] Clean up field initialization.  Fixes #497

---
 rest_framework/serializers.py      | 16 +++++++--
 rest_framework/tests/serializer.py | 54 +++++++++++++++++++++++++++++-
 2 files changed, 66 insertions(+), 4 deletions(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 5465d7b72..caa7c980f 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -100,7 +100,8 @@ class BaseSerializer(Field):
     _options_class = SerializerOptions
     _dict_class = SortedDictWithMetadata  # Set to unsorted dict for backwards compatibility with unsorted implementations.
 
-    def __init__(self, instance=None, data=None, files=None, context=None, partial=False, **kwargs):
+    def __init__(self, instance=None, data=None, files=None,
+                 context=None, partial=False, **kwargs):
         super(BaseSerializer, self).__init__(**kwargs)
         self.opts = self._options_class(self.Meta)
         self.parent = None
@@ -151,8 +152,6 @@ class BaseSerializer(Field):
         base_fields = copy.deepcopy(self.base_fields)
         for key, field in base_fields.items():
             ret[key] = field
-            # Set up the field
-            field.initialize(parent=self, field_name=key)
 
         # Add in the default fields
         default_fields = self.get_default_fields()
@@ -172,6 +171,10 @@ class BaseSerializer(Field):
             for key in self.opts.exclude:
                 ret.pop(key, None)
 
+        # Initialize the fields
+        for key, field in ret.items():
+            field.initialize(parent=self, field_name=key)
+
         return ret
 
     #####
@@ -186,6 +189,13 @@ class BaseSerializer(Field):
         if parent.opts.depth:
             self.opts.depth = parent.opts.depth - 1
 
+        # We need to call initialize here to ensure any nested
+        # serializers that will have already called initialize on their
+        # descendants get updated with *their* parent.
+        # We could be a bit more smart about this, but it'll do for now.
+        for key, field in self.fields.items():
+            field.initialize(parent=self, field_name=key)
+
     #####
     # Methods to convert or revert from objects <--> primitive representations.
 
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 50a5f5a48..780177aa0 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -4,7 +4,7 @@ from django.test import TestCase
 from rest_framework import serializers
 from rest_framework.tests.models import (Album, ActionItem, Anchor, BasicModel,
     BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel,
-    ManyToManyModel, Person, ReadOnlyManyToManyModel)
+    ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo)
 
 
 class SubComment(object):
@@ -764,3 +764,55 @@ class DepthTest(TestCase):
                     'writer': {'id': 1, 'name': u'django', 'age': 1}}
 
         self.assertEqual(serializer.data, expected)
+
+
+class NestedSerializerContextTests(TestCase):
+
+    def test_nested_serializer_context(self):
+        """
+        Regression for #497
+
+        https://github.com/tomchristie/django-rest-framework/issues/497
+        """
+        class PhotoSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = Photo
+                fields = ("description", "callable")
+
+            callable = serializers.SerializerMethodField('_callable')
+
+            def _callable(self, instance):
+                if not 'context_item' in self.context:
+                    raise RuntimeError("context isn't getting passed into 2nd level nested serializer")
+                return "success"
+
+        class AlbumSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = Album
+                fields = ("photo_set", "callable")
+
+            photo_set = PhotoSerializer(source="photo_set")
+            callable = serializers.SerializerMethodField("_callable")
+
+            def _callable(self, instance):
+                if not 'context_item' in self.context:
+                    raise RuntimeError("context isn't getting passed into 1st level nested serializer")
+                return "success"
+
+        class AlbumCollection(object):
+            albums = None
+
+        class AlbumCollectionSerializer(serializers.Serializer):
+            albums = AlbumSerializer(source="albums")
+
+        album1 = Album.objects.create(title="album 1")
+        album2 = Album.objects.create(title="album 2")
+        Photo.objects.create(description="Bigfoot", album=album1)
+        Photo.objects.create(description="Unicorn", album=album1)
+        Photo.objects.create(description="Yeti", album=album2)
+        Photo.objects.create(description="Sasquatch", album=album2)
+        album_collection = AlbumCollection()
+        album_collection.albums = [album1, album2]
+
+        # This will raise RuntimeError if context doesn't get passed correctly to the nested Serializers
+        AlbumCollectionSerializer(album_collection, context={'context_item': 'album context'}).data

From 5f08ec70e24ee3bdf74eed62def2fd9108af0eb0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Szymon=20Te=C5=BCewski?= <jasisz@gmail.com>
Date: Thu, 13 Dec 2012 12:07:56 +0100
Subject: [PATCH 46/56] context to custom field in pagination

---
 rest_framework/pagination.py       |  2 +-
 rest_framework/tests/pagination.py | 31 +++++++++++++++++++++++++++++-
 2 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index d241ade7c..7d7bb647d 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -62,7 +62,7 @@ class BasePaginationSerializer(serializers.Serializer):
         super(BasePaginationSerializer, self).__init__(*args, **kwargs)
         results_field = self.results_field
         object_serializer = self.opts.object_serializer_class
-        self.fields[results_field] = object_serializer(source='object_list')
+        self.fields[results_field] = object_serializer(source='object_list', **kwargs)
 
     def to_native(self, obj):
         """
diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py
index 3062007d4..9c34001de 100644
--- a/rest_framework/tests/pagination.py
+++ b/rest_framework/tests/pagination.py
@@ -4,7 +4,7 @@ from django.core.paginator import Paginator
 from django.test import TestCase
 from django.test.client import RequestFactory
 from django.utils import unittest
-from rest_framework import generics, status, pagination, filters
+from rest_framework import generics, status, pagination, filters, serializers
 from rest_framework.compat import django_filters
 from rest_framework.tests.models import BasicModel, FilterableItem
 
@@ -236,3 +236,32 @@ class TestCustomPaginateByParam(TestCase):
         response = self.view(request).render()
         self.assertEquals(response.data['count'], 13)
         self.assertEquals(response.data['results'], self.data[:5])
+
+
+class CustomField(serializers.Field):
+    def to_native(self, value):
+        if not 'view' in self.context:
+            raise RuntimeError("context isn't getting passed into custom field")
+        return "value"
+
+
+class BasicModelSerializer(serializers.Serializer):
+    text = CustomField()
+
+
+class TestContextPassedToCustomField(TestCase):
+    def setUp(self):
+        BasicModel.objects.create(text='ala ma kota')
+
+    def test_with_pagination(self):
+        class ListView(generics.ListCreateAPIView):
+            model = BasicModel
+            serializer_class = BasicModelSerializer
+            paginate_by = 1
+
+        self.view = ListView.as_view()
+        request = factory.get('/')
+        response = self.view(request).render()
+
+        self.assertEquals(response.status_code, status.HTTP_200_OK)
+

From 54d9cd4dba6b207fc8debaba1eda4d330c22e693 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Gro=C3=9F?= <stephan@minddust.com>
Date: Thu, 13 Dec 2012 16:44:45 +0100
Subject: [PATCH 47/56] fixed validationerror usage

---
 rest_framework/fields.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 75ce1b9f9..da588082c 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -581,7 +581,7 @@ class HyperlinkedRelatedField(RelatedField):
         except:
             pass
 
-        raise ValidationError('Could not resolve URL for field using view name "%s"', view_name)
+        raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name)
 
     def from_native(self, value):
         # Convert URL -> model instance pk
@@ -680,7 +680,7 @@ class HyperlinkedIdentityField(Field):
         except:
             pass
 
-        raise ValidationError('Could not resolve URL for field using view name "%s"', view_name)
+        raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name)
 
 
 ##### Typed Fields #####

From 19da42822a72d4f6c65a46ff282c736a9ca8d4ea Mon Sep 17 00:00:00 2001
From: Marko Tibold <marko@tibold.nl>
Date: Thu, 13 Dec 2012 23:14:45 +0100
Subject: [PATCH 48/56] Update yaml envs to latest Django

---
 .travis.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index ccfdeacbf..0dc878373 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,8 +6,8 @@ python:
 
 env:
   - DJANGO=https://github.com/django/django/zipball/master
-  - DJANGO=django==1.4.1 --use-mirrors
-  - DJANGO=django==1.3.3 --use-mirrors
+  - DJANGO=django==1.4.3 --use-mirrors
+  - DJANGO=django==1.3.5 --use-mirrors
 
 install:
   - pip install $DJANGO

From 9eaf8e4330e0c6a4485dba650481a2578a3979b4 Mon Sep 17 00:00:00 2001
From: Simon Pantzare <simon@pewpewlabs.com>
Date: Thu, 13 Dec 2012 17:57:27 +0100
Subject: [PATCH 49/56] Test to verify that context is passed on

The paginator and its object serializer should share the same context.
---
 rest_framework/tests/pagination.py | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py
index 3062007d4..7bc23f1d2 100644
--- a/rest_framework/tests/pagination.py
+++ b/rest_framework/tests/pagination.py
@@ -4,7 +4,7 @@ from django.core.paginator import Paginator
 from django.test import TestCase
 from django.test.client import RequestFactory
 from django.utils import unittest
-from rest_framework import generics, status, pagination, filters
+from rest_framework import generics, status, pagination, filters, serializers
 from rest_framework.compat import django_filters
 from rest_framework.tests.models import BasicModel, FilterableItem
 
@@ -148,6 +148,12 @@ class IntegrationTestPaginationAndFiltering(TestCase):
         self.assertEquals(response.data['previous'], None)
 
 
+class PassOnContextPaginationSerializer(pagination.PaginationSerializer):
+
+    class Meta:
+        object_serializer_class = serializers.Serializer
+
+
 class UnitTestPagination(TestCase):
     """
     Unit tests for pagination of primitive objects.
@@ -172,6 +178,12 @@ class UnitTestPagination(TestCase):
         self.assertEquals(serializer.data['previous'], '?page=2')
         self.assertEquals(serializer.data['results'], self.objects[20:])
 
+    def test_context_available_in_result(self):
+        serializer = PassOnContextPaginationSerializer(self.first_page)
+        results = serializer.fields[serializer.results_field]
+        # assertIs is available in Python 2.7
+        self.assertTrue(serializer.context is results.context)
+
 
 class TestUnpaginated(TestCase):
     """

From 39b01d6802310a90487f217c3de5428144c93429 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Fri, 14 Dec 2012 19:59:21 +0000
Subject: [PATCH 50/56] Ensure context is passed to dynamically added fields.
 Fixes #476.

---
 rest_framework/serializers.py      | 7 ++-----
 rest_framework/tests/pagination.py | 6 ++++--
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index caa7c980f..8026205e5 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -171,10 +171,6 @@ class BaseSerializer(Field):
             for key in self.opts.exclude:
                 ret.pop(key, None)
 
-        # Initialize the fields
-        for key, field in ret.items():
-            field.initialize(parent=self, field_name=key)
-
         return ret
 
     #####
@@ -214,6 +210,7 @@ class BaseSerializer(Field):
         ret.fields = {}
 
         for field_name, field in self.fields.items():
+            field.initialize(parent=self, field_name=field_name)
             key = self.get_field_key(field_name)
             value = field.field_to_native(obj, field_name)
             ret[key] = value
@@ -227,6 +224,7 @@ class BaseSerializer(Field):
         """
         reverted_data = {}
         for field_name, field in self.fields.items():
+            field.initialize(parent=self, field_name=field_name)
             try:
                 field.field_from_native(data, files, field_name, reverted_data)
             except ValidationError as err:
@@ -407,7 +405,6 @@ class ModelSerializer(Serializer):
                 field = self.get_field(model_field)
 
             if field:
-                field.initialize(parent=self, field_name=model_field.name)
                 ret[model_field.name] = field
 
         for field_name in self.opts.read_only_fields:
diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py
index 7bc23f1d2..3c65e9db6 100644
--- a/rest_framework/tests/pagination.py
+++ b/rest_framework/tests/pagination.py
@@ -149,7 +149,6 @@ class IntegrationTestPaginationAndFiltering(TestCase):
 
 
 class PassOnContextPaginationSerializer(pagination.PaginationSerializer):
-
     class Meta:
         object_serializer_class = serializers.Serializer
 
@@ -179,9 +178,12 @@ class UnitTestPagination(TestCase):
         self.assertEquals(serializer.data['results'], self.objects[20:])
 
     def test_context_available_in_result(self):
+        """
+        Ensure context gets passed through to the object serializer.
+        """
         serializer = PassOnContextPaginationSerializer(self.first_page)
+        serializer.data
         results = serializer.fields[serializer.results_field]
-        # assertIs is available in Python 2.7
         self.assertTrue(serializer.context is results.context)
 
 

From 6f8b432677f001c46128ed6f20b2efbcf2a70feb Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Fri, 14 Dec 2012 20:08:43 +0000
Subject: [PATCH 51/56] Added @pilt.  Thanks!

---
 docs/topics/credits.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index cdf57f7ec..e0bb366b6 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -77,6 +77,7 @@ The following people have helped make REST framework great.
 * Venkata Subramanian Mahalingam - [annacoder]
 * George Kappel - [gkappel]
 * Colin Murtaugh - [cmurtaugh]
+* Simon Pantzare - [pilt]
 
 Many thanks to everyone who's contributed to the project.
 
@@ -189,3 +190,4 @@ To contact the author directly:
 [annacoder]: https://github.com/annacoder
 [gkappel]: https://github.com/gkappel
 [cmurtaugh]: https://github.com/cmurtaugh
+[pilt]: https://github.com/pilt

From e9eb47207a7e599c09d9eda4e2f9adfe03ef4542 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Fri, 14 Dec 2012 20:08:53 +0000
Subject: [PATCH 52/56] Update release notes.

---
 docs/topics/release-notes.md | 74 ++++++++++++++++++++++++------------
 1 file changed, 49 insertions(+), 25 deletions(-)

diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 4f83cfd8f..4ea393afe 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -4,7 +4,13 @@
 >
 > &mdash; Eric S. Raymond, [The Cathedral and the Bazaar][cite].
 
-## 2.1.9
+## 2.1.x series
+
+### Master
+
+* Bugfix: Fix hyperlinked fields in paginated results.
+
+### 2.1.9
 
 **Date**: 11th Dec 2012
 
@@ -12,14 +18,14 @@
 * Bugfix: Fix `Meta.fields` only working as tuple not as list.
 * Bugfix: Edge case if unnecessarily specifying `required=False` on read only field.
 
-## 2.1.8
+### 2.1.8
 
 **Date**: 8th Dec 2012
 
 * Fix for creating nullable Foreign Keys with `''` as well as `None`.
 * Added `null=<bool>` related field option.
 
-## 2.1.7
+### 2.1.7
 
 **Date**: 7th Dec 2012
 
@@ -31,19 +37,19 @@
 * Make `Request.user` settable.
 * Bugfix: Fix `RegexField` to work with `BrowsableAPIRenderer`.
 
-## 2.1.6
+### 2.1.6
 
 **Date**: 23rd Nov 2012
 
 * Bugfix: Unfix DjangoModelPermissions.  (I am a doofus.)
 
-## 2.1.5
+### 2.1.5
 
 **Date**: 23rd Nov 2012
 
 * Bugfix: Fix DjangoModelPermissions.
 
-## 2.1.4
+### 2.1.4
 
 **Date**: 22nd Nov 2012
 
@@ -54,7 +60,7 @@
 * Added `obtain_token_view` to get tokens when using `TokenAuthentication`.
 * Bugfix: Django 1.5 configurable user support for `TokenAuthentication`.
 
-## 2.1.3
+### 2.1.3
 
 **Date**: 16th Nov 2012
 
@@ -65,14 +71,14 @@
 * 201 Responses now return a 'Location' header.
 * Bugfix: Serializer fields now respect `max_length`.
 
-## 2.1.2
+### 2.1.2
 
 **Date**: 9th Nov 2012
 
 * **Filtering support.**
 * Bugfix: Support creation of objects with reverse M2M relations.
 
-## 2.1.1
+### 2.1.1
 
 **Date**: 7th Nov 2012
 
@@ -82,7 +88,7 @@
 * Bugfix: Make textareas same width as other fields in browsable API.
 * Private API change: `.get_serializer` now uses same `instance` and `data` ordering as serializer initialization.
 
-## 2.1.0
+### 2.1.0
 
 **Date**: 5th Nov 2012
 
@@ -96,13 +102,17 @@
 * Bugfix: Support choice field in Browseable API.
 * Bugfix: Related fields with `read_only=True` do not require a `queryset` argument.
 
-## 2.0.2
+---
+
+## 2.0.x series
+
+### 2.0.2
 
 **Date**: 2nd Nov 2012
 
 * Fix issues with pk related fields in the browsable API.
 
-## 2.0.1
+### 2.0.1
 
 **Date**: 1st Nov 2012
 
@@ -110,7 +120,7 @@
 * Added SlugRelatedField and ManySlugRelatedField.
 * If PUT creates an instance return '201 Created', instead of '200 OK'.
 
-## 2.0.0
+### 2.0.0
 
 **Date**: 30th Oct 2012
 
@@ -119,7 +129,9 @@
 
 ---
 
-## 0.4.0
+## 0.4.x series
+
+### 0.4.0
 
 * Supports Django 1.5.
 * Fixes issues with 'HEAD' method.
@@ -131,7 +143,11 @@
 * Improve setup (eg use staticfiles, not the defunct ADMIN_MEDIA_PREFIX)
 * Sensible absolute URL generation, not using hacky set_script_prefix
 
-## 0.3.3
+---
+
+## 0.3.x series
+
+### 0.3.3
 
 * Added DjangoModelPermissions class to support `django.contrib.auth` style permissions.
 * Use `staticfiles` for css files.
@@ -146,7 +162,7 @@
 * Bugfixes:
   - Bug with PerUserThrottling when user contains unicode chars.
 
-## 0.3.2
+### 0.3.2
 
 * Bugfixes:
   * Fix 403 for POST and PUT from the UI with UserLoggedInAuthentication (#115)
@@ -158,37 +174,41 @@
 * get_name, get_description become methods on the view - makes them overridable.
 * Improved model mixin API - Hooks for build_query, get_instance_data, get_model, get_queryset, get_ordering
 
-## 0.3.1
+### 0.3.1
 
 * [not documented]
 
-## 0.3.0
+### 0.3.0
 
 * JSONP Support
 * Bugfixes, including support for latest markdown release
 
-## 0.2.4
+---
+
+## 0.2.x series
+
+### 0.2.4
 
 * Fix broken IsAdminUser permission.
 * OPTIONS support.
 * XMLParser.
 * Drop mentions of Blog, BitBucket.
 
-## 0.2.3
+### 0.2.3
 
 * Fix some throttling bugs.
 * ``X-Throttle`` header on throttling.
 * Support for nesting resources on related models.
 
-## 0.2.2
+### 0.2.2
 
 * Throttling support complete.
 
-## 0.2.1
+### 0.2.1
 
 * Couple of simple bugfixes over 0.2.0
 
-## 0.2.0
+### 0.2.0
 
 * Big refactoring changes since 0.1.0, ask on the discussion group if anything isn't clear.
   The public API has been massively cleaned up.  Expect it to be fairly stable from here on in.
@@ -212,11 +232,15 @@
 * The mixin classes have been nicely refactored, the basic mixins are now ``RequestMixin``, ``ResponseMixin``, ``AuthMixin``, and ``ResourceMixin``
   You can reuse these mixin classes individually without using the ``View`` class.
 
-## 0.1.1
+---
+
+## 0.1.x series
+
+### 0.1.1
 
 * Final build before pulling in all the refactoring changes for 0.2, in case anyone needs to hang on to 0.1.
 
-## 0.1.0
+### 0.1.0
 
 * Initial release.
 

From 65f7aa021450f86eaa7d9ac1a061138a71d20737 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Fri, 14 Dec 2012 20:12:50 +0000
Subject: [PATCH 53/56] Drop unneeded passing through of kwargs now context
 issue is resolved.

---
 rest_framework/pagination.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py
index 7d7bb647d..d241ade7c 100644
--- a/rest_framework/pagination.py
+++ b/rest_framework/pagination.py
@@ -62,7 +62,7 @@ class BasePaginationSerializer(serializers.Serializer):
         super(BasePaginationSerializer, self).__init__(*args, **kwargs)
         results_field = self.results_field
         object_serializer = self.opts.object_serializer_class
-        self.fields[results_field] = object_serializer(source='object_list', **kwargs)
+        self.fields[results_field] = object_serializer(source='object_list')
 
     def to_native(self, obj):
         """

From 1d24d1fc5928d32372e700907aa71cf887b16ba9 Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Fri, 14 Dec 2012 20:14:42 +0000
Subject: [PATCH 54/56] Added @sunscrapers.  Thanks!

---
 docs/topics/credits.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index e0bb366b6..ba37ce119 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -78,6 +78,7 @@ The following people have helped make REST framework great.
 * George Kappel - [gkappel]
 * Colin Murtaugh - [cmurtaugh]
 * Simon Pantzare - [pilt]
+* Szymon Teżewski - [sunscrapers]
 
 Many thanks to everyone who's contributed to the project.
 
@@ -191,3 +192,4 @@ To contact the author directly:
 [gkappel]: https://github.com/gkappel
 [cmurtaugh]: https://github.com/cmurtaugh
 [pilt]: https://github.com/pilt
+[sunscrapers]: https://github.com/sunscrapers

From 35f72cecb199e1790a42fadd6037da43cdd5a05a Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Sat, 15 Dec 2012 20:40:41 +0000
Subject: [PATCH 55/56] Fix model validation exclusions.  Fixes #500.  Fixes
 #506.

---
 docs/topics/release-notes.md   |  1 +
 rest_framework/fields.py       |  1 +
 rest_framework/serializers.py  | 25 +++++++++++++------------
 rest_framework/tests/models.py |  2 +-
 4 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 4ea393afe..6d7dc3485 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -8,6 +8,7 @@
 
 ### Master
 
+* Bugfix: Ensure read-only fields don't have model validation applied.
 * Bugfix: Fix hyperlinked fields in paginated results.
 
 ### 2.1.9
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index da588082c..d3ef8f779 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -32,6 +32,7 @@ def is_simple_callable(obj):
 
 
 class Field(object):
+    read_only = True
     creation_counter = 0
     empty = ''
     type_name = None
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 8026205e5..1d93f7775 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -128,17 +128,6 @@ class BaseSerializer(Field):
         """
         return {}
 
-    def get_excluded_fieldnames(self):
-        """
-        Returns the fieldnames that should not be validated.
-        """
-        excluded_fields = list(self.opts.exclude)
-        if self.opts.fields:
-            for field in self.fields.keys() + self.get_default_fields().keys():
-                if field not in list(self.opts.fields) + excluded_fields:
-                    excluded_fields.append(field)
-        return excluded_fields
-
     def get_fields(self):
         """
         Returns the complete set of fields for the object as a dict.
@@ -491,6 +480,18 @@ class ModelSerializer(Serializer):
         except KeyError:
             return ModelField(model_field=model_field, **kwargs)
 
+    def get_validation_exclusions(self):
+        """
+        Return a list of field names to exclude from model validation.
+        """
+        cls = self.opts.model
+        opts = get_concrete_model(cls)._meta
+        exclusions = [field.name for field in opts.fields + opts.many_to_many]
+        for field_name, field in self.fields.items():
+            if field_name in exclusions and not field.read_only:
+                exclusions.remove(field_name)
+        return exclusions
+
     def restore_object(self, attrs, instance=None):
         """
         Restore the model instance.
@@ -515,7 +516,7 @@ class ModelSerializer(Serializer):
 
         instance = self.opts.model(**attrs)
         try:
-            instance.full_clean(exclude=list(self.get_excluded_fieldnames()))
+            instance.full_clean(exclude=self.get_validation_exclusions())
         except ValidationError, err:
             self._errors = err.message_dict
             return None
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
index 428bf130d..e2b287d06 100644
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -61,7 +61,7 @@ class BasicModel(RESTFrameworkModel):
 
 class SlugBasedModel(RESTFrameworkModel):
     text = models.CharField(max_length=100)
-    slug = models.SlugField(max_length=32, blank=True)
+    slug = models.SlugField(max_length=32)
 
 
 class DefaultValueModel(RESTFrameworkModel):

From 70714c234630cd205ed88686ece3b594f387a48f Mon Sep 17 00:00:00 2001
From: Tom Christie <tom@tomchristie.com>
Date: Mon, 17 Dec 2012 09:08:28 +0000
Subject: [PATCH 56/56] Version 2.1.10

---
 README.md                    | 7 +++++++
 docs/topics/release-notes.md | 4 +++-
 rest_framework/__init__.py   | 2 +-
 3 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 1bc9628f0..1d49d3be6 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,13 @@ To run the tests.
 
 # Changelog
 
+## 2.1.10
+
+**Date**: 17th Dec 2012
+
+* Bugfix: Ensure read-only fields don't have model validation applied.
+* Bugfix: Fix hyperlinked fields in paginated results.
+
 ## 2.1.9
 
 **Date**: 11th Dec 2012
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 6d7dc3485..66d6f7f38 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -6,7 +6,9 @@
 
 ## 2.1.x series
 
-### Master
+### 2.1.10
+
+**Date**: 17th Dec 2012
 
 * Bugfix: Ensure read-only fields don't have model validation applied.
 * Bugfix: Fix hyperlinked fields in paginated results.
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 83a6f3022..d61632bcf 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -1,3 +1,3 @@
-__version__ = '2.1.9'
+__version__ = '2.1.10'
 
 VERSION = __version__  # synonym