diff --git a/rest_framework/compat.py b/rest_framework/compat.py index e7a73adda..2606d8733 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -170,6 +170,21 @@ else: super(MaxLengthValidator, self).__init__(*args, **kwargs) +# ForeignKey's to_fields parameter was changed in Django 1.6 to an array named to_fields +# to_fields is an array but django lets you only set one to_field for ForeignKeys +# https://docs.djangoproject.com/en/1.7/ref/models/fields/#django.db.models.ForeignKey.to_field +# see also ForeignKey and ForeignObject in +# https://docs.djangoproject.com/en/1.7/_modules/django/db/models/fields/related/ + +if django.VERSION >= (1, 6): + def get_to_field(field): + # This should work for casual ForeignKeys + return field.to_fields[0] if len(field.to_fields) else None +else: + def get_to_field(field): + return field.rel.field_name + + # URLValidator only accepts `message` in 1.6+ if django.VERSION >= (1, 6): from django.core.validators import URLValidator diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 9e4fff449..2dba37d12 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -751,6 +751,7 @@ class ModelSerializer(Serializer): if ModelDurationField is not None: serializer_field_mapping[ModelDurationField] = DurationField serializer_related_field = PrimaryKeyRelatedField + serializer_related_to_field = SlugRelatedField serializer_url_field = HyperlinkedIdentityField serializer_choice_field = ChoiceField @@ -1044,6 +1045,11 @@ class ModelSerializer(Serializer): 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 f20628be0..503780227 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -198,7 +198,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) @@ -207,6 +207,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 19f751b04..e20a4b1c3 100644 --- a/rest_framework/utils/model_meta.py +++ b/rest_framework/utils/model_meta.py @@ -9,6 +9,7 @@ from collections import namedtuple from django.core.exceptions import ImproperlyConfigured from django.db import models from django.utils import six +from rest_framework.compat import get_to_field from rest_framework.compat import OrderedDict import inspect @@ -26,6 +27,7 @@ RelationInfo = namedtuple('RelationInfo', [ 'model_field', 'related_model', 'to_many', + 'to_field', 'has_through_model' ]) @@ -100,6 +102,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 +112,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 +138,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 +150,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