diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index d505d8d3a8..35fb8fcf0d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -794,6 +794,7 @@ class ModelSerializer(Serializer): if ModelJSONField is not None: serializer_field_mapping[ModelJSONField] = JSONField serializer_related_field = PrimaryKeyRelatedField + serializer_related_to_field = SlugRelatedField serializer_url_field = HyperlinkedIdentityField serializer_choice_field = ChoiceField @@ -1129,6 +1130,11 @@ def build_relational_field(self, field_name, relation_info): field_class = self.serializer_related_field field_kwargs = get_relation_kwargs(field_name, relation_info) + to_field = field_kwargs.pop('to_field', None) + if to_field and to_field != 'id': + field_kwargs['slug_field'] = to_field + field_class = self.serializer_related_to_field + # `view_name` is only valid for hyperlinked relationships. if not issubclass(field_class, HyperlinkedRelatedField): field_kwargs.pop('view_name', None) diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index f109ad80d4..24fccec8cb 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -222,7 +222,7 @@ def get_relation_kwargs(field_name, relation_info): """ Creates a default instance of a flat relational field. """ - model_field, related_model, to_many, has_through_model = relation_info + model_field, related_model, to_many, to_field, has_through_model = relation_info kwargs = { 'queryset': related_model._default_manager, 'view_name': get_detail_view_name(related_model) @@ -231,6 +231,9 @@ def get_relation_kwargs(field_name, relation_info): if to_many: kwargs['many'] = True + if to_field: + kwargs['to_field'] = to_field + if has_through_model: kwargs['read_only'] = True kwargs.pop('queryset', None) diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py index d16630ed67..8eb00e4334 100644 --- a/rest_framework/utils/model_meta.py +++ b/rest_framework/utils/model_meta.py @@ -26,6 +26,7 @@ 'model_field', 'related_model', 'to_many', + 'to_field', 'has_through_model' ]) @@ -90,6 +91,10 @@ def _get_fields(opts): return fields +def _get_to_field(field): + return field.to_fields[0] if field.to_fields else None + + def _get_forward_relationships(opts): """ Returns an `OrderedDict` of field names to `RelationInfo`. @@ -100,6 +105,7 @@ def _get_forward_relationships(opts): model_field=field, related_model=_resolve_model(field.rel.to), to_many=False, + to_field=_get_to_field(field), has_through_model=False ) @@ -109,6 +115,8 @@ def _get_forward_relationships(opts): model_field=field, related_model=_resolve_model(field.rel.to), to_many=True, + # manytomany do not have to_fields + to_field=None, has_through_model=( not field.rel.through._meta.auto_created ) @@ -133,6 +141,7 @@ def _get_reverse_relationships(opts): model_field=None, related_model=related, to_many=relation.field.rel.multiple, + to_field=_get_to_field(relation.field), has_through_model=False ) @@ -144,6 +153,8 @@ def _get_reverse_relationships(opts): model_field=None, related_model=related, to_many=True, + # manytomany do not have to_fields + to_field=None, has_through_model=( (getattr(relation.field.rel, 'through', None) is not None) and not relation.field.rel.through._meta.auto_created