diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 8fe284bc8..74dd34be3 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1090,6 +1090,14 @@ class ModelSerializer(Serializer): # Determine the fields that should be included on the serializer. fields = {} + # If it's a ManyToMany field, and the default is None, then raises an exception to prevent exceptions on .set() + for field_name in declared_fields.keys(): + if field_name in info.relations and info.relations[field_name].to_many and declared_fields[field_name].default is None: + raise ValueError( + f"The field '{field_name}' on serializer '{self.__class__.__name__}' is a ManyToMany field and cannot have a default value of None. " + "Please set an appropriate default value, such as an empty list, or remove the default." + ) + for field_name in field_names: # If the field is explicitly declared on the class then use that. if field_name in declared_fields: diff --git a/tests/test_serializer.py b/tests/test_serializer.py index cefa2ee38..960c663f6 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -6,12 +6,14 @@ from collections.abc import Mapping import pytest from django.db import models +from django.test import TestCase from rest_framework import exceptions, fields, relations, serializers from rest_framework.fields import Field from .models import ( - ForeignKeyTarget, NestedForeignKeySource, NullableForeignKeySource + ForeignKeyTarget, ManyToManySource, ManyToManyTarget, + NestedForeignKeySource, NullableForeignKeySource ) from .utils import MockObject @@ -64,6 +66,7 @@ class TestSerializer: class ExampleSerializer(serializers.Serializer): char = serializers.CharField() integer = serializers.IntegerField() + self.Serializer = ExampleSerializer def test_valid_serializer(self): @@ -774,3 +777,35 @@ class TestSetValueMethod: ret = {'a': 1} self.s.set_value(ret, ['x', 'y'], 2) assert ret == {'a': 1, 'x': {'y': 2}} + + +class TestWarningManyToMany(TestCase): + def test_warning_many_to_many(self): + """Tests that using a PrimaryKeyRelatedField for a ManyToMany field breaks with default=None.""" + class ManyToManySourceSerializer(serializers.ModelSerializer): + targets = serializers.PrimaryKeyRelatedField( + many=True, + queryset=ManyToManyTarget.objects.all(), + default=None + ) + + class Meta: + model = ManyToManySource + fields = '__all__' + + # Instantiates with invalid data (not value for a ManyToMany field to force using the default) + serializer = ManyToManySourceSerializer(data={ + "name": "Invalid Example", + }) + + error_msg = "The field 'targets' on serializer 'ManyToManySourceSerializer' is a ManyToMany field and cannot have a default value of None. Please set an appropriate default value, such as an empty list, or remove the default." + + # Calls to get_fields() should raise a ValueError + with pytest.raises(ValueError) as exc_info: + serializer.get_fields() + assert str(exc_info.value) == error_msg + + # Calls to is_valid() should behave the same + with pytest.raises(ValueError) as exc_info: + serializer.is_valid(raise_exception=True) + assert str(exc_info.value) == error_msg