Skip to content

Commit

Permalink
Custom serialization of PrimaryKeyRelatedField values
Browse files Browse the repository at this point in the history
Adds a 'pk_field' parameter which can be used to proxy serialization and
deserialization of arbitrary primary key values.
  • Loading branch information
ovangle committed Jun 14, 2015
1 parent b088944 commit a1e0bae
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/api-guide/relations.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ By default this field is read-write, although you can change this behavior using
* `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`.
* `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.


## HyperlinkedRelatedField

Expand Down
8 changes: 8 additions & 0 deletions rest_framework/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,16 @@ class PrimaryKeyRelatedField(RelatedField):
'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'),
}

def __init__(self, **kwargs):
self.pk_field = kwargs.pop('pk_field', None)
super(PrimaryKeyRelatedField, self).__init__(**kwargs)

def use_pk_only_optimization(self):
return True

def to_internal_value(self, data):
if self.pk_field is not None:
data = self.pk_field.to_internal_value(data)
try:
return self.get_queryset().get(pk=data)
except ObjectDoesNotExist:
Expand All @@ -146,6 +152,8 @@ def to_internal_value(self, data):
self.fail('incorrect_type', data_type=type(data).__name__)

def to_representation(self, value):
if self.pk_field is not None:
return self.pk_field.to_representation(value.pk)
return value.pk


Expand Down
29 changes: 29 additions & 0 deletions tests/test_relations.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import uuid
from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
from django.core.exceptions import ImproperlyConfigured
from django.utils.datastructures import MultiValueDict
Expand Down Expand Up @@ -48,6 +49,34 @@ def test_pk_representation(self):
assert representation == self.instance.pk


class TestProxiedPrimaryKeyRelatedField(APISimpleTestCase):
def setUp(self):
self.queryset = MockQueryset([
MockObject(pk=uuid.UUID(int=0), name='foo'),
MockObject(pk=uuid.UUID(int=1), name='bar'),
MockObject(pk=uuid.UUID(int=2), name='baz')
])
self.instance = self.queryset.items[2]
self.field = serializers.PrimaryKeyRelatedField(
queryset=self.queryset,
pk_field=serializers.UUIDField(format='int')
)

def test_pk_related_lookup_exists(self):
instance = self.field.to_internal_value(self.instance.pk.int)
assert instance is self.instance

def test_pk_related_lookup_does_not_exist(self):
with pytest.raises(serializers.ValidationError) as excinfo:
self.field.to_internal_value(4)
msg = excinfo.value.detail[0]
assert msg == 'Invalid pk "00000000-0000-0000-0000-000000000004" - object does not exist.'

def test_pk_representation(self):
representation = self.field.to_representation(self.instance)
assert representation == self.instance.pk.int


class TestHyperlinkedIdentityField(APISimpleTestCase):
def setUp(self):
self.instance = MockObject(pk=1, name='foo')
Expand Down

0 comments on commit a1e0bae

Please sign in to comment.