Skip to content

Overriding RelatedField.to_representation causes error in Browseable API #5141

Description

@sloria

Checklist

  • I have verified that that issue exists against the master branch of Django REST framework.
  • I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
  • This is not a usage question. (Those should be directed to the discussion group instead.)
  • This cannot be dealt with as a third party library. (We prefer new functionality to be in the form of third party libraries where possible.)
  • I have reduced the issue to the simplest possible case.
  • I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

Steps to reproduce

Create a subclass of rest_framework.serializers.RelatedField that overrides to_representation such that it returns an unhashable type, such as a dict.

from rest_framework import serializers

class MyRelatedField(serializers.RelatedField):
    def to_representation(self, obj):
        return {
            'id': obj.pk,
        }


class MySerializer(serializers.Serializer):
    related_field = MyRelatedField()

Use this Serializer in a view, then visit the view's endpoint in the browsable API.

Expected behavior

The page renders.

Actual behavior

An uncaught exception is thrown:

TypeError: unhashable type: 'dict'
(44 additional frame(s) were not displayed)
...
  File "rest_framework/templatetags/rest_framework.py", line 91, in render_field
    return renderer.render_field(field, style)
  File "rest_framework/renderers.py", line 350, in render_field
    return template_render(template, context)
  File "rest_framework/compat.py", line 332, in template_render
    return template.render(context, request=request)
  File "rest_framework/relations.py", line 204, in iter_options
    self.get_choices(cutoff=self.html_cutoff),
  File "rest_framework/relations.py", line 191, in get_choices
    for item in queryset

TypeError: unhashable type: 'dict'

Additional notes

This is caused by the change in #4379 .

We are currently working around the issue by overriding get_choices. Since there is no hook to format the keys in the return value, we had to copy and paste RelatedField's implmentation.

class MyRelatedField(serializers.RelatedField):
    def to_representation(self, obj):
        return {
            'id': obj.pk,
        }

    # Below code is copied from rest_framework.serializers.RelatedField
    # because we need to override the keys in the return value
    def get_choices(self, cutoff=None):
        queryset = self.get_queryset()
        if queryset is None:
            # Ensure that field.choices returns something sensible
            # even when accessed with a read-only field.
            return {}

        if cutoff is not None:
            queryset = queryset[:cutoff]

        return OrderedDict([
            (
                # This is the only line that differs
                # from the RelatedField's implementation
                item.pk,
                self.display_value(item)
            )
            for item in queryset
        ])

One possible solution would be to expose a hook to format the keys of the returned dictionary (like display_value, except for the keys).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions