diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4cba31761..30af98a34 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -997,7 +997,7 @@ class ModelSerializer(Serializer): # relationships as being a special case. During updates we already # have an instance pk for the relationships to be associated with. m2m_fields = [] - partial_update_extra_fields = self.get_partial_update_extra_fields() + partial_update_extra_fields = self.get_partial_update_extra_fields(info.fields.keys()) update_fields = [*(partial_update_extra_fields or [])] for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: @@ -1633,7 +1633,7 @@ class ModelSerializer(Serializer): return validators - def get_partial_update_extra_fields(self): + def get_partial_update_extra_fields(self, field_names): partial_update_extra_fields = getattr(self.Meta, 'partial_update_extra_fields', None) if partial_update_extra_fields is not None and not isinstance(partial_update_extra_fields, (list, tuple)): @@ -1642,10 +1642,9 @@ class ModelSerializer(Serializer): type(partial_update_extra_fields).__name__ ) - fields = self.get_fields() if partial_update_extra_fields is not None: for field_name in partial_update_extra_fields: - assert field_name in fields, ( + assert field_name in field_names, ( "The field '{field_name}' was included on serializer " "{serializer_class} in the 'partial_update_extra_fields' option, but does " "not match any model field.".format( diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 7da1b41ae..a156ec93d 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -1301,6 +1301,11 @@ class Issue6751Model(models.Model): char_field2 = models.CharField(max_length=100) +class Issue2648Model(models.Model): + char_field = models.CharField(max_length=100) + updated_at = models.DateTimeField(auto_now=True, blank=True, null=True) + + @receiver(m2m_changed, sender=Issue6751Model.many_to_many.through) def process_issue6751model_m2m_changed(action, instance, **_): if action == 'post_add': @@ -1333,3 +1338,60 @@ class Issue6751Test(TestCase): serializer.save() self.assertEqual(instance.char_field, 'value changed by signal') + + +class Issue2648Test(TestCase): + def test_model_serializer_uses_partial_update_extra_fields_when_not_empty(self): + class TestSerializer(serializers.ModelSerializer): + class Meta: + model = Issue2648Model + partial_update_extra_fields = ('updated_at',) + fields = ('updated_at', 'char_field',) + + instance = Issue2648Model.objects.create(char_field='initial value') + instance.updated_at = None + instance.save(update_fields=['updated_at']) + + serializer = TestSerializer(instance=instance, data={'char_field': 'char_field updated value'}, partial=True) + serializer.is_valid() + serializer.save() + + self.assertEqual(instance.char_field, 'char_field updated value') + self.assertIsNotNone(instance.updated_at) + + def test_model_serializer_uses_partial_update_extra_fields_when_empty(self): + class TestSerializer(serializers.ModelSerializer): + class Meta: + model = Issue2648Model + partial_update_extra_fields = [] + fields = ('updated_at', 'char_field',) + + instance = Issue2648Model.objects.create(char_field='initial value') + instance.updated_at = None + instance.save(update_fields=['updated_at']) + + serializer = TestSerializer(instance=instance, data={'char_field': 'char_field updated value'}, partial=True) + serializer.is_valid() + serializer.save() + + self.assertEqual(instance.char_field, 'char_field updated value') + self.assertIsNone(instance.updated_at) + + def test_model_serializer_validate_partial_update_extra_fields_are_model_fields(self): + class TestSerializer(serializers.ModelSerializer): + class Meta: + model = Issue2648Model + partial_update_extra_fields = ('missing_model_field',) + fields = ('updated_at', 'char_field',) + + instance = Issue2648Model.objects.create(char_field='initial value') + serializer = TestSerializer(instance=instance, data={'char_field': 'char_field updated value'}, partial=True) + serializer.is_valid() + + expected = ( + "The field 'missing_model_field' was included on serializer " + "TestSerializer in the 'partial_update_extra_fields' option, but does " + "not match any model field." + ) + with self.assertRaisesMessage(AssertionError, expected): + serializer.save()