Repository: encode/django-rest-framework

Issue:
"TypeError: '*' is an invalid keyword argument for this function" in nested serializer
I have a nested serializer that looks like:

``` python
classs FooSerializer(serializers.Serializer):
    field_1 = serializers.Field('method1')
    field_2 = serializers.Field('method2')
```

And I use it like this:

``` python
class MyMainSerializer(serializer.ModelSerializer):
    foo = FooSerializer(source='*')
```

And testing it against master, though a CreateView I get:

``` python
TypeError: '*' is an invalid keyword argument for this function
```

By debugging I found the error is raised here:
https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py#L45

I git checkout backwards and it seems to begin happen after this commit:
https://github.com/tomchristie/django-rest-framework/commit/9bf7c9b714713f7b2fe84074cfd05a8bc3ef4022#L2L212

I couldn't reproduce it from python shell. Any ideas?



Discussion:
I'll try to reproduce this bug.
Maybe it's a silly question, but why using `source='*'` ?

Related to #694 - May be good for someone to try picking that one off first, then tackle this.

Let me add more context to clarify the issue. First one use case that was not what motivated me to open it.

Suppose I have a Place model [0] and for some reason™ I want some of it's fields to be exposed in the api as nested.
My first try for this was using a ModelSerializer [1], using worked well for read, but for write, it gives me the TypeError above.

[0] https://bitbucket.org/fernandogrd/foo/src/d4ea5d999f37fe35fde255e64a90c2fbb09898be/places/models.py?at=default
[1] https://bitbucket.org/fernandogrd/foo/src/d4ea5d999f37/places/serializers.py?at=default#cl-7

It may seems obvious to get some kind of error in that case, since docs says it is a read only serializer. What gets me to the kind of thing that made me create the issue.

My serializer was similar to the example app (it has fields from the model of root serializer, and some user model), but it was in fact read_only, I guess it fails in newer versions because I didn't make it read_only explicit (I couldn't reproduce it when I open it, so I also guess the error was poping up in some of my POST tests)

Today I ended up with [2], a WritableField, that seems to work well for write and read.

[2] https://bitbucket.org/fernandogrd/foo/src/d4ea5d999f37/places/serializers.py?at=default#cl-23

In the end, I think I like [1] style.. seems very logical to me, so it may be a good use case for writable nested serializers when souce='*'

I'm looking into this now


Resolving patch:
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -378,23 +378,27 @@ def field_from_native(self, data, files, field_name, into):
         # Set the serializer object if it exists
         obj = getattr(self.parent.object, field_name) if self.parent.object else None
 
-        if value in (None, ''):
-            into[(self.source or field_name)] = None
+        if self.source == '*':
+            if value:
+                into.update(value)
         else:
-            kwargs = {
-                'instance': obj,
-                'data': value,
-                'context': self.context,
-                'partial': self.partial,
-                'many': self.many
-            }
-            serializer = self.__class__(**kwargs)
-
-            if serializer.is_valid():
-                into[self.source or field_name] = serializer.object
+            if value in (None, ''):
+                into[(self.source or field_name)] = None
             else:
-                # Propagate errors up to our parent
-                raise NestedValidationError(serializer.errors)
+                kwargs = {
+                    'instance': obj,
+                    'data': value,
+                    'context': self.context,
+                    'partial': self.partial,
+                    'many': self.many
+                }
+                serializer = self.__class__(**kwargs)
+
+                if serializer.is_valid():
+                    into[self.source or field_name] = serializer.object
+                else:
+                    # Propagate errors up to our parent
+                    raise NestedValidationError(serializer.errors)
 
     def get_identity(self, data):
         """


Test patch:
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -78,6 +78,17 @@ class Meta:
         read_only_fields = ('age',)
 
 
+class NestedSerializer(serializers.Serializer):
+    info = serializers.Field()
+
+
+class ModelSerializerWithNestedSerializer(serializers.ModelSerializer):
+    nested = NestedSerializer(source='*')
+
+    class Meta:
+        model = Person
+
+
 class PersonSerializerInvalidReadOnly(serializers.ModelSerializer):
     """
     Testing for #652.
@@ -369,6 +380,17 @@ def test_missing_model_field_exception_msg(self):
         except:
             self.fail('Wrong exception type thrown.')
 
+    def test_writable_star_source_on_nested_serializer(self):
+        """
+        Assert that a nested serializer instantiated with source='*' correctly
+        expands the data into the outer serializer.
+        """
+        serializer = ModelSerializerWithNestedSerializer(data={
+            'name': 'marko',
+            'nested': {'info': 'hi'}},
+        )
+        self.assertEqual(serializer.is_valid(), True)
+
 
 class CustomValidationTests(TestCase):
     class CommentSerializerWithFieldValidator(CommentSerializer):