Repository: encode/django-rest-framework

Issue:
Raise error when `source=` use on a child, not a field.
I am trying to serialise a related model as a list using their names. I can't use a StringRelatedField as the `__str__` representation of my model doesn't match the desired output.
My approach was to use a ListField specifying a child with a source attribute, such as child=StringField(source='name'), but the inner source is being ignored.
There is no reference in the documentation of this being an issue. It only mentions that ListFields validate lists of objects. Maybe this is not the intended use case but it is not mentioned in the docs, although seems reasonable to me.

``` python
class A(models.Model):
    name = models.CharField(max_length=72)

class B(models.Model):
    name = models.CharField(max_length=72, unique=True)
    field_I_care_about_but_not_users = models.IntegerField()
    a = models.ForeignKey(A, related_name='related_bs')

    def __str__(self):
        return field_I_care_about_but_not_users

class ASerializer(serializers.ModelSerializer):
    bs = serializers.ListField(
        source='related_bs',
        child=serializers.CharField(source='name'))

    class Meta:
        model = A
        fields = ('name', 'bs')
```

When using `ASerializer` I get `field_I_care_about_but_not_users` in the list instead of `name`.

I know I can override `to_representation`, but it surprised me when it didn't work.



Discussion:
Specifying source in that situation doesn't have any meaning.

Normally you're using it to associate a field with a different attribute on the instance than its field name, but when used as a child in a list or other composite element its redundant.

We could consider raising an error in this use-case. I'd rather not go into it in the docs, as I think it'd likely be noise rather than actually helpful.

Have retitled with what I think would be an appropriate action for this.

Thanks for clarifying this.


Resolving patch:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -1381,7 +1381,13 @@ class ListField(Field):
     def __init__(self, *args, **kwargs):
         self.child = kwargs.pop('child', copy.deepcopy(self.child))
         self.allow_empty = kwargs.pop('allow_empty', True)
+
         assert not inspect.isclass(self.child), '`child` has not been instantiated.'
+        assert self.child.source is None, (
+            "The `source` argument is not meaningful when applied to a `child=` field. "
+            "Remove `source=` from the field declaration."
+        )
+
         super(ListField, self).__init__(*args, **kwargs)
         self.child.bind(field_name='', parent=self)
 
@@ -1424,7 +1430,13 @@ class DictField(Field):
 
     def __init__(self, *args, **kwargs):
         self.child = kwargs.pop('child', copy.deepcopy(self.child))
+
         assert not inspect.isclass(self.child), '`child` has not been instantiated.'
+        assert self.child.source is None, (
+            "The `source` argument is not meaningful when applied to a `child=` field. "
+            "Remove `source=` from the field declaration."
+        )
+
         super(DictField, self).__init__(*args, **kwargs)
         self.child.bind(field_name='', parent=self)
 


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -1416,6 +1416,15 @@ class TestListField(FieldValues):
     ]
     field = serializers.ListField(child=serializers.IntegerField())
 
+    def test_no_source_on_child(self):
+        with pytest.raises(AssertionError) as exc_info:
+            serializers.ListField(child=serializers.IntegerField(source='other'))
+
+        assert str(exc_info.value) == (
+            "The `source` argument is not meaningful when applied to a `child=` field. "
+            "Remove `source=` from the field declaration."
+        )
+
 
 class TestEmptyListField(FieldValues):
     """
@@ -1461,6 +1470,15 @@ class TestDictField(FieldValues):
     ]
     field = serializers.DictField(child=serializers.CharField())
 
+    def test_no_source_on_child(self):
+        with pytest.raises(AssertionError) as exc_info:
+            serializers.DictField(child=serializers.CharField(source='other'))
+
+        assert str(exc_info.value) == (
+            "The `source` argument is not meaningful when applied to a `child=` field. "
+            "Remove `source=` from the field declaration."
+        )
+
 
 class TestUnvalidatedDictField(FieldValues):
     """