Repository: encode/django-rest-framework

Issue:
UniqueTogetherValidator is incompatible with field source
The `UniqueTogetherValidator` is incompatible with serializer fields where the serializer field name is different than the underlying model field name. i.e., fields that have a `source` specified.

This manifests itself in two ways:
- For writeable fields, the validator raises a `required` validation error.
- For read-only fields that have a default value, this passes through to the ORM and ultimately raises a `django.core.exceptions.FieldError`.

<details>
<summary><b>Example setup</b></summary>

```python
from django.db import models
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator

from django.test import TestCase


class ExampleModel(models.Model):
    field1 = models.CharField(max_length=60)
    field2 = models.CharField(max_length=60)

    class Meta:
        unique_together = ['field1', 'field2']


class WriteableExample(serializers.ModelSerializer):
    alias = serializers.CharField(source='field1')

    class Meta:
        model = ExampleModel
        fields = ['alias', 'field2']
        validators = [
            UniqueTogetherValidator(
                queryset=ExampleModel.objects.all(),
                fields=['alias', 'field2']
            )
        ]


class ReadOnlyExample(serializers.ModelSerializer):
    alias = serializers.CharField(source='field1', default='test', read_only=True)

    class Meta:
        model = ExampleModel
        fields = ['alias', 'field2']
        validators = [
            UniqueTogetherValidator(
                queryset=ExampleModel.objects.all(),
                fields=['alias', 'field2']
            )
        ]


class ExampleTests(TestCase):

    def test_writeable(self):
        serializer = WriteableExample(data={'alias': 'test', 'field2': 'test'})
        serializer.is_valid(raise_exception=True)

    def test_read_only(self):
        serializer = ReadOnlyExample(data={'field2': 'test'})
        serializer.is_valid(raise_exception=True)

```

</details>

<details>

<summary><b>Test output</b></summary>

```
==================================================== FAILURES =====================================================
___________________________________________ ExampleTests.test_read_only ___________________________________________
tests/test_unique_together_example.py:52: in test_read_only
    serializer.is_valid(raise_exception=True)
rest_framework/serializers.py:234: in is_valid
    self._validated_data = self.run_validation(self.initial_data)
rest_framework/serializers.py:431: in run_validation
    self.run_validators(value)
rest_framework/serializers.py:464: in run_validators
    super().run_validators(to_validate)
rest_framework/fields.py:557: in run_validators
    validator(value)
rest_framework/validators.py:157: in __call__
    queryset = self.filter_queryset(attrs, queryset)
rest_framework/validators.py:143: in filter_queryset
    return qs_filter(queryset, **filter_kwargs)
rest_framework/validators.py:28: in qs_filter
    return queryset.filter(**kwargs)
.tox/venvs/py37-django22/lib/python3.7/site-packages/django/db/models/query.py:892: in filter
    return self._filter_or_exclude(False, *args, **kwargs)
.tox/venvs/py37-django22/lib/python3.7/site-packages/django/db/models/query.py:910: in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
.tox/venvs/py37-django22/lib/python3.7/site-packages/django/db/models/sql/query.py:1290: in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
.tox/venvs/py37-django22/lib/python3.7/site-packages/django/db/models/sql/query.py:1318: in _add_q
    split_subq=split_subq, simple_col=simple_col,
.tox/venvs/py37-django22/lib/python3.7/site-packages/django/db/models/sql/query.py:1190: in build_filter
    lookups, parts, reffed_expression = self.solve_lookup_type(arg)
.tox/venvs/py37-django22/lib/python3.7/site-packages/django/db/models/sql/query.py:1049: in solve_lookup_type
    _, field, _, lookup_parts = self.names_to_path(lookup_splitted, self.get_meta())
.tox/venvs/py37-django22/lib/python3.7/site-packages/django/db/models/sql/query.py:1420: in names_to_path
    "Choices are: %s" % (name, ", ".join(available)))
E   django.core.exceptions.FieldError: Cannot resolve keyword 'alias' into field. Choices are: field1, field2, id

___________________________________________ ExampleTests.test_writeable ___________________________________________
tests/test_unique_together_example.py:48: in test_writeable
    serializer.is_valid(raise_exception=True)
rest_framework/serializers.py:242: in is_valid
    raise ValidationError(self.errors)
E   rest_framework.exceptions.ValidationError: {'alias': [ErrorDetail(string='This field is required.', code='required')]}

```

