From ab3f9cf6e0f5b30f2256a59526481159bb8205d4 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 9 Feb 2016 21:50:29 +0100 Subject: [PATCH 1/6] Fix None UUID ForeignKey serialization --- rest_framework/fields.py | 2 ++ tests/models.py | 15 +++++++++++++++ tests/test_relations_pk.py | 21 ++++++++++++++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) 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..5d5d40968 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals +import uuid + from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -46,6 +48,11 @@ class ForeignKeyTarget(RESTFrameworkModel): name = models.CharField(max_length=100) +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) target = models.ForeignKey(ForeignKeyTarget, related_name='sources', @@ -62,6 +69,14 @@ class NullableForeignKeySource(RESTFrameworkModel): on_delete=models.CASCADE) +class NullableUUIDForeignKeySource(RESTFrameworkModel): + name = models.CharField(max_length=100) + target = models.ForeignKey(ForeignKeyTarget, null=True, blank=True, + related_name='nullable_sources', + verbose_name='Optional target object', + on_delete=models.CASCADE) + + # OneToOne class OneToOneTarget(RESTFrameworkModel): name = models.CharField(max_length=100) diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 169f7d9c5..658357b2f 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -6,7 +6,8 @@ from django.utils import six from rest_framework import serializers from tests.models import ( ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget, - NullableForeignKeySource, NullableOneToOneSource, OneToOneTarget + NullableForeignKeySource, NullableOneToOneSource, + NullableUUIDForeignKeySource, OneToOneTarget, UUIDForeignKeyTarget ) @@ -43,6 +44,18 @@ class NullableForeignKeySourceSerializer(serializers.ModelSerializer): fields = ('id', 'name', 'target') +# Nullable UUIDForeignKey +class NullableUUIDForeignKeySourceSerializer(serializers.ModelSerializer): + target = serializers.PrimaryKeyRelatedField( + pk_field=serializers.UUIDField(), + queryset=UUIDForeignKeyTarget.objects.all(), + allow_empty=True) + + class Meta: + model = NullableUUIDForeignKeySource + fields = ('id', 'name', 'target') + + # Nullable OneToOne class NullableOneToOneTargetSerializer(serializers.ModelSerializer): class Meta: @@ -432,6 +445,12 @@ class PKNullableForeignKeyTests(TestCase): ] self.assertEqual(serializer.data, expected) + 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) + class PKNullableOneToOneTests(TestCase): def setUp(self): From 50782ec99a6783537dd83dfb8b7fb4556eb5be9a Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 9 Feb 2016 22:26:24 +0100 Subject: [PATCH 2/6] Remove EOL Django 1.7 https://www.djangoproject.com/download/#supported-versions --- .travis.yml | 4 ---- tox.ini | 3 +-- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9aca4f3d4..cf3cddfec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,10 +15,6 @@ env: - TOX_ENV=py33-django18 - TOX_ENV=py32-django18 - TOX_ENV=py27-django18 - - TOX_ENV=py34-django17 - - TOX_ENV=py33-django17 - - TOX_ENV=py32-django17 - - TOX_ENV=py27-django17 matrix: fast_finish: true diff --git a/tox.ini b/tox.ini index 64b60327e..fe8ad4ce0 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ addopts=--tb=short [tox] envlist = py27-{lint,docs}, - {py27,py32,py33,py34}-django{17,18}, + {py27,py32,py33,py34}-django18, {py27,py34,py35}-django{19} [testenv] @@ -13,7 +13,6 @@ setenv = PYTHONDONTWRITEBYTECODE=1 PYTHONWARNINGS=once deps = - django17: Django==1.7.11 django18: Django==1.8.9 django19: Django==1.9.2 -rrequirements/requirements-testing.txt From 665f43434c9ef6c9eea216367e5de407a390e472 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 10 Feb 2016 09:12:03 +0100 Subject: [PATCH 3/6] Update release notes. --- docs/topics/release-notes.md | 7 +++++++ 1 file changed, 7 insertions(+) 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]. From 14e52ca9745e93870d2e64ef29a06c75b956c660 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 10 Feb 2016 10:07:11 +0100 Subject: [PATCH 4/6] Test deserialising data including `None` fk --- tests/test_relations_pk.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 658357b2f..ba75bd94f 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -49,7 +49,7 @@ class NullableUUIDForeignKeySourceSerializer(serializers.ModelSerializer): target = serializers.PrimaryKeyRelatedField( pk_field=serializers.UUIDField(), queryset=UUIDForeignKeyTarget.objects.all(), - allow_empty=True) + allow_null=True) class Meta: model = NullableUUIDForeignKeySource @@ -451,6 +451,11 @@ class PKNullableForeignKeyTests(TestCase): data = serializer.data self.assertEqual(data["target"], None) + 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): From 363bc4fbcc4098e7b25ecd42dd08cc836da6e57c Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 10 Feb 2016 17:20:21 +0100 Subject: [PATCH 5/6] Skip UUID tests for Django 1.7 --- tests/models.py | 23 ++++++++++++----------- tests/test_relations_pk.py | 32 +++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/tests/models.py b/tests/models.py index 5d5d40968..ec086d97e 100644 --- a/tests/models.py +++ b/tests/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import uuid +import django from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -47,10 +48,10 @@ class ManyToManySource(RESTFrameworkModel): class ForeignKeyTarget(RESTFrameworkModel): name = models.CharField(max_length=100) - -class UUIDForeignKeyTarget(RESTFrameworkModel): - uuid = models.UUIDField(primary_key=True, default=uuid.uuid4) - 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): @@ -68,13 +69,13 @@ class NullableForeignKeySource(RESTFrameworkModel): verbose_name='Optional target object', on_delete=models.CASCADE) - -class NullableUUIDForeignKeySource(RESTFrameworkModel): - name = models.CharField(max_length=100) - target = models.ForeignKey(ForeignKeyTarget, null=True, blank=True, - related_name='nullable_sources', - 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 diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index ba75bd94f..c3a5a5977 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -1,15 +1,22 @@ from __future__ import unicode_literals +import django +import pytest from django.test import TestCase from django.utils import six from rest_framework import serializers from tests.models import ( ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget, - NullableForeignKeySource, NullableOneToOneSource, - NullableUUIDForeignKeySource, OneToOneTarget, UUIDForeignKeyTarget + NullableForeignKeySource, NullableOneToOneSource, OneToOneTarget ) +if django.VERSION > (1, 7): + from tests.models import ( + NullableUUIDForeignKeySource, + UUIDForeignKeyTarget + ) + # ManyToMany class ManyToManyTargetSerializer(serializers.ModelSerializer): @@ -44,16 +51,17 @@ class NullableForeignKeySourceSerializer(serializers.ModelSerializer): fields = ('id', 'name', 'target') -# Nullable UUIDForeignKey -class NullableUUIDForeignKeySourceSerializer(serializers.ModelSerializer): - target = serializers.PrimaryKeyRelatedField( - pk_field=serializers.UUIDField(), - queryset=UUIDForeignKeyTarget.objects.all(), - allow_null=True) +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') + class Meta: + model = NullableUUIDForeignKeySource + fields = ('id', 'name', 'target') # Nullable OneToOne @@ -445,12 +453,14 @@ 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) From 793d03a88e5b9067d75164d7a17af7e835a4e3bf Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 10 Feb 2016 17:20:33 +0100 Subject: [PATCH 6/6] Revert "Remove EOL Django 1.7" This reverts commit 50782ec99a6783537dd83dfb8b7fb4556eb5be9a. --- .travis.yml | 4 ++++ tox.ini | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cf3cddfec..9aca4f3d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,10 @@ env: - TOX_ENV=py33-django18 - TOX_ENV=py32-django18 - TOX_ENV=py27-django18 + - TOX_ENV=py34-django17 + - TOX_ENV=py33-django17 + - TOX_ENV=py32-django17 + - TOX_ENV=py27-django17 matrix: fast_finish: true diff --git a/tox.ini b/tox.ini index fe8ad4ce0..64b60327e 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ addopts=--tb=short [tox] envlist = py27-{lint,docs}, - {py27,py32,py33,py34}-django18, + {py27,py32,py33,py34}-django{17,18}, {py27,py34,py35}-django{19} [testenv] @@ -13,6 +13,7 @@ setenv = PYTHONDONTWRITEBYTECODE=1 PYTHONWARNINGS=once deps = + django17: Django==1.7.11 django18: Django==1.8.9 django19: Django==1.9.2 -rrequirements/requirements-testing.txt