From e84ce60a0da77c0e07e0d6e5f627694ae3f4422f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 2 Nov 2012 19:11:40 +0000 Subject: [PATCH 1/3] Initial PK relationship tests --- rest_framework/tests/pk_relations.py | 86 ++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 rest_framework/tests/pk_relations.py diff --git a/rest_framework/tests/pk_relations.py b/rest_framework/tests/pk_relations.py new file mode 100644 index 000000000..eca534c2f --- /dev/null +++ b/rest_framework/tests/pk_relations.py @@ -0,0 +1,86 @@ +from django.db import models +from django.test import TestCase +from rest_framework import serializers + + +class Target(models.Model): + name = models.CharField(max_length=100) + + +class Source(models.Model): + name = models.CharField(max_length=100) + targets = models.ManyToManyField(Target, related_name='sources') + + +class TargetSerializer(serializers.ModelSerializer): + sources = serializers.ManyPrimaryKeyRelatedField() + + class Meta: + fields = ('id', 'name', 'sources') + model = Target + + +class SourceSerializer(serializers.ModelSerializer): + class Meta: + model = Source + + +# TODO: Add test that .data cannot be accessed prior to .is_valid + +class PrimaryKeyManyToManyTests(TestCase): + def setUp(self): + for idx in range(1, 4): + target = Target(name='target-%d' % idx) + target.save() + source = Source(name='source-%d' % idx) + source.save() + for target in Target.objects.all(): + source.targets.add(target) + + def test_many_to_many_retrieve(self): + serializer = SourceSerializer(instance=Source.objects.all()) + expected = [ + {'id': 1, 'name': u'source-1', 'targets': [1]}, + {'id': 2, 'name': u'source-2', 'targets': [1, 2]}, + {'id': 3, 'name': u'source-3', 'targets': [1, 2, 3]} + ] + self.assertEquals(serializer.data, expected) + + def test_reverse_many_to_many_retrieve(self): + serializer = TargetSerializer(instance=Target.objects.all()) + expected = [ + {'id': 1, 'name': u'target-1', 'sources': [1, 2, 3]}, + {'id': 2, 'name': u'target-2', 'sources': [2, 3]}, + {'id': 3, 'name': u'target-3', 'sources': [3]} + ] + self.assertEquals(serializer.data, expected) + + def test_many_to_many_update(self): + data = {'id': 1, 'name': u'source-1', 'targets': [1, 2, 3]} + serializer = SourceSerializer(data, instance=Source.objects.get(pk=1)) + self.assertTrue(serializer.is_valid()) + self.assertEquals(serializer.data, data) + + # Ensure source 1 is updated, and everything else is as expected + serializer = SourceSerializer(instance=Source.objects.all()) + expected = [ + {'id': 1, 'name': u'source-1', 'targets': [1, 2, 3]}, + {'id': 2, 'name': u'source-2', 'targets': [1, 2]}, + {'id': 3, 'name': u'source-3', 'targets': [1, 2, 3]} + ] + self.assertEquals(serializer.data, expected) + + def test_reverse_many_to_many_update(self): + data = {'id': 1, 'name': u'target-0', 'sources': [1]} + serializer = TargetSerializer(data, instance=Target.objects.get(pk=1)) + self.assertTrue(serializer.is_valid()) + self.assertEquals(serializer.data, data) + + # Ensure target 1 is updated, and everything else is as expected + serializer = TargetSerializer(instance=Target.objects.all()) + expected = [ + {'id': 1, 'name': u'target-1', 'sources': [1]}, + {'id': 2, 'name': u'target-2', 'sources': [2, 3]}, + {'id': 3, 'name': u'target-3', 'sources': [3]} + ] + self.assertEquals(serializer.data, expected) From 6eaec7a0eccabb3e1b010d07633632e8a3ecd86f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 2 Nov 2012 20:53:33 +0000 Subject: [PATCH 2/3] foreign key tests --- rest_framework/fields.py | 13 ++- rest_framework/tests/pk_relations.py | 139 +++++++++++++++++++++++---- 2 files changed, 132 insertions(+), 20 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 375d7a465..965e22c4d 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -383,7 +383,8 @@ class PrimaryKeyRelatedField(RelatedField): try: return self.queryset.get(pk=data) except ObjectDoesNotExist: - raise ValidationError('Invalid hyperlink - object does not exist.') + msg = "Invalid pk '%s' - object does not exist." % smart_unicode(data) + raise ValidationError(msg) def field_to_native(self, obj, field_name): try: @@ -430,6 +431,16 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): # Forward relationship return [self.to_native(item.pk) for item in queryset.all()] + def from_native(self, data): + if self.queryset is None: + raise Exception('Writable related fields must include a `queryset` argument') + + try: + return self.queryset.get(pk=data) + except ObjectDoesNotExist: + msg = "Invalid pk '%s' - object does not exist." % smart_unicode(data) + raise ValidationError(msg) + ### Slug relationships diff --git a/rest_framework/tests/pk_relations.py b/rest_framework/tests/pk_relations.py index eca534c2f..a09343448 100644 --- a/rest_framework/tests/pk_relations.py +++ b/rest_framework/tests/pk_relations.py @@ -3,26 +3,50 @@ from django.test import TestCase from rest_framework import serializers -class Target(models.Model): +# ManyToMany + +class ManyToManyTarget(models.Model): name = models.CharField(max_length=100) -class Source(models.Model): +class ManyToManySource(models.Model): name = models.CharField(max_length=100) - targets = models.ManyToManyField(Target, related_name='sources') + targets = models.ManyToManyField(ManyToManyTarget, related_name='sources') -class TargetSerializer(serializers.ModelSerializer): - sources = serializers.ManyPrimaryKeyRelatedField() +class ManyToManyTargetSerializer(serializers.ModelSerializer): + sources = serializers.ManyPrimaryKeyRelatedField(queryset=ManyToManySource.objects.all()) class Meta: - fields = ('id', 'name', 'sources') - model = Target + model = ManyToManyTarget -class SourceSerializer(serializers.ModelSerializer): +class ManyToManySourceSerializer(serializers.ModelSerializer): class Meta: - model = Source + model = ManyToManySource + + +# ForeignKey + +class ForeignKeyTarget(models.Model): + name = models.CharField(max_length=100) + + +class ForeignKeySource(models.Model): + name = models.CharField(max_length=100) + target = models.ForeignKey(ForeignKeyTarget, related_name='sources') + + +class ForeignKeyTargetSerializer(serializers.ModelSerializer): + sources = serializers.ManyPrimaryKeyRelatedField(queryset=ForeignKeySource.objects.all()) + + class Meta: + model = ForeignKeyTarget + + +class ForeignKeySourceSerializer(serializers.ModelSerializer): + class Meta: + model = ForeignKeySource # TODO: Add test that .data cannot be accessed prior to .is_valid @@ -30,15 +54,16 @@ class SourceSerializer(serializers.ModelSerializer): class PrimaryKeyManyToManyTests(TestCase): def setUp(self): for idx in range(1, 4): - target = Target(name='target-%d' % idx) + target = ManyToManyTarget(name='target-%d' % idx) target.save() - source = Source(name='source-%d' % idx) + source = ManyToManySource(name='source-%d' % idx) source.save() - for target in Target.objects.all(): + for target in ManyToManyTarget.objects.all(): source.targets.add(target) def test_many_to_many_retrieve(self): - serializer = SourceSerializer(instance=Source.objects.all()) + queryset = ManyToManySource.objects.all() + serializer = ManyToManySourceSerializer(instance=queryset) expected = [ {'id': 1, 'name': u'source-1', 'targets': [1]}, {'id': 2, 'name': u'source-2', 'targets': [1, 2]}, @@ -47,7 +72,8 @@ class PrimaryKeyManyToManyTests(TestCase): self.assertEquals(serializer.data, expected) def test_reverse_many_to_many_retrieve(self): - serializer = TargetSerializer(instance=Target.objects.all()) + queryset = ManyToManyTarget.objects.all() + serializer = ManyToManyTargetSerializer(instance=queryset) expected = [ {'id': 1, 'name': u'target-1', 'sources': [1, 2, 3]}, {'id': 2, 'name': u'target-2', 'sources': [2, 3]}, @@ -57,12 +83,15 @@ class PrimaryKeyManyToManyTests(TestCase): def test_many_to_many_update(self): data = {'id': 1, 'name': u'source-1', 'targets': [1, 2, 3]} - serializer = SourceSerializer(data, instance=Source.objects.get(pk=1)) + instance = ManyToManySource.objects.get(pk=1) + serializer = ManyToManySourceSerializer(data, instance=instance) self.assertTrue(serializer.is_valid()) self.assertEquals(serializer.data, data) + serializer.save() # Ensure source 1 is updated, and everything else is as expected - serializer = SourceSerializer(instance=Source.objects.all()) + queryset = ManyToManySource.objects.all() + serializer = ManyToManySourceSerializer(instance=queryset) expected = [ {'id': 1, 'name': u'source-1', 'targets': [1, 2, 3]}, {'id': 2, 'name': u'source-2', 'targets': [1, 2]}, @@ -71,16 +100,88 @@ class PrimaryKeyManyToManyTests(TestCase): self.assertEquals(serializer.data, expected) def test_reverse_many_to_many_update(self): - data = {'id': 1, 'name': u'target-0', 'sources': [1]} - serializer = TargetSerializer(data, instance=Target.objects.get(pk=1)) + data = {'id': 1, 'name': u'target-1', 'sources': [1]} + instance = ManyToManyTarget.objects.get(pk=1) + serializer = ManyToManyTargetSerializer(data, instance=instance) self.assertTrue(serializer.is_valid()) self.assertEquals(serializer.data, data) + serializer.save() # Ensure target 1 is updated, and everything else is as expected - serializer = TargetSerializer(instance=Target.objects.all()) + queryset = ManyToManyTarget.objects.all() + serializer = ManyToManyTargetSerializer(instance=queryset) expected = [ {'id': 1, 'name': u'target-1', 'sources': [1]}, {'id': 2, 'name': u'target-2', 'sources': [2, 3]}, {'id': 3, 'name': u'target-3', 'sources': [3]} ] self.assertEquals(serializer.data, expected) + + +class PrimaryKeyForeignKeyTests(TestCase): + def setUp(self): + target = ForeignKeyTarget(name='target-1') + target.save() + new_target = ForeignKeyTarget(name='target-2') + new_target.save() + for idx in range(1, 4): + source = ForeignKeySource(name='source-%d' % idx, target=target) + source.save() + + def test_foreign_key_retrieve(self): + queryset = ForeignKeySource.objects.all() + serializer = ForeignKeySourceSerializer(instance=queryset) + expected = [ + {'id': 1, 'name': u'source-1', 'target': 1}, + {'id': 2, 'name': u'source-2', 'target': 1}, + {'id': 3, 'name': u'source-3', 'target': 1} + ] + self.assertEquals(serializer.data, expected) + + def test_reverse_foreign_key_retrieve(self): + queryset = ForeignKeyTarget.objects.all() + serializer = ForeignKeyTargetSerializer(instance=queryset) + expected = [ + {'id': 1, 'name': u'target-1', 'sources': [1, 2, 3]}, + {'id': 2, 'name': u'target-2', 'sources': []}, + ] + self.assertEquals(serializer.data, expected) + + def test_foreign_key_update(self): + data = {'id': 1, 'name': u'source-1', 'target': 2} + instance = ForeignKeySource.objects.get(pk=1) + serializer = ForeignKeySourceSerializer(data, instance=instance) + self.assertTrue(serializer.is_valid()) + self.assertEquals(serializer.data, data) + serializer.save() + + # # Ensure source 1 is updated, and everything else is as expected + queryset = ForeignKeySource.objects.all() + serializer = ForeignKeySourceSerializer(instance=queryset) + expected = [ + {'id': 1, 'name': u'source-1', 'target': 2}, + {'id': 2, 'name': u'source-2', 'target': 1}, + {'id': 3, 'name': u'source-3', 'target': 1} + ] + self.assertEquals(serializer.data, expected) + + # reverse foreign keys MUST be read_only + # In the general case they do not provide .remove() or .clear() + # and cannot be arbitrarily set. + + # def test_reverse_foreign_key_update(self): + # data = {'id': 1, 'name': u'target-1', 'sources': [1]} + # instance = ForeignKeyTarget.objects.get(pk=1) + # serializer = ForeignKeyTargetSerializer(data, instance=instance) + # self.assertTrue(serializer.is_valid()) + # self.assertEquals(serializer.data, data) + # serializer.save() + + # # Ensure target 1 is updated, and everything else is as expected + # queryset = ForeignKeyTarget.objects.all() + # serializer = ForeignKeyTargetSerializer(instance=queryset) + # expected = [ + # {'id': 1, 'name': u'target-1', 'sources': [1]}, + # {'id': 2, 'name': u'target-2', 'sources': []}, + # ] + # self.assertEquals(serializer.data, expected) From 8ec54e6a9fe8e46955098ca8e7d728d8bb4f7053 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 2 Nov 2012 20:56:51 +0000 Subject: [PATCH 3/3] Tweaks --- rest_framework/fields.py | 2 +- rest_framework/runtests/runcoverage.py | 4 ++-- rest_framework/tests/pk_relations.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 965e22c4d..661d02dee 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -344,7 +344,7 @@ class ManyRelatedField(ManyRelatedMixin, RelatedField): """ Base class for related model managers. - If not overridden, this represents a to-many relatinship, using the unicode + If not overridden, this represents a to-many relationship, using the unicode representations of the target, and is read-only. """ pass diff --git a/rest_framework/runtests/runcoverage.py b/rest_framework/runtests/runcoverage.py index ea2e3d458..0ce379ebe 100755 --- a/rest_framework/runtests/runcoverage.py +++ b/rest_framework/runtests/runcoverage.py @@ -32,10 +32,10 @@ def main(): 'Function-based test runners are deprecated. Test runners should be classes with a run_tests() method.', DeprecationWarning ) - failures = TestRunner(['rest_framework']) + failures = TestRunner(['tests']) else: test_runner = TestRunner() - failures = test_runner.run_tests(['rest_framework']) + failures = test_runner.run_tests(['tests']) cov.stop() # Discover the list of all modules that we should test coverage for diff --git a/rest_framework/tests/pk_relations.py b/rest_framework/tests/pk_relations.py index a09343448..9095dcd81 100644 --- a/rest_framework/tests/pk_relations.py +++ b/rest_framework/tests/pk_relations.py @@ -38,7 +38,7 @@ class ForeignKeySource(models.Model): class ForeignKeyTargetSerializer(serializers.ModelSerializer): - sources = serializers.ManyPrimaryKeyRelatedField(queryset=ForeignKeySource.objects.all()) + sources = serializers.ManyPrimaryKeyRelatedField(read_only=True) class Meta: model = ForeignKeyTarget