From 41edb3b9dde2105b9fb015ce6b8171e7822baa30 Mon Sep 17 00:00:00 2001 From: Yuekui Date: Fri, 26 Jan 2024 02:36:18 -0800 Subject: [PATCH] Avoid unnecessary unique together checking (#9154) --- rest_framework/validators.py | 17 +++++++++++++---- tests/test_validators.py | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/rest_framework/validators.py b/rest_framework/validators.py index 07ad11b47..3f09c15cd 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -160,10 +160,19 @@ class UniqueTogetherValidator: queryset = self.exclude_current_instance(attrs, queryset, serializer.instance) # Ignore validation if any field is None - checked_values = [ - value for field, value in attrs.items() if field in self.fields - ] - if None not in checked_values and qs_exists(queryset): + if serializer.instance is None: + checked_values = [ + value for field, value in attrs.items() if field in self.fields + ] + else: + # Ignore validation if all field values are unchanged + checked_values = [ + value + for field, value in attrs.items() + if field in self.fields and value != getattr(serializer.instance, field) + ] + + if checked_values and None not in checked_values and qs_exists(queryset): field_names = ', '.join(self.fields) message = self.message.format(field_names=field_names) raise ValidationError(message, code='unique') diff --git a/tests/test_validators.py b/tests/test_validators.py index 1cf42ed07..49b0db63a 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,5 +1,5 @@ import datetime -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import pytest from django.db import DataError, models @@ -447,6 +447,22 @@ class TestUniquenessTogetherValidation(TestCase): serializer = NullUniquenessTogetherSerializer(data=data) assert not serializer.is_valid() + def test_ignore_validation_for_unchanged_fields(self): + """ + If all fields in the unique together constraint are unchanged, + then the instance should skip uniqueness validation. + """ + instance = UniquenessTogetherModel.objects.create( + race_name="Paris Marathon", position=1 + ) + data = {"race_name": "Paris Marathon", "position": 1} + serializer = UniquenessTogetherSerializer(data=data, instance=instance) + with patch( + "rest_framework.validators.qs_exists" + ) as mock: + assert serializer.is_valid() + assert not mock.called + def test_filter_queryset_do_not_skip_existing_attribute(self): """ filter_queryset should add value from existing instance attribute