Skip to content

Unable to render regular Serializer (missing PK) #1126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
axieum opened this issue Feb 9, 2023 · 2 comments · Fixed by #1127
Closed

Unable to render regular Serializer (missing PK) #1126

axieum opened this issue Feb 9, 2023 · 2 comments · Fixed by #1127
Labels

Comments

@axieum
Copy link
Contributor

axieum commented Feb 9, 2023

It appears that the JSON renderer expects every serializer to be a ModelSerializer, preventing regular Serializer use without a model.

Reproduction:

from typing import TYPE_CHECKING

from rest_framework.permissions import AllowAny
from rest_framework.fields import CharField, Field
from rest_framework.views import Response
from rest_framework.viewsets import GenericViewSet
from rest_framework_json_api.serializers import Serializer

from .serializers import HealthStatusSerializer

if TYPE_CHECKING:
    from rest_framework. request import Request


class HealthStatusSerializer(Serializer):
    """The health status of all services"""

    def get_fields(self) -> dict[str, Field]:
        return {"Database: default": CharField()}  # for demo purposes - usually populate from installed health checks

    class Meta:
        resource_name = "HealthStatus"


class HealthStatusAPIView(GenericViewSet):
    """A health status view"""

    serializer = HealthStatusSerializer
    permission_clases = [AllowAny]
    pagination_class = None  # remove pagination
    filter_backends = []  # remove default filters

    def retrieve(request: Request, **kwargs) -> Response:
        """Retrieves the health status of all services"""

        errors: bool = False  # for demo purposes - usually check status of installed health checks
        return Response(
            self.get_serializer({"Database: default": "working"}).data,
            status_code=200 if not errors else 500,
        )

Stacktrace:

Traceback (most recent call last):
  File ".../django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File ".../django/core/handlers/base.py", line 220, in _get_response
    response = response.render()
  File ".../django/template/response.py", line 114, in render
    self. content = self.rendered_content
  File ".../rest_framework/response.py", line 70, in rendered_content
    ret = renderer. data, accepted_media_type, context)
  File ".../rest_framework_json_api/renderers.py", line 602, in render
    json_api_data = self.build_json_resource_obj(
  File ".../rest_framework_json_api/renderers.py", line 464, in build_json_resource_obj
    encoding. force_str(resource_instance.pk) if resource_instance else None,
AttributeError: 'dict' object has no attribute 'pk'

Suspected code:

resource_data = [
("type", resource_name),
(
"id",
encoding.force_str(resource_instance.pk) if resource_instance else None,
),
("attributes", cls.extract_attributes(fields, resource)),
]

Suggestion:

        resource_data = [
            ("type", resource_name),
            (
                "id",
-                encoding.force_str(resource_instance.pk) if resource_instance else None,
+                encoding.force_str(resource_instance.pk) if hasattr(resource_instance, "pk") else None,
            ),
            ("attributes", cls.extract_attributes(fields, resource)),
        ]
@axieum
Copy link
Contributor Author

axieum commented Feb 9, 2023

A temporary workaround is to wrap the dictionary passed to the serializer in an object that proxies the pk property.

from typing import Any

# See https://github.com/django-json-api/django-rest-framework-json-api/issues/1126
class SerializerResult(dict):
    """The result of a non-model serializer to workaround DJA expecting a `pk` attr."""

    def __init__(self, other: dict | None = None, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.update(other)

    @property
    def pk(self) -> Any | None:
        return self.get("id", self.get("pk", None))

Then use it like so -

return Response(
    self.get_serializer(
        SerializerResult(
            {
                # ...
            }
        )
    )
)

References:

The class used in the tests.

class CustomModel:
def __init__(self, response_dict):
for k, v in response_dict.items():
setattr(self, k, v)
@property
def pk(self):
return self.id if hasattr(self, "id") else None

@sliverc sliverc changed the title Unable to serialize without a model: dict object has no attribute pk Unable to render regular Serializer (missing PK) Feb 13, 2023
@sliverc
Copy link
Member

sliverc commented Feb 13, 2023

Thanks for bring awareness for this issue again and working on it. See my #1127 (comment) to see how we can go from here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants