diff --git a/example/settings/dev.py b/example/settings/dev.py index 2db2c259..12bed35c 100644 --- a/example/settings/dev.py +++ b/example/settings/dev.py @@ -27,6 +27,7 @@ "example", "debug_toolbar", "django_filters", + "tests", ] TEMPLATES = [ diff --git a/example/tests/test_relations.py b/example/tests/test_relations.py index b83bbef4..6d1cf83b 100644 --- a/example/tests/test_relations.py +++ b/example/tests/test_relations.py @@ -6,130 +6,17 @@ from rest_framework.fields import SkipField from rest_framework.reverse import reverse -from rest_framework_json_api.exceptions import Conflict from rest_framework_json_api.relations import ( HyperlinkedRelatedField, ResourceRelatedField, SerializerMethodHyperlinkedRelatedField, ) -from rest_framework_json_api.utils import format_resource_type from . import TestBase from example.models import Author, Blog, Comment, Entry -from example.serializers import CommentSerializer from example.views import EntryViewSet -class TestResourceRelatedField(TestBase): - def setUp(self): - super(TestResourceRelatedField, self).setUp() - self.blog = Blog.objects.create(name="Some Blog", tagline="It's a blog") - self.entry = Entry.objects.create( - blog=self.blog, - headline="headline", - body_text="body_text", - pub_date=timezone.now(), - mod_date=timezone.now(), - n_comments=0, - n_pingbacks=0, - rating=3, - ) - for i in range(1, 6): - name = "some_author{}".format(i) - self.entry.authors.add( - Author.objects.create(name=name, email="{}@example.org".format(name)) - ) - - self.comment = Comment.objects.create( - entry=self.entry, - body="testing one two three", - author=Author.objects.first(), - ) - - def test_data_in_correct_format_when_instantiated_with_blog_object(self): - serializer = BlogFKSerializer(instance={"blog": self.blog}) - - expected_data = {"type": format_resource_type("Blog"), "id": str(self.blog.id)} - - actual_data = serializer.data["blog"] - - self.assertEqual(actual_data, expected_data) - - def test_data_in_correct_format_when_instantiated_with_entry_object(self): - serializer = EntryFKSerializer(instance={"entry": self.entry}) - - expected_data = { - "type": format_resource_type("Entry"), - "id": str(self.entry.id), - } - - actual_data = serializer.data["entry"] - - self.assertEqual(actual_data, expected_data) - - def test_deserialize_primitive_data_blog(self): - serializer = BlogFKSerializer( - data={ - "blog": {"type": format_resource_type("Blog"), "id": str(self.blog.id)} - } - ) - - self.assertTrue(serializer.is_valid()) - self.assertEqual(serializer.validated_data["blog"], self.blog) - - def test_validation_fails_for_wrong_type(self): - with self.assertRaises(Conflict) as cm: - serializer = BlogFKSerializer( - data={"blog": {"type": "Entries", "id": str(self.blog.id)}} - ) - serializer.is_valid() - the_exception = cm.exception - self.assertEqual(the_exception.status_code, 409) - - def test_serialize_many_to_many_relation(self): - serializer = EntryModelSerializer(instance=self.entry) - - type_string = format_resource_type("Author") - author_pks = Author.objects.values_list("pk", flat=True) - expected_data = [{"type": type_string, "id": str(pk)} for pk in author_pks] - - self.assertEqual(serializer.data["authors"], expected_data) - - def test_deserialize_many_to_many_relation(self): - type_string = format_resource_type("Author") - author_pks = Author.objects.values_list("pk", flat=True) - authors = [{"type": type_string, "id": pk} for pk in author_pks] - - serializer = EntryModelSerializer(data={"authors": authors, "comments": []}) - - self.assertTrue(serializer.is_valid()) - self.assertEqual( - len(serializer.validated_data["authors"]), Author.objects.count() - ) - for author in serializer.validated_data["authors"]: - self.assertIsInstance(author, Author) - - def test_read_only(self): - serializer = EntryModelSerializer( - data={"authors": [], "comments": [{"type": "Comments", "id": 2}]} - ) - serializer.is_valid(raise_exception=True) - self.assertNotIn("comments", serializer.validated_data) - - def test_invalid_resource_id_object(self): - comment = { - "body": "testing 123", - "entry": {"type": "entry"}, - "author": {"id": "5"}, - } - serializer = CommentSerializer(data=comment) - assert not serializer.is_valid() - assert serializer.errors == { - "author": ["Invalid resource identifier object: missing 'type' attribute"], - "entry": ["Invalid resource identifier object: missing 'id' attribute"], - } - - class TestHyperlinkedFieldBase(TestBase): def setUp(self): super(TestHyperlinkedFieldBase, self).setUp() diff --git a/tests/conftest.py b/tests/conftest.py index 4942bf63..fbd3f453 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,7 @@ import pytest +from tests.models import ForeignKeyTarget, ManyToManySource, ManyToManyTarget + @pytest.fixture(autouse=True) def use_rest_framework_json_api_defaults(settings): @@ -19,3 +21,23 @@ def use_rest_framework_json_api_defaults(settings): settings.JSON_API_FORMAT_FIELD_NAMES = False settings.JSON_API_FORMAT_TYPES = False settings.JSON_API_PLURALIZE_TYPES = False + + +@pytest.fixture +def foreign_key_target(db): + return ForeignKeyTarget.objects.create(name="Target") + + +@pytest.fixture +def many_to_many_source(db, many_to_many_targets): + source = ManyToManySource.objects.create(name="Source") + source.targets.add(*many_to_many_targets) + return source + + +@pytest.fixture +def many_to_many_targets(db): + return [ + ManyToManyTarget.objects.create(name="Target1"), + ManyToManyTarget.objects.create(name="Target2"), + ] diff --git a/tests/serializers.py b/tests/serializers.py new file mode 100644 index 00000000..8894a211 --- /dev/null +++ b/tests/serializers.py @@ -0,0 +1,44 @@ +from rest_framework_json_api.relations import ResourceRelatedField +from rest_framework_json_api.serializers import ModelSerializer +from tests.models import ( + BasicModel, + ForeignKeySource, + ForeignKeyTarget, + ManyToManySource, + ManyToManyTarget, +) + + +class BasicModelSerializer(ModelSerializer): + class Meta: + fields = ("text",) + model = BasicModel + + +class ForeignKeySourceSerializer(ModelSerializer): + target = ResourceRelatedField(queryset=ForeignKeyTarget.objects) + + class Meta: + model = ForeignKeySource + fields = ("target",) + + +class ManyToManySourceSerializer(ModelSerializer): + targets = ResourceRelatedField(many=True, queryset=ManyToManyTarget.objects) + + class Meta: + model = ManyToManySource + fields = ("targets",) + + +class ManyToManyTargetSerializer(ModelSerializer): + class Meta: + model = ManyToManyTarget + + +class ManyToManySourceReadOnlySerializer(ModelSerializer): + targets = ResourceRelatedField(many=True, read_only=True) + + class Meta: + model = ManyToManySource + fields = ("targets",) diff --git a/tests/test_relations.py b/tests/test_relations.py index 2565542f..4cc9833d 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -1,11 +1,17 @@ import pytest from django.conf.urls import re_path +from rest_framework import status from rest_framework.routers import SimpleRouter +from rest_framework_json_api.exceptions import Conflict from rest_framework_json_api.relations import HyperlinkedRelatedField from rest_framework_json_api.views import ModelViewSet, RelationshipView - -from .models import BasicModel +from tests.models import BasicModel +from tests.serializers import ( + ForeignKeySourceSerializer, + ManyToManySourceReadOnlySerializer, + ManyToManySourceSerializer, +) @pytest.mark.urls(__name__) @@ -43,6 +49,158 @@ def test_relationship_urls_respect_format_related_links_setting( assert expected == actual +@pytest.mark.django_db +class TestResourceRelatedField: + @pytest.mark.parametrize( + "format_type,pluralize_type,resource_type", + [ + (False, False, "ForeignKeyTarget"), + (False, True, "ForeignKeyTargets"), + ("dasherize", False, "foreign-key-target"), + ("dasherize", True, "foreign-key-targets"), + ], + ) + def test_serialize( + self, format_type, pluralize_type, resource_type, foreign_key_target, settings + ): + settings.JSON_API_FORMAT_TYPES = format_type + settings.JSON_API_PLURALIZE_TYPES = pluralize_type + + serializer = ForeignKeySourceSerializer(instance={"target": foreign_key_target}) + expected = { + "type": resource_type, + "id": str(foreign_key_target.pk), + } + + assert serializer.data["target"] == expected + + @pytest.mark.parametrize( + "format_type,pluralize_type,resource_type", + [ + (False, False, "ForeignKeyTarget"), + (False, True, "ForeignKeyTargets"), + ("dasherize", False, "foreign-key-target"), + ("dasherize", True, "foreign-key-targets"), + ], + ) + def test_deserialize( + self, format_type, pluralize_type, resource_type, foreign_key_target, settings + ): + settings.JSON_API_FORMAT_TYPES = format_type + settings.JSON_API_PLURALIZE_TYPES = pluralize_type + + serializer = ForeignKeySourceSerializer( + data={"target": {"type": resource_type, "id": str(foreign_key_target.pk)}} + ) + + assert serializer.is_valid() + assert serializer.validated_data["target"] == foreign_key_target + + @pytest.mark.parametrize( + "format_type,pluralize_type,resource_type", + [ + (False, False, "ForeignKeyTargets"), + (False, False, "Invalid"), + (False, False, "foreign-key-target"), + (False, True, "ForeignKeyTarget"), + ("dasherize", False, "ForeignKeyTarget"), + ("dasherize", True, "ForeignKeyTargets"), + ], + ) + def test_validation_fails_on_invalid_type( + self, format_type, pluralize_type, resource_type, foreign_key_target, settings + ): + settings.JSON_API_FORMAT_TYPES = format_type + settings.JSON_API_PLURALIZE_TYPES = pluralize_type + + with pytest.raises(Conflict) as e: + serializer = ForeignKeySourceSerializer( + data={ + "target": {"type": resource_type, "id": str(foreign_key_target.pk)} + } + ) + serializer.is_valid() + assert e.value.status_code == status.HTTP_409_CONFLICT + + @pytest.mark.parametrize( + "format_type,pluralize_type,resource_type", + [ + (False, False, "ManyToManyTarget"), + (False, True, "ManyToManyTargets"), + ("dasherize", False, "many-to-many-target"), + ("dasherize", True, "many-to-many-targets"), + ], + ) + def test_serialize_many_to_many_relation( + self, + format_type, + pluralize_type, + resource_type, + many_to_many_source, + many_to_many_targets, + settings, + ): + settings.JSON_API_FORMAT_TYPES = format_type + settings.JSON_API_PLURALIZE_TYPES = pluralize_type + + serializer = ManyToManySourceSerializer(instance=many_to_many_source) + expected = [ + {"type": resource_type, "id": str(target.pk)} + for target in many_to_many_targets + ] + assert serializer.data["targets"] == expected + + @pytest.mark.parametrize( + "format_type,pluralize_type,resource_type", + [ + (False, False, "ManyToManyTarget"), + (False, True, "ManyToManyTargets"), + ("dasherize", False, "many-to-many-target"), + ("dasherize", True, "many-to-many-targets"), + ], + ) + @pytest.mark.parametrize( + "serializer_class", + [ManyToManySourceSerializer, ManyToManySourceReadOnlySerializer], + ) + def test_deserialize_many_to_many_relation( + self, + format_type, + pluralize_type, + resource_type, + serializer_class, + many_to_many_targets, + settings, + ): + settings.JSON_API_FORMAT_TYPES = format_type + settings.JSON_API_PLURALIZE_TYPES = pluralize_type + + targets = [ + {"type": resource_type, "id": target.pk} for target in many_to_many_targets + ] + serializer = ManyToManySourceSerializer(data={"targets": targets}) + assert serializer.is_valid() + assert serializer.validated_data["targets"] == many_to_many_targets + + @pytest.mark.parametrize( + "resource_identifier,error", + [ + ( + {"type": "ForeignKeyTarget"}, + "Invalid resource identifier object: missing 'id' attribute", + ), + ( + {"id": "1234"}, + "Invalid resource identifier object: missing 'type' attribute", + ), + ], + ) + def test_invalid_resource_id_object(self, resource_identifier, error): + serializer = ForeignKeySourceSerializer(data={"target": resource_identifier}) + assert not serializer.is_valid() + assert serializer.errors == {"target": [error]} + + # Routing setup diff --git a/tests/test_utils.py b/tests/test_utils.py index f542ad7d..40f9d391 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -23,6 +23,7 @@ ManyToManySource, ManyToManyTarget, ) +from tests.serializers import BasicModelSerializer, ManyToManyTargetSerializer def test_get_resource_name_no_view(): @@ -99,11 +100,6 @@ def test_get_resource_name_from_model(settings, format_type, pluralize_type, out def test_get_resource_name_from_model_serializer_class( settings, format_type, pluralize_type, output ): - class BasicModelSerializer(serializers.ModelSerializer): - class Meta: - fields = ("text",) - model = BasicModel - settings.JSON_API_FORMAT_TYPES = format_type settings.JSON_API_PLURALIZE_TYPES = pluralize_type @@ -125,11 +121,6 @@ class Meta: def test_get_resource_name_from_model_serializer_class_custom_resource_name( settings, format_type, pluralize_type ): - class BasicModelSerializer(serializers.ModelSerializer): - class Meta: - fields = ("text",) - model = BasicModel - settings.JSON_API_FORMAT_TYPES = format_type settings.JSON_API_PLURALIZE_TYPES = pluralize_type @@ -280,11 +271,6 @@ class PlainRelatedResourceTypeSerializer(serializers.Serializer): assert get_related_resource_type(field) == output -class ManyToManyTargetSerializer(serializers.ModelSerializer): - class Meta: - model = ManyToManyTarget - - def test_get_included_serializers(): class IncludedSerializersModel(DJAModel): self = models.ForeignKey("self", on_delete=models.CASCADE) @@ -298,7 +284,7 @@ class IncludedSerializersSerializer(serializers.ModelSerializer): included_serializers = { "self": "self", "target": ManyToManyTargetSerializer, - "other_target": "tests.test_utils.ManyToManyTargetSerializer", + "other_target": "tests.serializers.ManyToManyTargetSerializer", } class Meta: