aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
authorTom Christie2013-02-22 22:02:42 +0000
committerTom Christie2013-02-22 22:02:42 +0000
commitbc87bf13b48e4ad6b3957ed686cad8a68c2072a4 (patch)
tree6fbe53fffd6589cfcdcbcddf97d7355df0a7150a /rest_framework
parentd44eb2094211820fbdd014fd2884fd5ed04688ab (diff)
downloaddjango-rest-framework-bc87bf13b48e4ad6b3957ed686cad8a68c2072a4.tar.bz2
Tests and fix for #666.
Closes #666.
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/mixins.py30
-rw-r--r--rest_framework/serializers.py6
-rw-r--r--rest_framework/tests/generics.py34
3 files changed, 64 insertions, 6 deletions
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index d898ca12..4e8551f2 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -12,6 +12,28 @@ from rest_framework.response import Response
from rest_framework.request import clone_request
+def _get_validation_exclusions(obj, pk=None, slug_field=None):
+ """
+ Given a model instance, and an optional pk and slug field,
+ return the full list of all other field names on that model.
+
+ For use when performing full_clean on a model instance,
+ so we only clean the required fields.
+ """
+ include = []
+
+ if pk:
+ pk_field = obj._meta.pk
+ while pk_field.rel:
+ pk_field = pk_field.rel.to._meta.pk
+ include.append(pk_field.name)
+
+ if slug_field:
+ include.append(slug_field)
+
+ return [field.name for field in obj._meta.fields if field.name not in include]
+
+
class CreateModelMixin(object):
"""
Create a model instance.
@@ -117,18 +139,20 @@ class UpdateModelMixin(object):
"""
# pk and/or slug attributes are implicit in the URL.
pk = self.kwargs.get(self.pk_url_kwarg, None)
+ slug = self.kwargs.get(self.slug_url_kwarg, None)
+ slug_field = slug and self.get_slug_field() or None
+
if pk:
setattr(obj, 'pk', pk)
- slug = self.kwargs.get(self.slug_url_kwarg, None)
if slug:
- slug_field = self.get_slug_field()
setattr(obj, slug_field, slug)
# Ensure we clean the attributes so that we don't eg return integer
# pk using a string representation, as provided by the url conf kwarg.
if hasattr(obj, 'full_clean'):
- obj.full_clean()
+ exclude = _get_validation_exclusions(obj, pk, slug_field)
+ obj.full_clean(exclude)
class DestroyModelMixin(object):
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index b0372ab8..89b3d8ce 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -158,6 +158,7 @@ class BaseSerializer(Field):
# If 'fields' is specified, use those fields, in that order.
if self.opts.fields:
+ assert isinstance(self.opts.fields, (list, tuple)), '`include` must be a list or tuple'
new = SortedDict()
for key in self.opts.fields:
new[key] = ret[key]
@@ -165,6 +166,7 @@ class BaseSerializer(Field):
# Remove anything in 'exclude'
if self.opts.exclude:
+ assert isinstance(self.opts.fields, (list, tuple)), '`exclude` must be a list or tuple'
for key in self.opts.exclude:
ret.pop(key, None)
@@ -421,8 +423,8 @@ class ModelSerializer(Serializer):
cls = self.opts.model
opts = get_concrete_model(cls)._meta
pk_field = opts.pk
- while pk_field.rel:
- pk_field = pk_field.rel.to._meta.pk
+ # while pk_field.rel:
+ # pk_field = pk_field.rel.to._meta.pk
fields = [pk_field]
fields += [field for field in opts.fields if field.serialize]
fields += [field for field in opts.many_to_many if field.serialize]
diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py
index 2e7ce727..2dcf0106 100644
--- a/rest_framework/tests/generics.py
+++ b/rest_framework/tests/generics.py
@@ -319,7 +319,7 @@ class TestCreateModelWithAutoNowAddField(TestCase):
self.assertEquals(created.content, 'foobar')
-# Test for particularly ugly reression with m2m in browseable API
+# Test for particularly ugly regression with m2m in browseable API
class ClassB(models.Model):
name = models.CharField(max_length=255)
@@ -350,3 +350,35 @@ class TestM2MBrowseableAPI(TestCase):
view = ExampleView().as_view()
response = view(request).render()
self.assertEquals(response.status_code, status.HTTP_200_OK)
+
+
+# Regression for #666
+
+class ValidationModel(models.Model):
+ blank_validated_field = models.CharField(max_length=255)
+
+
+class ValidationModelSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = ValidationModel
+ fields = ('blank_validated_field',)
+ read_only_fields = ('blank_validated_field',)
+
+
+class UpdateValidationModel(generics.RetrieveUpdateDestroyAPIView):
+ model = ValidationModel
+ serializer_class = ValidationModelSerializer
+
+
+class TestPreSaveValidationExclusions(TestCase):
+ def test_pre_save_validation_exclusions(self):
+ """
+ Somewhat weird test case to ensure that we don't perform model
+ validation on read only fields.
+ """
+ obj = ValidationModel.objects.create(blank_validated_field='')
+ request = factory.put('/', json.dumps({}),
+ content_type='application/json')
+ view = UpdateValidationModel().as_view()
+ response = view(request, pk=obj.pk).render()
+ self.assertEquals(response.status_code, status.HTTP_200_OK)