Skip to content

Commit e8bca56

Browse files
committed
Custom serialization of PrimaryKeyRelatedField values
Adds a 'pk_field' parameter which can be used to proxy serialization and deserialization of arbitrary primary key values.
1 parent cdd3aca commit e8bca56

File tree

3 files changed

+39
-0
lines changed

3 files changed

+39
-0
lines changed

docs/api-guide/relations.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ By default this field is read-write, although you can change this behavior using
116116
* `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`.
117117
* `many` - If applied to a to-many relationship, you should set this argument to `True`.
118118
* `allow_null` - If set to `True`, the field will accept values of `None` or the empty string for nullable relationships. Defaults to `False`.
119+
* `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.
120+
119121

120122
## HyperlinkedRelatedField
121123

rest_framework/relations.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,16 @@ class PrimaryKeyRelatedField(RelatedField):
134134
'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'),
135135
}
136136

137+
def __init__(self, **kwargs):
138+
self.pk_field = kwargs.pop('pk_field', None)
139+
super(PrimaryKeyRelatedField, self).__init__(**kwargs)
140+
137141
def use_pk_only_optimization(self):
138142
return True
139143

140144
def to_internal_value(self, data):
145+
if self.pk_field is not None:
146+
data = self.pk_field.to_internal_value(data)
141147
try:
142148
return self.get_queryset().get(pk=data)
143149
except ObjectDoesNotExist:
@@ -146,6 +152,8 @@ def to_internal_value(self, data):
146152
self.fail('incorrect_type', data_type=type(data).__name__)
147153

148154
def to_representation(self, value):
155+
if self.pk_field is not None:
156+
return self.pk_field.to_representation(value.pk)
149157
return value.pk
150158

151159

tests/test_relations.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
23
from django.core.exceptions import ImproperlyConfigured
34
from django.utils.datastructures import MultiValueDict
@@ -48,6 +49,34 @@ def test_pk_representation(self):
4849
assert representation == self.instance.pk
4950

5051

52+
class TestProxiedPrimaryKeyRelatedField(APISimpleTestCase):
53+
def setUp(self):
54+
self.queryset = MockQueryset([
55+
MockObject(pk=uuid.UUID(int=0), name='foo'),
56+
MockObject(pk=uuid.UUID(int=1), name='bar'),
57+
MockObject(pk=uuid.UUID(int=2), name='baz')
58+
])
59+
self.instance = self.queryset.items[2]
60+
self.field = serializers.PrimaryKeyRelatedField(
61+
queryset=self.queryset,
62+
pk_field=serializers.UUIDField(format='int')
63+
)
64+
65+
def test_pk_related_lookup_exists(self):
66+
instance = self.field.to_internal_value(self.instance.pk.int)
67+
assert instance is self.instance
68+
69+
def test_pk_related_lookup_does_not_exist(self):
70+
with pytest.raises(serializers.ValidationError) as excinfo:
71+
self.field.to_internal_value(4)
72+
msg = excinfo.value.detail[0]
73+
assert msg == 'Invalid pk "00000000-0000-0000-0000-000000000004" - object does not exist.'
74+
75+
def test_pk_representation(self):
76+
representation = self.field.to_representation(self.instance)
77+
assert representation == self.instance.pk.int
78+
79+
5180
class TestHyperlinkedIdentityField(APISimpleTestCase):
5281
def setUp(self):
5382
self.instance = MockObject(pk=1, name='foo')

0 commit comments

Comments
 (0)