aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md11
-rw-r--r--docs/api-guide/fields.md10
-rw-r--r--docs/api-guide/serializers.md6
-rw-r--r--docs/topics/credits.md4
-rw-r--r--docs/topics/release-notes.md12
-rw-r--r--rest_framework/__init__.py2
-rw-r--r--rest_framework/fields.py33
-rw-r--r--rest_framework/renderers.py3
-rw-r--r--rest_framework/serializers.py5
-rw-r--r--rest_framework/tests/models.py5
-rw-r--r--rest_framework/tests/serializer.py40
11 files changed, 121 insertions, 10 deletions
diff --git a/README.md b/README.md
index 9a12d535..2743dd3e 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,17 @@ To run the tests.
# Changelog
+## 2.1.4
+
+**Date**: 22nd Nov 2012
+
+* Support for partial updates with serializers.
+* Added `RegexField`.
+* Added `SerializerMethodField`.
+* Serializer performance improvements.
+* Added `obtain_token_view` to get tokens when using `TokenAuthentication`.
+* Bugfix: Django 1.5 configurable user support for `TokenAuthentication`.
+
## 2.1.3
**Date**: 16th Nov 2012
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index 914d0861..1d4c34cb 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -153,6 +153,16 @@ A text representation, validates the text to be a valid e-mail address.
Corresponds to `django.db.models.fields.EmailField`
+## RegexField
+
+A text representation, that validates the given value matches against a certain regular expression.
+
+Uses Django's `django.core.validators.RegexValidator` for validation.
+
+Corresponds to `django.forms.fields.RegexField`
+
+**Signature:** `RegexField(regex, max_length=None, min_length=None)`
+
## DateField
A date representation.
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index a9589144..19efde3c 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -34,7 +34,7 @@ Declaring a serializer looks very similar to declaring a form:
created = serializers.DateTimeField()
def restore_object(self, attrs, instance=None):
- if instance:
+ if instance is not None:
instance.title = attrs['title']
instance.content = attrs['content']
instance.created = attrs['created']
@@ -77,6 +77,10 @@ When deserializing data, we can either create a new instance, or update an exist
serializer = CommentSerializer(data=data) # Create new instance
serializer = CommentSerializer(comment, data=data) # Update `instance`
+By default, serializers must be passed values for all required fields or they will throw validation errors. You can use the `partial` argument in order to allow partial updates.
+
+ serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True) # Update `instance` with partial data
+
## Validation
When deserializing data, you always need to call `is_valid()` before attempting to access the deserialized object. If any validation errors occur, the `.errors` and `.non_field_errors` properties will contain the resulting error messages.
diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index 955870d2..5323e9c0 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -64,6 +64,8 @@ The following people have helped make REST framework great.
* Eugene Mechanism - [mechanism]
* Jonas Liljestrand - [jonlil]
* Justin Davis - [irrelative]
+* Dustin Bachrach - [dbachrach]
+* Mark Shirley - [maspwr]
Many thanks to everyone who's contributed to the project.
@@ -163,3 +165,5 @@ To contact the author directly:
[mechanism]: https://github.com/mechanism
[jonlil]: https://github.com/jonlil
[irrelative]: https://github.com/irrelative
+[dbachrach]: https://github.com/dbachrach
+[maspwr]: https://github.com/maspwr \ No newline at end of file
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 0b8a7a8f..fe5466be 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -4,12 +4,16 @@
>
> — Eric S. Raymond, [The Cathedral and the Bazaar][cite].
-## Master
+## 2.1.4
-* Added `SerializerMethodField`
+**Date**: 22nd Nov 2012
+
+* Support for partial updates with serializers.
+* Added `RegexField`.
+* Added `SerializerMethodField`.
* Serializer performance improvements.
-* Added `obtain_token_view` to get tokens when using `TokenAuthentication`
-* Bugfix: Django 1.5 configurable user support for `TokenAuthentication`
+* Added `obtain_token_view` to get tokens when using `TokenAuthentication`.
+* Bugfix: Django 1.5 configurable user support for `TokenAuthentication`.
## 2.1.3
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py
index 88108a8d..a2233f3d 100644
--- a/rest_framework/__init__.py
+++ b/rest_framework/__init__.py
@@ -1,3 +1,3 @@
-__version__ = '2.1.3'
+__version__ = '2.1.4'
VERSION = __version__ # synonym
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index 9dce0143..dff9123d 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -1,6 +1,7 @@
import copy
import datetime
import inspect
+import re
import warnings
from io import BytesIO
@@ -53,6 +54,8 @@ class Field(object):
self.parent = parent
self.root = parent.root or parent
self.context = self.root.context
+ if self.root.partial:
+ self.required = False
def field_from_native(self, data, files, field_name, into):
"""
@@ -802,8 +805,37 @@ class EmailField(CharField):
return result
+class RegexField(CharField):
+ type_name = 'RegexField'
+
+ def __init__(self, regex, max_length=None, min_length=None, *args, **kwargs):
+ super(RegexField, self).__init__(max_length, min_length, *args, **kwargs)
+ self.regex = regex
+
+ def _get_regex(self):
+ return self._regex
+
+ def _set_regex(self, regex):
+ if isinstance(regex, basestring):
+ regex = re.compile(regex)
+ self._regex = regex
+ if hasattr(self, '_regex_validator') and self._regex_validator in self.validators:
+ self.validators.remove(self._regex_validator)
+ self._regex_validator = validators.RegexValidator(regex=regex)
+ self.validators.append(self._regex_validator)
+
+ regex = property(_get_regex, _set_regex)
+
+ def __deepcopy__(self, memo):
+ result = copy.copy(self)
+ memo[id(self)] = result
+ result.validators = self.validators[:]
+ return result
+
+
class DateField(WritableField):
type_name = 'DateField'
+ widget = widgets.DateInput
default_error_messages = {
'invalid': _(u"'%s' value has an invalid date format. It must be "
@@ -841,6 +873,7 @@ class DateField(WritableField):
class DateTimeField(WritableField):
type_name = 'DateTimeField'
+ widget = widgets.DateTimeInput
default_error_messages = {
'invalid': _(u"'%s' value has an invalid format. It must be in "
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index db1bce39..550963cb 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -312,6 +312,7 @@ class BrowsableAPIRenderer(BaseRenderer):
serializers.DateTimeField: forms.DateTimeField,
serializers.DateField: forms.DateField,
serializers.EmailField: forms.EmailField,
+ serializers.RegexField: forms.RegexField,
serializers.CharField: forms.CharField,
serializers.ChoiceField: forms.ChoiceField,
serializers.BooleanField: forms.BooleanField,
@@ -326,7 +327,7 @@ class BrowsableAPIRenderer(BaseRenderer):
}
fields = {}
- for k, v in serializer.get_fields(True).items():
+ for k, v in serializer.get_fields().items():
if getattr(v, 'read_only', True):
continue
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 546abc06..4519ab05 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -91,11 +91,12 @@ class BaseSerializer(Field):
_options_class = SerializerOptions
_dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatibility with unsorted implementations.
- def __init__(self, instance=None, data=None, files=None, context=None, **kwargs):
+ def __init__(self, instance=None, data=None, files=None, context=None, partial=False, **kwargs):
super(BaseSerializer, self).__init__(**kwargs)
self.opts = self._options_class(self.Meta)
self.parent = None
self.root = None
+ self.partial = partial
self.context = context or {}
@@ -456,7 +457,7 @@ class ModelSerializer(Serializer):
"""
self.m2m_data = {}
- if instance:
+ if instance is not None:
for key, val in attrs.items():
setattr(instance, key, val)
return instance
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
index 3704cda7..c35861c6 100644
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -165,3 +165,8 @@ class BlankFieldModel(RESTFrameworkModel):
# Model for issue #380
class OptionalRelationModel(RESTFrameworkModel):
other = models.ForeignKey('OptionalRelationModel', blank=True, null=True)
+
+
+# Model for RegexField
+class Book(RESTFrameworkModel):
+ isbn = models.CharField(max_length=13)
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 656bc1dd..61a05da1 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -2,7 +2,7 @@ import datetime
from django.test import TestCase
from rest_framework import serializers
from rest_framework.tests.models import (ActionItem, Anchor, BasicModel,
- BlankFieldModel, BlogPost, CallableDefaultValueModel, DefaultValueModel,
+ BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel,
ManyToManyModel, Person, ReadOnlyManyToManyModel)
@@ -40,6 +40,13 @@ class CommentSerializer(serializers.Serializer):
return instance
+class BookSerializer(serializers.ModelSerializer):
+ isbn = serializers.RegexField(regex=r'^[0-9]{13}$', error_messages={'invalid': 'isbn has to be exact 13 numbers'})
+
+ class Meta:
+ model = Book
+
+
class ActionItemSerializer(serializers.ModelSerializer):
class Meta:
@@ -108,6 +115,18 @@ class BasicTests(TestCase):
self.assertTrue(serializer.object is expected)
self.assertEquals(serializer.data['sub_comment'], 'And Merry Christmas!')
+ def test_partial_update(self):
+ msg = 'Merry New Year!'
+ partial_data = {'content': msg}
+ serializer = CommentSerializer(self.comment, data=partial_data)
+ self.assertEquals(serializer.is_valid(), False)
+ serializer = CommentSerializer(self.comment, data=partial_data, partial=True)
+ expected = self.comment
+ self.assertEqual(serializer.is_valid(), True)
+ self.assertEquals(serializer.object, expected)
+ self.assertTrue(serializer.object is expected)
+ self.assertEquals(serializer.data['content'], msg)
+
def test_model_fields_as_expected(self):
"""
Make sure that the fields returned are the same as defined
@@ -258,6 +277,25 @@ class ValidationTests(TestCase):
self.assertEquals(serializer.errors, {'info': [u'Ensure this value has at most 12 characters (it has 13).']})
+class RegexValidationTest(TestCase):
+ def test_create_failed(self):
+ serializer = BookSerializer(data={'isbn': '1234567890'})
+ self.assertFalse(serializer.is_valid())
+ self.assertEquals(serializer.errors, {'isbn': [u'isbn has to be exact 13 numbers']})
+
+ serializer = BookSerializer(data={'isbn': '12345678901234'})
+ self.assertFalse(serializer.is_valid())
+ self.assertEquals(serializer.errors, {'isbn': [u'isbn has to be exact 13 numbers']})
+
+ serializer = BookSerializer(data={'isbn': 'abcdefghijklm'})
+ self.assertFalse(serializer.is_valid())
+ self.assertEquals(serializer.errors, {'isbn': [u'isbn has to be exact 13 numbers']})
+
+ def test_create_success(self):
+ serializer = BookSerializer(data={'isbn': '1234567890123'})
+ self.assertTrue(serializer.is_valid())
+
+
class MetadataTests(TestCase):
def test_empty(self):
serializer = CommentSerializer()