mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-10-18 17:54:18 +03:00
Validation on ManyToManyField when default=None (#9790)
* Added validation on ManyToMany relations when default=None and tests * Some clarifications in contributing.md
This commit is contained in:
parent
c0f3649224
commit
577bb3c819
|
@ -81,12 +81,45 @@ To run the tests, clone the repository, and then:
|
|||
# Run the tests
|
||||
./runtests.py
|
||||
|
||||
---
|
||||
|
||||
**Note:** if your tests require access to the database, do not forget to inherit from `django.test.TestCase` or use the `@pytest.mark.django_db()` decorator.
|
||||
|
||||
For example, with TestCase:
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
class MyDatabaseTest(TestCase):
|
||||
def test_something(self):
|
||||
# Your test code here
|
||||
pass
|
||||
|
||||
Or with decorator:
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.mark.django_db()
|
||||
class MyDatabaseTest:
|
||||
def test_something(self):
|
||||
# Your test code here
|
||||
pass
|
||||
|
||||
You can reuse existing models defined in `tests/models.py` for your tests.
|
||||
|
||||
---
|
||||
|
||||
### Test options
|
||||
|
||||
Run using a more concise output style.
|
||||
|
||||
./runtests.py -q
|
||||
|
||||
|
||||
If you do not want the output to be captured (for example, to see print statements directly), you can use the `-s` flag.
|
||||
|
||||
./runtests.py -s
|
||||
|
||||
|
||||
Run the tests for a given test case.
|
||||
|
||||
./runtests.py MyTestCase
|
||||
|
@ -99,6 +132,7 @@ Shorter form to run the tests for a given test method.
|
|||
|
||||
./runtests.py test_this_method
|
||||
|
||||
|
||||
Note: The test case and test method matching is fuzzy and will sometimes run other tests that contain a partial string match to the given command line input.
|
||||
|
||||
### Running against multiple environments
|
||||
|
|
|
@ -1090,6 +1090,13 @@ 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."
|
||||
)
|
||||
|
||||
for field_name in field_names:
|
||||
# If the field is explicitly declared on the class then use that.
|
||||
if field_name in declared_fields:
|
||||
|
|
|
@ -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 serializer without 'value' field to force using the default=None for the ManyToMany relation
|
||||
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."
|
||||
|
||||
# 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
|
||||
|
|
Loading…
Reference in New Issue
Block a user