diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index bc24667de..152f44cdc 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -537,6 +537,14 @@ If you explicitly specify a relational field pointing to a ``ManyToManyField`` with a through model, be sure to set ``read_only`` to ``True``. +## The `display_value` method + +The `__str__` (`__unicode__` on Python 2) method of the model will be called to generate string representations of the objects used to populate the `choices` property. These choices are used to populate select HTML inputs in the browsable API. To provide customized representations for such inputs, override `display_value` of a `RelatedField` subclass. This method will receive a model object, and should return a string suitable for representing it. For example: + + class TrackPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): + def display_value(self, instance): + return 'Track: %s' % (instance.title) + --- # Third Party Packages diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 330d17c08..e0edc645f 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -148,7 +148,7 @@ class RelatedField(Field): return OrderedDict([ ( six.text_type(self.to_representation(item)), - six.text_type(item) + self.display_value(item) ) for item in queryset ]) @@ -160,6 +160,9 @@ class RelatedField(Field): def iter_options(self): return iter_options(self.grouped_choices) + def display_value(self, instance): + return six.text_type(instance) + class StringRelatedField(RelatedField): """ diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 00c6319ea..62a9cc05a 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -21,7 +21,7 @@ from django.utils import six from rest_framework import serializers from rest_framework.compat import DurationField as ModelDurationField -from rest_framework.compat import unicode_repr +from rest_framework.compat import OrderedDict, unicode_repr def dedent(blocktext): @@ -546,6 +546,50 @@ class TestRelationalFieldMappings(TestCase): self.assertEqual(unicode_repr(TestSerializer()), expected) +class DisplayValueTargetModel(models.Model): + name = models.CharField(max_length=100) + + def __str__(self): + return '%s Color' % (self.name) + + +class DisplayValueModel(models.Model): + color = models.ForeignKey(DisplayValueTargetModel) + + +class TestRelationalFieldDisplayValue(TestCase): + def setUp(self): + DisplayValueTargetModel.objects.bulk_create([ + DisplayValueTargetModel(name='Red'), + DisplayValueTargetModel(name='Yellow'), + DisplayValueTargetModel(name='Green'), + ]) + + def test_default_display_value(self): + class TestSerializer(serializers.ModelSerializer): + class Meta: + model = DisplayValueModel + + serializer = TestSerializer() + expected = OrderedDict([('1', 'Red Color'), ('2', 'Yellow Color'), ('3', 'Green Color')]) + self.assertEqual(serializer.fields['color'].choices, expected) + + def test_custom_display_value(self): + class TestField(serializers.PrimaryKeyRelatedField): + def display_value(self, instance): + return 'My %s Color' % (instance.name) + + class TestSerializer(serializers.ModelSerializer): + color = TestField(queryset=DisplayValueTargetModel.objects.all()) + + class Meta: + model = DisplayValueModel + + serializer = TestSerializer() + expected = OrderedDict([('1', 'My Red Color'), ('2', 'My Yellow Color'), ('3', 'My Green Color')]) + self.assertEqual(serializer.fields['color'].choices, expected) + + class TestIntegration(TestCase): def setUp(self): self.foreign_key_target = ForeignKeyTargetModel.objects.create(