Repository: encode/django-rest-framework

Issue:
model serializer's 'id' field returns null
re-posted from stackoverflow (http://stackoverflow.com/questions/18886171/django-rest-framework-serializer-datas-id-field-returns-null/18895869?noredirect=1#18895869)

Here's a failing test case

**models.py**

```
class SomeModel(models.Model):
  field = models.CharField(max_length=100)
```

**serializers.py**

```
class SomeModelSerializer(serializers.ModelSerializer):
  class Meta:
    model = SomeModel
    fields = ('id', 'field',)
```

The error happens in the view, where serializer.data['id'] is null:

**views.py**

```
... within an user-defined subclass of APIView ...
serializer = SomeModelSerializer(data=request.DATA)
if serializer.is_valid():
  some_dict = {'hello': serializer.data['field']}
  serializer.save()
  return Response(serializer.data, status=status.HTTP_201_CREATED)
else: <do something else>
```



Discussion:
Ok. I'll look at this.

@fangsterr if you could zip up a demo project with this code that'd speed me up. ;-)

@fangsterr I realise there's not enough info here. Please post a full version of the APIView subclass. Thanks

The reason for this will be that you're accessing `request.data` before calling `serializer.save()` - that'll mean it's being populated with a serialized representation of the instance, before it's been saved, so `.id` will be `None`.

Just put your `some_dict` statement after the `save()`.

Not sure if we should consider this a bug, a documentation bug, or a non-issue.

Of course! 

I'm inclined to say non-issue — unless it's the sort of thing that comes up a lot — in which case Docs. 

---

With this view with it's easily seen:

```
@api_view(['GET', 'POST'])
def model_list(request):

    if request.method == 'GET':
        our_models = SomeModel.objects.all()
        serializer = SomeModelSerializer(our_models, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = SomeModelSerializer(data=request.DATA)
        if serializer.is_valid():
            some_dict = {'field': serializer.data['field']}
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
```

The view works as expected though (i.e. the items are written to the DB etc). 

In fact I think perhaps not. 

It would be easy enough to clear the _data attribute on save()... Perhaps we should do that. (It's a edge case but the cost in the normal run of things is basically nil.)

@tomchristie I'm happy to write a test for that and make the change if you like.

> clear the _data attribute on save()

Yeah seems reasonable.  As it happens you'd still be incurring extra cost with this style, by forcing the serialization to run twice, but at least there'd be no functional issues with the code then.

On 20 Sep 2013, at 10:47, Tom Christie notifications@github.com wrote:

> As it happens you'd still be incurring extra cost with this style, by forcing the serialization to run twice

Yeah — but if you're going to do funny things then that's up to you. ;-)

I meant there'd be no performance cost for "normal use" — where I'm defining that as not accessing the data both before and after save. 

I'll put a pull request together for this.


Resolving patch:
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -518,6 +518,9 @@ def save(self, **kwargs):
         """
         Save the deserialized object and return it.
         """
+        # Clear cached _data, which may be invalidated by `save()`
+        self._data = None
+
         if isinstance(self.object, list):
             [self.save_object(item, **kwargs) for item in self.object]
 


Test patch:
diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py
--- a/rest_framework/tests/test_serializer.py
+++ b/rest_framework/tests/test_serializer.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from django.db import models
 from django.db.models.fields import BLANK_CHOICE_DASH
@@ -136,6 +137,7 @@ def setUp(self):
             'Happy new year!',
             datetime.datetime(2012, 1, 1)
         )
+        self.actionitem = ActionItem(title='Some to do item',)
         self.data = {
             'email': 'tom@example.com',
             'content': 'Happy new year!',
@@ -264,6 +266,20 @@ def test_invalid_read_only_fields(self):
         """
         self.assertRaises(AssertionError, PersonSerializerInvalidReadOnly, [])
 
+    def test_serializer_data_is_cleared_on_save(self):
+        """
+        Check _data attribute is cleared on `save()`
+
+        Regression test for #1116
+            — id field is not populated if `data` is accessed prior to `save()`
+        """
+        serializer = ActionItemSerializer(self.actionitem)
+        self.assertIsNone(serializer.data.get('id',None), 'New instance. `id` should not be set.')
+        serializer.save()
+        self.assertIsNotNone(serializer.data.get('id',None), 'Model is saved. `id` should be set.')
+
+
+
 
 class DictStyleSerializer(serializers.Serializer):
     """