aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
authorRyan Kaskel2013-05-18 13:11:40 +0100
committerRyan Kaskel2013-05-18 13:11:40 +0100
commit770ed3de2ef8339ec0b74f7eb522283718e01a3b (patch)
tree563b57c1af80d04ac7564c2e040781ce57bff4f7 /rest_framework
parent34776da9249a5d73f822b3562bc56a5674b10ac7 (diff)
downloaddjango-rest-framework-770ed3de2ef8339ec0b74f7eb522283718e01a3b.tar.bz2
ToMany fields default to read-only if targeting ManyToManyField.
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/serializers.py17
-rw-r--r--rest_framework/tests/relations_pk.py63
2 files changed, 80 insertions, 0 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 7707de7a..75f1e1d0 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -587,11 +587,16 @@ class ModelSerializer(Serializer):
forward_rels += [field for field in opts.many_to_many if field.serialize]
for model_field in forward_rels:
+ has_user_through_model = False
+
if model_field.rel:
to_many = isinstance(model_field,
models.fields.related.ManyToManyField)
related_model = model_field.rel.to
+ if to_many and not model_field.rel.through._meta.auto_created:
+ has_user_through_model = True
+
if model_field.rel and nested:
if len(inspect.getargspec(self.get_nested_field).args) == 2:
warnings.warn(
@@ -620,6 +625,9 @@ class ModelSerializer(Serializer):
field = self.get_field(model_field)
if field:
+ if has_user_through_model:
+ field.read_only = True
+
ret[model_field.name] = field
# Deal with reverse relationships
@@ -637,6 +645,12 @@ class ModelSerializer(Serializer):
continue
related_model = relation.model
to_many = relation.field.rel.multiple
+ has_user_through_model = False
+ is_m2m = isinstance(relation.field,
+ models.fields.related.ManyToManyField)
+
+ if is_m2m and not relation.field.rel.through._meta.auto_created:
+ has_user_through_model = True
if nested:
field = self.get_nested_field(None, related_model, to_many)
@@ -644,6 +658,9 @@ class ModelSerializer(Serializer):
field = self.get_related_field(None, related_model, to_many)
if field:
+ if has_user_through_model:
+ field.read_only = True
+
ret[accessor_name] = field
# Add the `read_only` flag to any fields that have bee specified
diff --git a/rest_framework/tests/relations_pk.py b/rest_framework/tests/relations_pk.py
index 5ce8b567..02ffd264 100644
--- a/rest_framework/tests/relations_pk.py
+++ b/rest_framework/tests/relations_pk.py
@@ -1,4 +1,5 @@
from __future__ import unicode_literals
+from django.db import models
from django.test import TestCase
from rest_framework import serializers
from rest_framework.tests.models import ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource, NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource
@@ -124,6 +125,7 @@ class PKManyToManyTests(TestCase):
# Ensure source 4 is added, and everything else is as expected
queryset = ManyToManySource.objects.all()
serializer = ManyToManySourceSerializer(queryset, many=True)
+ self.assertFalse(serializer.fields['targets'].read_only)
expected = [
{'id': 1, 'name': 'source-1', 'targets': [1]},
{'id': 2, 'name': 'source-2', 'targets': [1, 2]},
@@ -135,6 +137,7 @@ class PKManyToManyTests(TestCase):
def test_reverse_many_to_many_create(self):
data = {'id': 4, 'name': 'target-4', 'sources': [1, 3]}
serializer = ManyToManyTargetSerializer(data=data)
+ self.assertFalse(serializer.fields['sources'].read_only)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEqual(serializer.data, data)
@@ -421,3 +424,63 @@ class PKNullableOneToOneTests(TestCase):
{'id': 2, 'name': 'target-2', 'nullable_source': 1},
]
self.assertEqual(serializer.data, expected)
+
+
+# The below models and tests ensure that serializer fields corresponding
+# to a ManyToManyField field with a user-specified ``through`` model are
+# set to read only
+
+
+class ManyToManyThroughTarget(models.Model):
+ name = models.CharField(max_length=100)
+
+
+class ManyToManyThrough(models.Model):
+ source = models.ForeignKey('ManyToManyThroughSource')
+ target = models.ForeignKey(ManyToManyThroughTarget)
+
+
+class ManyToManyThroughSource(models.Model):
+ name = models.CharField(max_length=100)
+ targets = models.ManyToManyField(ManyToManyThroughTarget,
+ related_name='sources',
+ through='ManyToManyThrough')
+
+
+class ManyToManyThroughTargetSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ManyToManyThroughTarget
+ fields = ('id', 'name', 'sources')
+
+
+class ManyToManyThroughSourceSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ManyToManyThroughSource
+ fields = ('id', 'name', 'targets')
+
+
+class PKManyToManyThroughTests(TestCase):
+ def setUp(self):
+ self.source = ManyToManyThroughSource.objects.create(
+ name='through-source-1')
+ self.target = ManyToManyThroughTarget.objects.create(
+ name='through-target-1')
+
+ def test_many_to_many_create(self):
+ data = {'id': 2, 'name': 'source-2', 'targets': [self.target.pk]}
+ serializer = ManyToManyThroughSourceSerializer(data=data)
+ self.assertTrue(serializer.fields['targets'].read_only)
+ self.assertTrue(serializer.is_valid())
+ obj = serializer.save()
+ self.assertEqual(obj.name, 'source-2')
+ self.assertEqual(obj.targets.count(), 0)
+
+ def test_many_to_many_reverse_create(self):
+ data = {'id': 2, 'name': 'target-2', 'sources': [self.source.pk]}
+ serializer = ManyToManyThroughTargetSerializer(data=data)
+ self.assertTrue(serializer.fields['sources'].read_only)
+ self.assertTrue(serializer.is_valid())
+ serializer.save()
+ obj = serializer.save()
+ self.assertEqual(obj.name, 'target-2')
+ self.assertEqual(obj.sources.count(), 0)