</details>

----

<details>

<summary><b>Original Description</b></summary>

## Checklist

- [+] I have verified that that issue exists against the `master` branch of Django REST framework.
- [+] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [+] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [+] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.)
- [+] I have reduced the issue to the simplest possible case.
- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

## Steps to reproduce
according to the changes in drf 3.8.2 the read_only+default fields should be explicilty saved in perform_create of viewset or create, save or update method of serializer.

this perform_create is called after validating the data by `serializer.is_valid()` inside create method of CreateModelMixin

```
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
```

now in my codebase i had two cases:

case1 : when the name of field of serializer is same as model like:

model:

```
class A(models.Model):
      a = models.ForeignKey(B, on_delete=models.CASCADE)
      c = models.CharField(max_length=32)
      ....
```

serializer:

```
 class SomeSerializer(serializers.ModelSerializer):
      a = HyperlinkedRelatedField(view_name='c-detail', read_only=True, default=<some instance of Model B>)

      class Meta:
      model=A
      fields=( 'a', 'c', ......)
      validators = [
            UniqueTogetherValidator(
                queryset=A.objects.all(),
                fields=('a', 'c')
            )
        ]

```

In this case serializer.is_valid() is not failing if i POST something to this serializer and my overwritten perform_create is being called so problem here

because as we can see these fields are included in _read_only_defaults after https://github.com/encode/django-rest-framework/pull/5922 and here https://github.com/encode/django-rest-framework/blob/master/rest_framework/serializers.py#L451 the field.field_name is added in the return value that will be validated with uniquetogether so no issues come. 
 
case 2:  when the name of field of serializer is 'not' same as model's field and a source is added in the serializer field like:
model:

```
class A(models.Model):
      a = models.ForeignKey(B, on_delete=models.CASCADE)
      c = models.CharField(max_length=32)
      ....
```

serializer:

```
 class SomeSerializer(serializers.ModelSerializer):
      b = HyperlinkedRelatedField(source='a', view_name='c-detail', read_only=True, default=<some instance of Model B>)

      class Meta:
      model=A
      fields=( 'b', 'c', ......)
      validators = [
            UniqueTogetherValidator(
                queryset=A.objects.all(),
                fields=('a', 'c')
            )
        ]

```
in this case, if i POST something to this serializer, a ValidationError exception is thrown
saying 
`{'a': [ErrorDetail(string='This field is required.', code='required')]}`

because since field.field_name is included in the return ordered dictionary as key so here in the unique together will fail as it expects ('a', 'c') but gets ('b', 'c').

## Expected behavior
in second case also it should work fine

## Actual behavior
throwing error `{'a': [ErrorDetail(string='This field is required.', code='required')]}`

## Possible source of error found
Since all the fields (including read_only) were earlier present in _wriable_fields, this is the place where it used to get the field name from source - https://github.com/encode/django-rest-framework/blob/master/rest_framework/serializers.py#L496

And since it started using the method _read_only_defaults for these read_only+default fields, it populates using field name rather than source name - https://github.com/encode/django-rest-framework/blob/master/rest_framework/serializers.py#L451

which maybe a bug

</details>


Discussion:
Hi @anveshagarwal. After looking at your PR, I've updated the issue description. Note that the bug is not limited to read-only fields, however the writeable and read-only cases raise different errors. 
@rpkilby So ideally uniquetogether validator in serializer should take the name of the field declared in serializer, not the name of the source? But till now it was taking the name of the source or field in the model.
> uniquetogether validator in serializer should take the name of the field declared in serializer, not the name of the source?

