Repository: encode/django-rest-framework

Issue:
ModelSerializer.get_extra_kwargs() should not alter Meta.extra_kwargs
ModelSerializer.get_extra_kwargs() currently does:

```
extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})
<snip>
extra_kwargs[field_name] = kwargs
```

If extra_kwargs is defined in the Meta class, this alters the value of it. To avoid this, get_extra_kwargs() should update a _copy_ of it:

```
extra_kwargs = copy.deepcopy(getattr(self.Meta, 'extra_kwargs', {}))
```

When the value of Meta.extra_kwargs is updated, it causes side effects for child serializers that inherit from Meta. (I know that this is officially not recommended, but the doc does show how to do it.) For example:

```
>>> from django.contrib.auth.models import User
>>> from rest_framework import serializers
>>> 
>>> class UserSerializer(serializers.ModelSerializer):
...     class Meta:
...         model = User
...         read_only_fields = ('username', 'email')
...         fields = read_only_fields
...         extra_kwargs = {}
... 
>>> class ChildUserSerializer(UserSerializer):
...     class Meta(UserSerializer.Meta):
...         read_only_fields = ()
... 
```

Before instantiating the base class, the child class looks correct:

```
>>> ChildUserSerializer.Meta.extra_kwargs
{}
>>> print ChildUserSerializer()
ChildUserSerializer():
    username = CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, validators=[<django.core.validators.RegexValidator object>, <UniqueValidator(queryset=User.objects.all())>])
    email = EmailField(allow_blank=True, label='Email address', max_length=254, required=False)
```

But after instantiating the base class, the child class' Meta.extra_kwargs is corrupted:

```
>>> print UserSerializer()
UserSerializer():
    username = CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', read_only=True)
    email = EmailField(label='Email address', read_only=True)
>>> ChildUserSerializer.Meta.extra_kwargs
{'username': {u'read_only': True}, 'email': {u'read_only': True}}
>>> print ChildUserSerializer()
ChildUserSerializer():
    username = CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', read_only=True)
    email = EmailField(label='Email address', read_only=True)
```



Discussion:
Seems valid. Pull request with test case would be next step here.


Resolving patch:
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -1212,7 +1212,7 @@ def get_extra_kwargs(self):
         Return a dictionary mapping field names to a dictionary of
         additional keyword arguments.
         """
-        extra_kwargs = getattr(self.Meta, 'extra_kwargs', {})
+        extra_kwargs = copy.deepcopy(getattr(self.Meta, 'extra_kwargs', {}))
 
         read_only_fields = getattr(self.Meta, 'read_only_fields', None)
         if read_only_fields is not None:


Test patch:
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -909,3 +909,34 @@ class Meta:
         serializer = TestSerializer()
 
         assert serializer.fields['decimal_field'].max_value == 3
+
+
+class TestMetaInheritance(TestCase):
+    def test_extra_kwargs_not_altered(self):
+        class TestSerializer(serializers.ModelSerializer):
+            non_model_field = serializers.CharField()
+
+            class Meta:
+                model = OneFieldModel
+                read_only_fields = ('char_field', 'non_model_field')
+                fields = read_only_fields
+                extra_kwargs = {}
+
+        class ChildSerializer(TestSerializer):
+            class Meta(TestSerializer.Meta):
+                read_only_fields = ()
+
+        test_expected = dedent("""
+            TestSerializer():
+                char_field = CharField(read_only=True)
+                non_model_field = CharField()
+        """)
+
+        child_expected = dedent("""
+            ChildSerializer():
+                char_field = CharField(max_length=100)
+                non_model_field = CharField()
+        """)
+        self.assertEqual(unicode_repr(ChildSerializer()), child_expected)
+        self.assertEqual(unicode_repr(TestSerializer()), test_expected)
+        self.assertEqual(unicode_repr(ChildSerializer()), child_expected)