Added validation on ManyToMany relations when default=None

+ Added tests
This commit is contained in:
Genaro Camele 2025-09-25 22:35:14 -03:00
parent f0ba887761
commit 7e8962c622
2 changed files with 44 additions and 1 deletions

View File

@ -1090,6 +1090,14 @@ class ModelSerializer(Serializer):
# Determine the fields that should be included on the serializer. # Determine the fields that should be included on the serializer.
fields = {} 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: for field_name in field_names:
# If the field is explicitly declared on the class then use that. # If the field is explicitly declared on the class then use that.
if field_name in declared_fields: if field_name in declared_fields:

View File

@ -6,12 +6,14 @@ from collections.abc import Mapping
import pytest import pytest
from django.db import models from django.db import models
from django.test import TestCase
from rest_framework import exceptions, fields, relations, serializers from rest_framework import exceptions, fields, relations, serializers
from rest_framework.fields import Field from rest_framework.fields import Field
from .models import ( from .models import (
ForeignKeyTarget, NestedForeignKeySource, NullableForeignKeySource ForeignKeyTarget, ManyToManySource, ManyToManyTarget,
NestedForeignKeySource, NullableForeignKeySource
) )
from .utils import MockObject from .utils import MockObject
@ -64,6 +66,7 @@ class TestSerializer:
class ExampleSerializer(serializers.Serializer): class ExampleSerializer(serializers.Serializer):
char = serializers.CharField() char = serializers.CharField()
integer = serializers.IntegerField() integer = serializers.IntegerField()
self.Serializer = ExampleSerializer self.Serializer = ExampleSerializer
def test_valid_serializer(self): def test_valid_serializer(self):
@ -774,3 +777,35 @@ class TestSetValueMethod:
ret = {'a': 1} ret = {'a': 1}
self.s.set_value(ret, ['x', 'y'], 2) self.s.set_value(ret, ['x', 'y'], 2)
assert ret == {'a': 1, '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