Yes, per the [`UniqueTogetherValidator` docs](https://www.django-rest-framework.org/api-guide/validators/#uniquetogethervalidator):

> * `fields` _required_ - A list or tuple of field names which should make a unique set. These must exist as fields on the serializer class.
Thanks for sharing the docs.

Resolving patch:
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -448,7 +448,7 @@ def _read_only_defaults(self):
                 default = field.get_default()
             except SkipField:
                 continue
-            defaults[field.field_name] = default
+            defaults[field.source] = default
 
         return defaults
 
diff --git a/rest_framework/validators.py b/rest_framework/validators.py
--- a/rest_framework/validators.py
+++ b/rest_framework/validators.py
@@ -106,7 +106,7 @@ def enforce_required_fields(self, attrs, serializer):
         missing_items = {
             field_name: self.missing_message
             for field_name in self.fields
-            if field_name not in attrs
+            if serializer.fields[field_name].source not in attrs
         }
         if missing_items:
             raise ValidationError(missing_items, code='required')
@@ -115,17 +115,23 @@ def filter_queryset(self, attrs, queryset, serializer):
         """
         Filter the queryset to all instances matching the given attributes.
         """
+        # field names => field sources
+        sources = [
+            serializer.fields[field_name].source
+            for field_name in self.fields
+        ]
+
         # If this is an update, then any unprovided field should
         # have it's value set based on the existing instance attribute.
         if serializer.instance is not None:
-            for field_name in self.fields:
-                if field_name not in attrs:
-                    attrs[field_name] = getattr(serializer.instance, field_name)
+            for source in sources:
+                if source not in attrs:
+                    attrs[source] = getattr(serializer.instance, source)
 
         # Determine the filter keyword arguments and filter the queryset.
         filter_kwargs = {
-            field_name: attrs[field_name]
-            for field_name in self.fields
+            source: attrs[source]
+            for source in sources
         }
         return qs_filter(queryset, **filter_kwargs)
 


Test patch:
diff --git a/tests/test_validators.py b/tests/test_validators.py
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -301,6 +301,49 @@ class Meta:
             ]
         }
 
+    def test_read_only_fields_with_default_and_source(self):
+        class ReadOnlySerializer(serializers.ModelSerializer):
+            name = serializers.CharField(source='race_name', default='test', read_only=True)
+
+            class Meta:
+                model = UniquenessTogetherModel
+                fields = ['name', 'position']
+                validators = [
+                    UniqueTogetherValidator(
+                        queryset=UniquenessTogetherModel.objects.all(),
+                        fields=['name', 'position']
+                    )
+                ]
+
+        serializer = ReadOnlySerializer(data={'position': 1})
+        assert serializer.is_valid(raise_exception=True)
+
+    def test_writeable_fields_with_source(self):
+        class WriteableSerializer(serializers.ModelSerializer):
+            name = serializers.CharField(source='race_name')
+
+            class Meta:
+                model = UniquenessTogetherModel
+                fields = ['name', 'position']
+                validators = [
+                    UniqueTogetherValidator(
+                        queryset=UniquenessTogetherModel.objects.all(),
+                        fields=['name', 'position']
+                    )
+                ]
+
+        serializer = WriteableSerializer(data={'name': 'test', 'position': 1})
+        assert serializer.is_valid(raise_exception=True)
+
+        # Validation error should use seriazlier field name, not source
+        serializer = WriteableSerializer(data={'position': 1})
+        assert not serializer.is_valid()
+        assert serializer.errors == {
+            'name': [
+                'This field is required.'
+            ]
+        }
+
     def test_allow_explict_override(self):
         """
         Ensure validators can be explicitly removed..
@@ -357,13 +400,9 @@ class MockQueryset:
             def filter(self, **kwargs):
                 self.called_with = kwargs
 
-        class MockSerializer:
-            def __init__(self, instance):
-                self.instance = instance
-
         data = {'race_name': 'bar'}
         queryset = MockQueryset()
-        serializer = MockSerializer(instance=self.instance)
+        serializer = UniquenessTogetherSerializer(instance=self.instance)
         validator = UniqueTogetherValidator(queryset, fields=('race_name',
                                                               'position'))
         validator.filter_queryset(attrs=data, queryset=queryset, serializer=serializer)