From bbeb13ada97cee3e86d3943015d317e84bf2b6c4 Mon Sep 17 00:00:00 2001 From: Ashley Loewen Date: Wed, 13 Jul 2022 16:59:47 -0400 Subject: [PATCH] Support many related field (#1065) Co-authored-by: Oliver Sauder --- AUTHORS | 1 + CHANGELOG.md | 1 + rest_framework_json_api/utils.py | 18 ++++++------- tests/models.py | 11 ++++++++ tests/test_utils.py | 43 ++++++++++++++++++++++++++++++++ 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/AUTHORS b/AUTHORS index fa91e9b2..419144b5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2,6 +2,7 @@ Adam Wróbel Adam Ziolkowski Alan Crosswell Anton Shutik +Ashley Loewen Asif Saif Uddin Beni Keller Boris Pleshakov diff --git a/CHANGELOG.md b/CHANGELOG.md index a1b72009..bb0ec748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ any parts of the framework not mentioned in the documentation should generally b ### Fixed * Fixed invalid relationship pointer in error objects when field naming formatting is used. +* Properly resolved related resource type when nested source field is defined. ## [5.0.0] - 2022-01-03 diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index 8d2dfa73..8317c10f 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -211,15 +211,15 @@ def get_related_resource_type(relation): relation_model = relation.model elif hasattr(relation, "get_queryset") and relation.get_queryset() is not None: relation_model = relation.get_queryset().model - elif ( - getattr(relation, "many", False) - and hasattr(relation.child, "Meta") - and hasattr(relation.child.Meta, "model") - ): - # For ManyToMany relationships, get the model from the child - # serializer of the list serializer - relation_model = relation.child.Meta.model - else: + elif hasattr(relation, "child_relation"): + # For ManyRelatedField relationships, get the model from the child relationship + try: + return get_related_resource_type(relation.child_relation) + except AttributeError: + # Some read only relationships fail to get it directly, fall through to + # get via the parent + pass + if not relation_model: parent_serializer = relation.parent parent_model = None if isinstance(parent_serializer, PolymorphicModelSerializer): diff --git a/tests/models.py b/tests/models.py index 6ad9ad6a..63718fc6 100644 --- a/tests/models.py +++ b/tests/models.py @@ -39,3 +39,14 @@ class ForeignKeySource(DJAModel): target = models.ForeignKey( ForeignKeyTarget, related_name="sources", on_delete=models.CASCADE ) + + +class NestedRelatedSource(DJAModel): + m2m_source = models.ManyToManyField(ManyToManySource, related_name="nested_source") + fk_source = models.ForeignKey( + ForeignKeySource, related_name="nested_source", on_delete=models.CASCADE + ) + m2m_target = models.ManyToManyField(ManyToManySource, related_name="nested_source") + fk_target = models.ForeignKey( + ForeignKeySource, related_name="nested_source", on_delete=models.CASCADE + ) diff --git a/tests/test_utils.py b/tests/test_utils.py index efb1325d..9b47e9ff 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -25,6 +25,7 @@ ForeignKeyTarget, ManyToManySource, ManyToManyTarget, + NestedRelatedSource, ) from tests.serializers import BasicModelSerializer @@ -313,6 +314,48 @@ class Meta: assert get_related_resource_type(field) == output +@pytest.mark.parametrize( + "field,output,related_field_kwargs", + [ + ( + "m2m_source.targets", + "ManyToManyTarget", + {"many": True, "queryset": ManyToManyTarget.objects.all()}, + ), + ( + "m2m_target.sources.", + "ManyToManySource", + {"many": True, "queryset": ManyToManySource.objects.all()}, + ), + ( + "fk_source.target", + "ForeignKeyTarget", + {"many": True, "queryset": ForeignKeyTarget.objects.all()}, + ), + ( + "fk_target.source", + "ForeignKeySource", + {"many": True, "queryset": ForeignKeySource.objects.all()}, + ), + ], +) +def test_get_related_resource_type_from_nested_source( + db, field, output, related_field_kwargs +): + class RelatedResourceTypeSerializer(serializers.ModelSerializer): + relation = serializers.ResourceRelatedField( + source=field, **related_field_kwargs + ) + + class Meta: + model = NestedRelatedSource + fields = ("relation",) + + serializer = RelatedResourceTypeSerializer() + field = serializer.fields["relation"] + assert get_related_resource_type(field) == output + + @pytest.mark.parametrize( "related_field_kwargs,output", [