Custom serialization of PrimaryKeyRelatedField values

Adds a 'pk_field' parameter which can be used to proxy serialization and
deserialization of arbitrary primary key values.
This commit is contained in:
Thomas Stephenson 2015-04-02 15:45:12 +11:00
parent b0889446a5
commit a1e0bae9da
3 changed files with 39 additions and 0 deletions

View File

@ -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`. * `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`. * `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`. * `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 ## HyperlinkedRelatedField

View File

@ -134,10 +134,16 @@ class PrimaryKeyRelatedField(RelatedField):
'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'), '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): def use_pk_only_optimization(self):
return True return True
def to_internal_value(self, data): def to_internal_value(self, data):
if self.pk_field is not None:
data = self.pk_field.to_internal_value(data)
try: try:
return self.get_queryset().get(pk=data) return self.get_queryset().get(pk=data)
except ObjectDoesNotExist: except ObjectDoesNotExist:
@ -146,6 +152,8 @@ class PrimaryKeyRelatedField(RelatedField):
self.fail('incorrect_type', data_type=type(data).__name__) self.fail('incorrect_type', data_type=type(data).__name__)
def to_representation(self, value): def to_representation(self, value):
if self.pk_field is not None:
return self.pk_field.to_representation(value.pk)
return value.pk return value.pk

View File

@ -1,3 +1,4 @@
import uuid
from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
@ -48,6 +49,34 @@ class TestPrimaryKeyRelatedField(APISimpleTestCase):
assert representation == self.instance.pk 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): class TestHyperlinkedIdentityField(APISimpleTestCase):
def setUp(self): def setUp(self):
self.instance = MockObject(pk=1, name='foo') self.instance = MockObject(pk=1, name='foo')