Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/community/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,23 @@ 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`.
For example:

from django.test import TestCase

class MyDatabaseTest(TestCase):
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.
Expand All @@ -99,6 +116,10 @@ Shorter form to run the tests for a given test method.

./runtests.py test_this_method

**Note:** 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

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
Expand Down
8 changes: 8 additions & 0 deletions rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,14 @@ def get_fields(self):
# 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:
Expand Down
37 changes: 36 additions & 1 deletion tests/test_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

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

Expand Down Expand Up @@ -64,6 +66,7 @@ def setup_method(self):
class ExampleSerializer(serializers.Serializer):
char = serializers.CharField()
integer = serializers.IntegerField()

self.Serializer = ExampleSerializer

def test_valid_serializer(self):
Expand Down Expand Up @@ -774,3 +777,35 @@ def test_nested_key(self):
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