Skip to content

Fix TypeError in RelatedField.get_choices() when to_representation returns unhashable type - #9985

Open
LeSingh1 wants to merge 9 commits into
encode:mainfrom
LeSingh1:fix/related-field-unhashable-get-choices
Open

Fix TypeError in RelatedField.get_choices() when to_representation returns unhashable type#9985
LeSingh1 wants to merge 9 commits into
encode:mainfrom
LeSingh1:fix/related-field-unhashable-get-choices

Conversation

@LeSingh1

Copy link
Copy Markdown

Summary

Fixes #5141.

When a user subclasses RelatedField and overrides to_representation() to return an unhashable type (e.g. a dict), visiting the endpoint in the Browsable API raises:

TypeError: unhashable type: 'dict'
  File "rest_framework/relations.py", in get_choices
    self.to_representation(item): self.display_value(item) for item in queryset

This happens because get_choices() uses the return value of to_representation() directly as a dictionary key, which requires it to be hashable.

Fix

Wrap the key in str() before inserting into the dict:

# Before
return {
    self.to_representation(item): self.display_value(item) for item in queryset
}

# After
return {
    str(self.to_representation(item)): self.display_value(item) for item in queryset
}

This is safe because:

  • The choices dict keys are used as HTML <option value="..."> values, which are always strings in HTML.
  • iter_options() uses these keys as option.value for the Browsable API select widget — string values are correct here.
  • The existing behaviour for all built-in RelatedField subclasses is unchanged, since str() on an int (pk), str, or Hyperlink returns the same value.

Reproduction

from rest_framework import serializers

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

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

Before this fix, visiting this endpoint in the Browsable API raises TypeError: unhashable type: 'dict'. After this fix it renders correctly.

LeSingh1 and others added 2 commits June 20, 2026 12:25
…turns unhashable type

When a subclass overrides to_representation() to return an unhashable
type such as a dict, RelatedField.get_choices() raises:

  TypeError: unhashable type: 'dict'

because the return value is used directly as a dictionary key.

Fix by converting the representation to str() before using it as the
key. This is safe since HTML select option values are always strings.

Fixes encode#5141

@auvipy auvipy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the tests are failing

@auvipy
auvipy requested a balanced review from Copilot September 7, 2026 06:55
@auvipy auvipy added this to the 3.18 milestone Sep 7, 2026
@auvipy auvipy added the Bug label Sep 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Narrow the TypeError handling so display_value() is computed once and its errors are not misclassified.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Fixes Browsable API failures when related-field representations are unhashable.

Changes:

  • Falls back to string keys for unhashable representations.
  • Adds regression and compatibility tests.
File summaries
File Description
rest_framework/relations.py Handles unhashable choice keys. Moderate issue: the broad try can misclassify and repeat display_value() errors.
tests/test_relations.py Tests unhashable and existing hashable behavior.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread rest_framework/relations.py Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The import-blocking indentation error and overly broad TypeError handling must be fixed.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

rest_framework/relations.py:208

  • Catching TypeError around the whole insertion also swallows errors raised by a hashable key's equality comparison during a collision, incorrectly treating that key as unhashable. Probe hash(value) separately, then let unrelated insertion errors propagate.

tests/test_relations.py:528

  • This constructs a read-only field and then manually adds a queryset, bypassing the constructor invariant that read-only relational fields must not have querysets. Read-only fields are also excluded from HTML forms, so this does not model the reported Browsable API path; use a supported writable field configuration instead.
        field = DictRelatedField(read_only=True)
        field.queryset = queryset

tests/test_relations.py:544

  • This preservation assertion contradicts the PR description's advertised unconditional str(...) conversion. Preserving non-string RelatedField.choices keys is an established compatibility requirement from #4379, so the implementation and test are appropriate, but the PR description should be updated to describe conditional stringification rather than claiming every key is converted.
        # Keys should remain ints (not stringified), preserving existing behavior
        assert list(choices.keys()) == [1, 2]
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread rest_framework/relations.py Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@browniebroke browniebroke changed the title Fix TypeError in RelatedField.get_choices() when to_representation returns unhashable type Fix TypeError in RelatedField.get_choices() when to_representation returns unhashable type Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

Overriding RelatedField.to_representation causes error in Browseable API

3 participants