diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index 8695b2c1e..d968d6b1e 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -119,6 +119,50 @@ By default this field is read-write, although you can change this behavior using * `pk_field` - Set to a field to control serialization/deserialization of the primary key's value. For example, `pk_field=UUIDField(format='hex')` would serialize a UUID primary key into its compact hex representation. +## SerializableRelatedField + +`SerializableRelatedField` may be used to represent the target of the relationship using serializer passed as argument. + +For example, if we pass `TrackSerializer` the following serializer: + + class AlbumSerializer(serializers.ModelSerializer): + tracks = serializers.SerializableRelatedField(many=True, read_only=True) + + class Meta: + model = Album + fields = ('album_name', 'artist', 'tracks') + +Would serialize to a representation like this: + + { + 'album_name': 'The Roots', + 'artist': 'Undun', + 'tracks': [ + { + 'order': 1, + 'title': 'Public Service Announcement', + 'duration': 245, + }, + { + 'order': 2, + 'title': 'What More Can I Say', + 'duration': 264, + }, + ... + ] + } + +By default this field take queryset from passed `serializer`. + +**Arguments**: + +* `queryset` - The queryset used for model instance lookups when validating the field input. Relationships must either set a queryset explicitly, or set `read_only=True`. +* `many` - If applied to a to-many relationship, you should set this argument to `True`. +* `allow_null` - If set to `True`, the field will accept values of `None` or the empty string for nullable relationships. Defaults to `False`. +* `serializer` - Required field, serializer to represent the target of the relationship +* `serializer_params` - Parameters, passed to serializer + + ## HyperlinkedRelatedField `HyperlinkedRelatedField` may be used to represent the target of the relationship using a hyperlink. diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 572b69170..dfd69db8f 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -231,6 +231,36 @@ class PrimaryKeyRelatedField(RelatedField): return value.pk +class SerializableRelatedField(RelatedField): + default_error_messages = { + 'required': _('This field is required.'), + 'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'), + 'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'), + } + + def __init__(self, **kwargs): + self.serializer = kwargs.pop('serializer', None) + assert self.serializer is not None, ( + 'SerializableRelatedField field must provide a `serializer` argument' + ) + self.serializer_params = kwargs.pop('serializer_params', dict()) + if 'queryset' not in kwargs: + kwargs['queryset'] = self.serializer.Meta.model.objects.all() + kwargs['style'] = {'base_template': 'input.html', 'input_type': 'numeric'} + super(SerializableRelatedField, self).__init__(**kwargs) + + def to_internal_value(self, data): + try: + return self.get_queryset().get(pk=data) + except ObjectDoesNotExist: + self.fail('does_not_exist', pk_value=data) + except (TypeError, ValueError): + self.fail('incorrect_type', data_type=type(data).__name__) + + def to_representation(self, value): + return self.serializer(instance=value, context=self.context, **self.serializer_params).data + + class HyperlinkedRelatedField(RelatedField): lookup_field = 'pk' view_name = None