mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-10-19 10:14:32 +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
|
# Run the tests
|
||||||
./runtests.py
|
./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
|
### Test options
|
||||||
|
|
||||||
Run using a more concise output style.
|
Run using a more concise output style.
|
||||||
|
|
||||||
./runtests.py -q
|
./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.
|
Run the tests for a given test case.
|
||||||
|
|
||||||
./runtests.py MyTestCase
|
./runtests.py MyTestCase
|
||||||
|
@ -99,6 +132,7 @@ Shorter form to run the tests for a given test method.
|
||||||
|
|
||||||
./runtests.py test_this_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.
|
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
|
### Running against multiple environments
|
||||||
|
|
|
@ -1090,6 +1090,13 @@ 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."
|
||||||
|
)
|
||||||
|
|
||||||
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:
|
||||||
|
|
|
@ -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 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