diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index d21f8e51b..98f143d25 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,6 +40,13 @@ You can determine your currently installed version using `pip freeze`: ## 3.3.x series +### 3.4 + +**Unreleased** + +* Fixed null foreign keys targeting UUIDField primary keys. ([#3915][gh3915]) +* Dropped support for EOL Django 1.7 ([#3915][gh3915]) + ### 3.3.2 **Date**: [14th December 2015][3.3.2-milestone]. diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6d5962c8e..c700b85e8 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -778,6 +778,8 @@ class UUIDField(Field): return data def to_representation(self, value): + if value is None: + return None if self.uuid_format == 'hex_verbose': return str(value) else: diff --git a/tests/models.py b/tests/models.py index 8ec274d8b..ec086d97e 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,5 +1,8 @@ from __future__ import unicode_literals +import uuid + +import django from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -45,6 +48,11 @@ class ManyToManySource(RESTFrameworkModel): class ForeignKeyTarget(RESTFrameworkModel): name = models.CharField(max_length=100) +if django.VERSION > (1, 7): + class UUIDForeignKeyTarget(RESTFrameworkModel): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4) + name = models.CharField(max_length=100) + class ForeignKeySource(RESTFrameworkModel): name = models.CharField(max_length=100) @@ -61,6 +69,14 @@ class NullableForeignKeySource(RESTFrameworkModel): verbose_name='Optional target object', on_delete=models.CASCADE) +if django.VERSION > (1, 7): + class NullableUUIDForeignKeySource(RESTFrameworkModel): + name = models.CharField(max_length=100) + target = models.ForeignKey(UUIDForeignKeyTarget, null=True, blank=True, + related_name='nullable_sources', + verbose_name='Optional target object', + on_delete=models.CASCADE) + # OneToOne class OneToOneTarget(RESTFrameworkModel): diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 169f7d9c5..c3a5a5977 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals +import django +import pytest from django.test import TestCase from django.utils import six @@ -9,6 +11,12 @@ from tests.models import ( NullableForeignKeySource, NullableOneToOneSource, OneToOneTarget ) +if django.VERSION > (1, 7): + from tests.models import ( + NullableUUIDForeignKeySource, + UUIDForeignKeyTarget + ) + # ManyToMany class ManyToManyTargetSerializer(serializers.ModelSerializer): @@ -43,6 +51,19 @@ class NullableForeignKeySourceSerializer(serializers.ModelSerializer): fields = ('id', 'name', 'target') +if django.VERSION > (1, 7): + # Nullable UUIDForeignKey + class NullableUUIDForeignKeySourceSerializer(serializers.ModelSerializer): + target = serializers.PrimaryKeyRelatedField( + pk_field=serializers.UUIDField(), + queryset=UUIDForeignKeyTarget.objects.all(), + allow_null=True) + + class Meta: + model = NullableUUIDForeignKeySource + fields = ('id', 'name', 'target') + + # Nullable OneToOne class NullableOneToOneTargetSerializer(serializers.ModelSerializer): class Meta: @@ -432,6 +453,19 @@ class PKNullableForeignKeyTests(TestCase): ] self.assertEqual(serializer.data, expected) + @pytest.mark.skipif(django.VERSION < (1, 8), reason="UUIDField not available") + def test_null_uuid_foreign_key_serializes_as_none(self): + source = NullableUUIDForeignKeySource(name='Source') + serializer = NullableUUIDForeignKeySourceSerializer(source) + data = serializer.data + self.assertEqual(data["target"], None) + + @pytest.mark.skipif(django.VERSION < (1, 8), reason="UUIDField not available") + def test_nullable_uuid_foreign_key_is_valid_when_none(self): + data = {"name": "Source", "target": None} + serializer = NullableUUIDForeignKeySourceSerializer(data=data) + self.assertTrue(serializer.is_valid(), serializer.errors) + class PKNullableOneToOneTests(TestCase): def setUp(self):