From 3803e6032713c19faa8e867957985d5a0bb4421b Mon Sep 17 00:00:00 2001 From: Jonathan Hiles Date: Thu, 9 Feb 2023 12:31:46 +1000 Subject: [PATCH 1/7] fix: serialization of non-model dict objects (fixes #155 #722 #1126) --- rest_framework_json_api/renderers.py | 5 +---- rest_framework_json_api/utils.py | 13 +++++++++++++ tests/test_views.py | 26 ++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index db297870..b8140797 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -459,10 +459,7 @@ def build_json_resource_obj( resource_name = utils.get_resource_type_from_instance(resource_instance) resource_data = [ ("type", resource_name), - ( - "id", - encoding.force_str(resource_instance.pk) if resource_instance else None, - ), + ("id", utils.get_resource_id_from_instance(resource_instance)), ("attributes", cls.extract_attributes(fields, resource)), ] relationships = cls.extract_relationships(fields, resource, resource_instance) diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index 130894aa..e41fb271 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -308,6 +308,19 @@ def get_resource_type_from_serializer(serializer): ) +def get_resource_id_from_instance(instance): + """Returns the resource identifier for a given instance (`id` takes priority over `pk`).""" + if not instance: + return None + elif hasattr(instance, "id"): + return encoding.force_str(instance.id) + elif hasattr(instance, "pk"): + return encoding.force_str(instance.pk) + elif isinstance(instance, dict): + return instance.get("id", instance.get("pk", None)) + return None + + def get_included_resources(request, serializer=None): """Build a list of included resources.""" include_resources_param = request.query_params.get("include") if request else None diff --git a/tests/test_views.py b/tests/test_views.py index 42680d6a..52fb8c21 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -183,6 +183,28 @@ def test_patch(self, client): } } + @pytest.mark.urls(__name__) + def test_post_with_missing_id(self, client): + data = { + "data": { + "id": None, + "type": "custom", + "attributes": {"body": "hello"}, + } + } + + url = reverse("custom") + + response = client.post(url, data=data) + assert response.status_code == status.HTTP_200_OK + assert response.json() == { + "data": { + "type": "custom", + "id": None, + "attributes": {"body": "hello"}, + } + } + # Routing setup @@ -211,6 +233,10 @@ def patch(self, request, *args, **kwargs): serializer = CustomModelSerializer(CustomModel(request.data)) return Response(status=status.HTTP_200_OK, data=serializer.data) + def post(self, request, *args, **kwargs): + serializer = CustomModelSerializer(request.data) + return Response(status=status.HTTP_200_OK, data=serializer.data) + router = SimpleRouter() router.register(r"basic_models", BasicModelViewSet, basename="basic-model") From 82bcdc73e4136f18b819b2186381a2d9d6150560 Mon Sep 17 00:00:00 2001 From: Jonathan Hiles Date: Thu, 9 Feb 2023 12:38:00 +1000 Subject: [PATCH 2/7] docs: update authors and changelog --- AUTHORS | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS b/AUTHORS index e27ba5f5..a5e08553 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,6 +19,7 @@ Jeppe Fihl-Pearson Jerel Unruh Jonas Kiefer Jonas Metzener +Jonathan Hiles Jonathan Senecal Joseba Mendivil Kal diff --git a/CHANGELOG.md b/CHANGELOG.md index a509b262..cddac60c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ any parts of the framework not mentioned in the documentation should generally b ### Fixed * Refactored handling of the `sort` query parameter to fix duplicate declaration in the generated schema definition +* Serialization of non-model `Serializer` results, e.g. `dict` without a `pk` attribute ## [6.0.0] - 2022-09-24 From 0df12586c6aadd7fc4c6e66c61dfeedcc5264f73 Mon Sep 17 00:00:00 2001 From: Jonathan Hiles Date: Tue, 4 Apr 2023 15:41:50 +1000 Subject: [PATCH 3/7] refactor: prefer pk/id from resource data over resource instance test: add tests for `get_resource_id_from_instance` util function --- rest_framework_json_api/renderers.py | 6 ++++-- rest_framework_json_api/utils.py | 4 ++-- tests/test_utils.py | 30 ++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index 2041bb27..c10a85a9 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -443,10 +443,12 @@ def build_json_resource_obj( # Determine type from the instance if the underlying model is polymorphic if force_type_resolution: resource_name = utils.get_resource_type_from_instance(resource_instance) - resource_id = utils.get_resource_id_from_instance(resource_instance) + resource_id = utils.get_resource_id_from_instance( + resource + ) or utils.get_resource_id_from_instance(resource_instance) resource_data = { "type": resource_name, - "id": resource_id, + "id": force_str(resource_id) if resource_id is not None else None, "attributes": cls.extract_attributes(fields, resource), } relationships = cls.extract_relationships(fields, resource, resource_instance) diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index 6185f683..e8f03225 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -309,9 +309,9 @@ def get_resource_id_from_instance(instance): if not instance: return None elif hasattr(instance, "id"): - return encoding.force_str(instance.id) + return instance.id elif hasattr(instance, "pk"): - return encoding.force_str(instance.pk) + return instance.pk elif isinstance(instance, dict): return instance.get("id", instance.get("pk", None)) return None diff --git a/tests/test_utils.py b/tests/test_utils.py index 038e8ce9..41a8a541 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -14,6 +14,7 @@ format_resource_type, format_value, get_related_resource_type, + get_resource_id_from_instance, get_resource_name, get_resource_type_from_serializer, undo_format_field_name, @@ -392,6 +393,35 @@ class SerializerWithoutResourceName(serializers.Serializer): ) +class ObjectWithId: + id: int = 9 + + +class ObjectWithPk: + pk: int = 7 + + +class ObjectWithPkAndId(ObjectWithId, ObjectWithPk): + pass + + +@pytest.mark.parametrize( + "instance, expected", + [ + (None, None), + (BasicModel(id=5), 5), + (ObjectWithId(), 9), + (ObjectWithPk(), 7), + (ObjectWithPkAndId(), 9), + ({"id": 11}, 11), + ({"pk": 13}, 13), + ({"pk": 11, "id": 13}, 13), + ], +) +def test_get_resource_id_from_instance(instance, expected): + assert get_resource_id_from_instance(instance) == expected + + @pytest.mark.parametrize( "message,pointer,response,result", [ From 0d3a0bc2b5933eb4cd0f00b3caac3645075df736 Mon Sep 17 00:00:00 2001 From: Jonathan Hiles Date: Thu, 20 Apr 2023 17:04:38 +1000 Subject: [PATCH 4/7] refactor: add `get_id` support for resource identifier [wip] --- rest_framework_json_api/renderers.py | 5 +--- rest_framework_json_api/utils.py | 20 ++++++++------ tests/test_utils.py | 25 +++++++++-------- tests/test_views.py | 41 ++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 24 deletions(-) diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index c10a85a9..cde0300a 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -443,12 +443,9 @@ def build_json_resource_obj( # Determine type from the instance if the underlying model is polymorphic if force_type_resolution: resource_name = utils.get_resource_type_from_instance(resource_instance) - resource_id = utils.get_resource_id_from_instance( - resource - ) or utils.get_resource_id_from_instance(resource_instance) resource_data = { "type": resource_name, - "id": force_str(resource_id) if resource_id is not None else None, + "id": utils.get_resource_id_from_instance(resource_instance, resource), "attributes": cls.extract_attributes(fields, resource), } relationships = cls.extract_relationships(fields, resource, resource_instance) diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index e8f03225..4b738b63 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -304,16 +304,18 @@ def get_resource_type_from_serializer(serializer): ) -def get_resource_id_from_instance(instance): +def get_resource_id_from_instance(resource_instance, resource): """Returns the resource identifier for a given instance (`id` takes priority over `pk`).""" - if not instance: - return None - elif hasattr(instance, "id"): - return instance.id - elif hasattr(instance, "pk"): - return instance.pk - elif isinstance(instance, dict): - return instance.get("id", instance.get("pk", None)) + if resource and "id" in resource: + return resource["id"] and encoding.force_str(resource["id"]) or None + if resource_instance: + if hasattr(resource_instance, "get_id") and callable(resource_instance.get_id): + return encoding.force_str(resource_instance.get_id()) + return ( + hasattr(resource_instance, "pk") + and encoding.force_str(resource_instance.pk) + or None + ) return None diff --git a/tests/test_utils.py b/tests/test_utils.py index 41a8a541..edb705b5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -406,20 +406,23 @@ class ObjectWithPkAndId(ObjectWithId, ObjectWithPk): @pytest.mark.parametrize( - "instance, expected", + "resource_instance, resource, expected", [ - (None, None), - (BasicModel(id=5), 5), - (ObjectWithId(), 9), - (ObjectWithPk(), 7), - (ObjectWithPkAndId(), 9), - ({"id": 11}, 11), - ({"pk": 13}, 13), - ({"pk": 11, "id": 13}, 13), + (None, None, None), + (object(), {}, None), + (BasicModel(id=5), None, "5"), + (ObjectWithId(), {}, "9"), + (ObjectWithPk(), None, "7"), + (ObjectWithPkAndId(), None, "9"), + (None, {"id": 11}, "11"), + (object(), {"pk": 11}, None), + (ObjectWithId(), {"id": 11}, "11"), + (ObjectWithPk(), {"pk": 13}, "7"), + (ObjectWithPkAndId(), {"id": 12, "pk": 13}, "12"), ], ) -def test_get_resource_id_from_instance(instance, expected): - assert get_resource_id_from_instance(instance) == expected +def test_get_resource_id_from_instance(resource_instance, resource, expected): + assert get_resource_id_from_instance(resource_instance, resource) == expected @pytest.mark.parametrize( diff --git a/tests/test_views.py b/tests/test_views.py index 52fb8c21..7cc7912d 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -205,6 +205,28 @@ def test_post_with_missing_id(self, client): } } + @pytest.mark.urls(__name__) + def test_custom_id(self, client): + data = { + "data": { + "id": 2_193_102, + "type": "custom", + "attributes": {"body": "hello"}, + } + } + + url = reverse("custom-id") + + response = client.patch(url, data=data) + assert response.status_code == status.HTTP_200_OK + assert response.json() == { + "data": { + "type": "custom", + "id": "2176ce", # get_id() -> hex + "attributes": {"body": "hello"}, + } + } + # Routing setup @@ -224,6 +246,14 @@ class CustomModelSerializer(serializers.Serializer): id = serializers.IntegerField() +class CustomIdModelSerializer(serializers.Serializer): + body = serializers.CharField() + id = serializers.IntegerField() + + def get_id(self): + return hex(self.validated_data["id"])[2:] + + class CustomAPIView(APIView): parser_classes = [JSONParser] renderer_classes = [JSONRenderer] @@ -238,10 +268,21 @@ def post(self, request, *args, **kwargs): return Response(status=status.HTTP_200_OK, data=serializer.data) +class CustomIdAPIView(APIView): + parser_classes = [JSONParser] + renderer_classes = [JSONRenderer] + resource_name = "custom" + + def patch(self, request, *args, **kwargs): + serializer = CustomIdModelSerializer(CustomModel(request.data)) + return Response(status=status.HTTP_200_OK, data=serializer.data) + + router = SimpleRouter() router.register(r"basic_models", BasicModelViewSet, basename="basic-model") urlpatterns = [ path("custom", CustomAPIView.as_view(), name="custom"), + path("custom-id", CustomIdAPIView.as_view(), name="custom-id"), ] urlpatterns += router.urls From 3319e296caa5d766fdb3cce12d406822cba086fb Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Tue, 13 Jun 2023 17:22:53 +0400 Subject: [PATCH 5/7] Simplified so only pk on instance and id on resource are allowed. --- rest_framework_json_api/renderers.py | 2 +- rest_framework_json_api/utils.py | 4 +--- tests/test_utils.py | 26 +++++--------------------- tests/test_views.py | 8 ++++---- 4 files changed, 11 insertions(+), 29 deletions(-) diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index cde0300a..f7660208 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -445,7 +445,7 @@ def build_json_resource_obj( resource_name = utils.get_resource_type_from_instance(resource_instance) resource_data = { "type": resource_name, - "id": utils.get_resource_id_from_instance(resource_instance, resource), + "id": utils.get_resource_id(resource_instance, resource), "attributes": cls.extract_attributes(fields, resource), } relationships = cls.extract_relationships(fields, resource, resource_instance) diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index 4b738b63..2e57fbbd 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -304,13 +304,11 @@ def get_resource_type_from_serializer(serializer): ) -def get_resource_id_from_instance(resource_instance, resource): +def get_resource_id(resource_instance, resource): """Returns the resource identifier for a given instance (`id` takes priority over `pk`).""" if resource and "id" in resource: return resource["id"] and encoding.force_str(resource["id"]) or None if resource_instance: - if hasattr(resource_instance, "get_id") and callable(resource_instance.get_id): - return encoding.force_str(resource_instance.get_id()) return ( hasattr(resource_instance, "pk") and encoding.force_str(resource_instance.pk) diff --git a/tests/test_utils.py b/tests/test_utils.py index edb705b5..f2a3d176 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -14,7 +14,7 @@ format_resource_type, format_value, get_related_resource_type, - get_resource_id_from_instance, + get_resource_id, get_resource_name, get_resource_type_from_serializer, undo_format_field_name, @@ -393,36 +393,20 @@ class SerializerWithoutResourceName(serializers.Serializer): ) -class ObjectWithId: - id: int = 9 - - -class ObjectWithPk: - pk: int = 7 - - -class ObjectWithPkAndId(ObjectWithId, ObjectWithPk): - pass - - @pytest.mark.parametrize( "resource_instance, resource, expected", [ (None, None, None), (object(), {}, None), (BasicModel(id=5), None, "5"), - (ObjectWithId(), {}, "9"), - (ObjectWithPk(), None, "7"), - (ObjectWithPkAndId(), None, "9"), + (BasicModel(id=9), {}, "9"), (None, {"id": 11}, "11"), (object(), {"pk": 11}, None), - (ObjectWithId(), {"id": 11}, "11"), - (ObjectWithPk(), {"pk": 13}, "7"), - (ObjectWithPkAndId(), {"id": 12, "pk": 13}, "12"), + (BasicModel(id=6), {"id": 11}, "11"), ], ) -def test_get_resource_id_from_instance(resource_instance, resource, expected): - assert get_resource_id_from_instance(resource_instance, resource) == expected +def test_get_resource_id(resource_instance, resource, expected): + assert get_resource_id(resource_instance, resource) == expected @pytest.mark.parametrize( diff --git a/tests/test_views.py b/tests/test_views.py index 7cc7912d..47fec02a 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -206,7 +206,7 @@ def test_post_with_missing_id(self, client): } @pytest.mark.urls(__name__) - def test_custom_id(self, client): + def test_patch_with_custom_id(self, client): data = { "data": { "id": 2_193_102, @@ -247,11 +247,11 @@ class CustomModelSerializer(serializers.Serializer): class CustomIdModelSerializer(serializers.Serializer): + id = serializers.SerializerMethodField() body = serializers.CharField() - id = serializers.IntegerField() - def get_id(self): - return hex(self.validated_data["id"])[2:] + def get_id(self, obj): + return hex(obj.id)[2:] class CustomAPIView(APIView): From af3d9614e181b898435e9c6c1f567187c531392b Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Tue, 13 Jun 2023 22:10:22 +0400 Subject: [PATCH 6/7] Allow overwriting of resource id on resource related fields --- CHANGELOG.md | 17 +++++++++++- docs/usage.md | 39 ++++++++++++++++++++++++++++ rest_framework_json_api/relations.py | 14 ++++++---- tests/test_relations.py | 25 +++++++++++++++++- 4 files changed, 88 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d8932b3..8f4263f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,12 +22,27 @@ any parts of the framework not mentioned in the documentation should generally b * Replaced `OrderedDict` with `dict` which is also ordered since Python 3.7. * Compound document "include" parameter is only included in the OpenAPI schema if serializer implements `included_serializers`. +* Allowed overwriting of resource id by defining an `id` field on the serializer. + +Example: +```python +class CustomIdSerializer(serializers.Serializer): + id = serializers.CharField(source='name') + body = serializers.CharField() +``` +* Allowed overwriting resource id on resource related fields by creating custom `ResourceRelatedField`. + +Example: +```python +class CustomResourceRelatedField(relations.ResourceRelatedField): + def get_resource_id(self, value): + return value.name +``` ### Fixed * Refactored handling of the `sort` query parameter to fix duplicate declaration in the generated schema definition * Non-field serializer errors are given a source.pointer value of "/data". -* Serialization of non-model `Serializer` results, e.g. `dict` without a `pk` attribute * Fixed "id" field being added to /data/attributes in the OpenAPI schema when it is not rendered there. ## [6.0.0] - 2022-09-24 diff --git a/docs/usage.md b/docs/usage.md index 5da8846a..a9a7f29b 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -278,6 +278,45 @@ class MyModelSerializer(serializers.ModelSerializer): # ... ``` +### Overwriting the resource object's id + +Per default the primary key property `pk` on the instance is used as the resource identifier. + +It is possible to overwrite this by defining an `id` field on the serializer like: + +```python +class UserSerializer(serializers.ModelSerializer): + id = serializers.CharField(source='email') + name = serializers.CharField() + + class Meta: + model = User +``` + +This also works on generic serializers. + +In case you also use a model as a resource related field make sure to overwrite `get_resource_id` by creating a custom `ResourceRelatedField` class: + +```python +class UserResourceRelatedField(ResourceRelatedField): + def get_resource_id(self, value): + return value.email + + +class GroupSerializer(serializers.ModelSerializer): + user = UserResourceRelatedField(queryset=User.objects) + name = serializers.CharField() + + class Meta: + model = Group +``` + +
+ Note: + When using different id than primary key, make sure that your view + manages it properly by overwriting `get_object`. +
+ ### Setting resource identifier object type You may manually set resource identifier object type by using `resource_name` property on views, serializers, or diff --git a/rest_framework_json_api/relations.py b/rest_framework_json_api/relations.py index bb360ebb..32547253 100644 --- a/rest_framework_json_api/relations.py +++ b/rest_framework_json_api/relations.py @@ -247,17 +247,21 @@ def to_internal_value(self, data): return super().to_internal_value(data["id"]) def to_representation(self, value): - if getattr(self, "pk_field", None) is not None: - pk = self.pk_field.to_representation(value.pk) - else: - pk = value.pk - + pk = self.get_resource_id(value) resource_type = self.get_resource_type_from_included_serializer() if resource_type is None or not self._skip_polymorphic_optimization: resource_type = get_resource_type_from_instance(value) return {"type": resource_type, "id": str(pk)} + def get_resource_id(self, value): + """ + Get resource id of related field. + + Per default pk of value is returned. + """ + return super().to_representation(value) + def get_resource_type_from_included_serializer(self): """ Check to see it this resource has a different resource_name when diff --git a/tests/test_relations.py b/tests/test_relations.py index 74721cfa..ad4bebcb 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -10,9 +10,10 @@ HyperlinkedRelatedField, SerializerMethodHyperlinkedRelatedField, ) +from rest_framework_json_api.serializers import ModelSerializer, ResourceRelatedField from rest_framework_json_api.utils import format_link_segment from rest_framework_json_api.views import RelationshipView -from tests.models import BasicModel +from tests.models import BasicModel, ForeignKeySource, ForeignKeyTarget from tests.serializers import ( ForeignKeySourceSerializer, ManyToManySourceReadOnlySerializer, @@ -46,6 +47,28 @@ def test_serialize( assert serializer.data["target"] == expected + def test_get_resource_id(self, foreign_key_target): + class CustomResourceRelatedField(ResourceRelatedField): + def get_resource_id(self, value): + return value.name + + class CustomPkFieldSerializer(ModelSerializer): + target = CustomResourceRelatedField( + queryset=ForeignKeyTarget.objects, pk_field="name" + ) + + class Meta: + model = ForeignKeySource + fields = ("target",) + + serializer = CustomPkFieldSerializer(instance={"target": foreign_key_target}) + expected = { + "type": "ForeignKeyTarget", + "id": "Target", + } + + assert serializer.data["target"] == expected + @pytest.mark.parametrize( "format_type,pluralize_type,resource_type", [ From a11ed422ca6fe8524360ccae220508bcf521a489 Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Tue, 13 Jun 2023 22:18:20 +0400 Subject: [PATCH 7/7] Updated docs indent --- CHANGELOG.md | 25 +++++++++++++------------ docs/usage.md | 3 +-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f4263f8..972a3daf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,20 +24,21 @@ any parts of the framework not mentioned in the documentation should generally b implements `included_serializers`. * Allowed overwriting of resource id by defining an `id` field on the serializer. -Example: -```python -class CustomIdSerializer(serializers.Serializer): - id = serializers.CharField(source='name') - body = serializers.CharField() -``` + Example: + ```python + class CustomIdSerializer(serializers.Serializer): + id = serializers.CharField(source='name') + body = serializers.CharField() + ``` + * Allowed overwriting resource id on resource related fields by creating custom `ResourceRelatedField`. -Example: -```python -class CustomResourceRelatedField(relations.ResourceRelatedField): - def get_resource_id(self, value): - return value.name -``` + Example: + ```python + class CustomResourceRelatedField(relations.ResourceRelatedField): + def get_resource_id(self, value): + return value.name + ``` ### Fixed diff --git a/docs/usage.md b/docs/usage.md index a9a7f29b..a7dadcce 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -282,7 +282,7 @@ class MyModelSerializer(serializers.ModelSerializer): Per default the primary key property `pk` on the instance is used as the resource identifier. -It is possible to overwrite this by defining an `id` field on the serializer like: +It is possible to overwrite the resource id by defining an `id` field on the serializer like: ```python class UserSerializer(serializers.ModelSerializer): @@ -302,7 +302,6 @@ class UserResourceRelatedField(ResourceRelatedField): def get_resource_id(self, value): return value.email - class GroupSerializer(serializers.ModelSerializer): user = UserResourceRelatedField(queryset=User.objects) name = serializers.CharField()