From 95143814f4ad1af7d83e3d0f85305a8663faa1db Mon Sep 17 00:00:00 2001 From: Ashley Loewen Date: Mon, 30 May 2022 15:16:28 -0400 Subject: [PATCH 1/3] Support many related field --- rest_framework_json_api/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index 8d2dfa73..458d531f 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -219,6 +219,12 @@ def get_related_resource_type(relation): # For ManyToMany relationships, get the model from the child # serializer of the list serializer relation_model = relation.child.Meta.model + elif ( + hasattr(relation, "child_relation") + and hasattr(relation.child_relation, "model") + ): + # For ManyRelatedField relationships, get the model from the child relationship + relation_model = relation.child_relation.model else: parent_serializer = relation.parent parent_model = None From 2656d003b097a996711ad6e9afef9fc2c914a4c6 Mon Sep 17 00:00:00 2001 From: Ashley Loewen Date: Tue, 14 Jun 2022 10:55:14 -0400 Subject: [PATCH 2/3] Adding tests and cleanup --- AUTHORS | 1 + rest_framework_json_api/utils.py | 22 ++++++--------- tests/models.py | 11 ++++++++ tests/test_utils.py | 47 ++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 14 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/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index 458d531f..8317c10f 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -211,21 +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 - elif ( - hasattr(relation, "child_relation") - and hasattr(relation.child_relation, "model") - ): + elif hasattr(relation, "child_relation"): # For ManyRelatedField relationships, get the model from the child relationship - relation_model = relation.child_relation.model - else: + 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..e331e8ae 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 NestedRelationshipSource(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..a7c1810a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -25,6 +25,7 @@ ForeignKeyTarget, ManyToManySource, ManyToManyTarget, + NestedRelationshipSource, ) from tests.serializers import BasicModelSerializer @@ -313,6 +314,52 @@ class Meta: assert get_related_resource_type(field) == output +@pytest.mark.parametrize( + "model_class,field,output,related_field_kwargs", + [ + ( + NestedRelationshipSource, + "m2m_source.targets", + "ManyToManyTarget", + {"many": True, "queryset": ManyToManyTarget.objects.all()}, + ), + ( + NestedRelationshipSource, + "m2m_target.sources.", + "ManyToManySource", + {"many": True, "queryset": ManyToManySource.objects.all()}, + ), + ( + NestedRelationshipSource, + "fk_source.target", + "ForeignKeyTarget", + {"many": True, "queryset": ForeignKeyTarget.objects.all()}, + ), + ( + NestedRelationshipSource, + "fk_target.source", + "ForeignKeySource", + {"many": True, "queryset": ForeignKeySource.objects.all()}, + ), + ], +) +def test_get_related_resource_type_nested_source( + model_class, field, output, related_field_kwargs +): + class RelatedResourceTypeSerializer(serializers.ModelSerializer): + relationship = serializers.ResourceRelatedField( + source=field, **related_field_kwargs + ) + + class Meta: + model = model_class + fields = ("relationship",) + + serializer = RelatedResourceTypeSerializer() + field = serializer.fields["relationship"] + assert get_related_resource_type(field) == output + + @pytest.mark.parametrize( "related_field_kwargs,output", [ From 098cead823c45d23786d598ba774ff4badb11893 Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Wed, 13 Jul 2022 22:47:16 +0200 Subject: [PATCH 3/3] Cleaning up code and add changelog --- CHANGELOG.md | 1 + tests/models.py | 2 +- tests/test_utils.py | 20 ++++++++------------ 3 files changed, 10 insertions(+), 13 deletions(-) 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/tests/models.py b/tests/models.py index e331e8ae..63718fc6 100644 --- a/tests/models.py +++ b/tests/models.py @@ -41,7 +41,7 @@ class ForeignKeySource(DJAModel): ) -class NestedRelationshipSource(DJAModel): +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 diff --git a/tests/test_utils.py b/tests/test_utils.py index a7c1810a..9b47e9ff 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -25,7 +25,7 @@ ForeignKeyTarget, ManyToManySource, ManyToManyTarget, - NestedRelationshipSource, + NestedRelatedSource, ) from tests.serializers import BasicModelSerializer @@ -315,48 +315,44 @@ class Meta: @pytest.mark.parametrize( - "model_class,field,output,related_field_kwargs", + "field,output,related_field_kwargs", [ ( - NestedRelationshipSource, "m2m_source.targets", "ManyToManyTarget", {"many": True, "queryset": ManyToManyTarget.objects.all()}, ), ( - NestedRelationshipSource, "m2m_target.sources.", "ManyToManySource", {"many": True, "queryset": ManyToManySource.objects.all()}, ), ( - NestedRelationshipSource, "fk_source.target", "ForeignKeyTarget", {"many": True, "queryset": ForeignKeyTarget.objects.all()}, ), ( - NestedRelationshipSource, "fk_target.source", "ForeignKeySource", {"many": True, "queryset": ForeignKeySource.objects.all()}, ), ], ) -def test_get_related_resource_type_nested_source( - model_class, field, output, related_field_kwargs +def test_get_related_resource_type_from_nested_source( + db, field, output, related_field_kwargs ): class RelatedResourceTypeSerializer(serializers.ModelSerializer): - relationship = serializers.ResourceRelatedField( + relation = serializers.ResourceRelatedField( source=field, **related_field_kwargs ) class Meta: - model = model_class - fields = ("relationship",) + model = NestedRelatedSource + fields = ("relation",) serializer = RelatedResourceTypeSerializer() - field = serializer.fields["relationship"] + field = serializer.fields["relation"] assert get_related_resource_type(field) == output