Merge remote-tracking branch 'upstream/master' into partial-update

This commit is contained in:
Mark Aaron Shirley 2012-11-21 09:37:22 -08:00
commit 0876bed963
9 changed files with 160 additions and 21 deletions

View File

@ -153,6 +153,16 @@ A text representation, validates the text to be a valid e-mail address.
Corresponds to `django.db.models.fields.EmailField` 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 ## DateField
A date representation. A date representation.
@ -324,5 +334,25 @@ This field is always read-only.
* `pk_url_kwarg` - The named url parameter for the pk field lookup. Default is `pk`. * `pk_url_kwarg` - The named url parameter for the pk field lookup. Default is `pk`.
* `slug_url_kwarg` - The named url parameter for the slug field lookup. Default is to use the same value as given for `slug_field`. * `slug_url_kwarg` - The named url parameter for the slug field lookup. Default is to use the same value as given for `slug_field`.
# Other Fields
## SerializerMethodField
This is a read-only field. It gets its value by calling a method on the serializer class it is attached to. It can be used to add any sort of data to the serialized representation of your object. The field's constructor accepts a single argument, which is the name of the method on the serializer to be called. The method should accept a single argument (in addition to `self`), which is the object being serialized. It should return whatever you want to be included in the serialized representation of the object. For example:
from rest_framework import serializers
from django.contrib.auth.models import User
from django.utils.timezone import now
class UserSerializer(serializers.ModelSerializer):
days_since_joined = serializers.SerializerMethodField('get_days_since_joined')
class Meta:
model = User
def get_days_since_joined(self, obj):
return (now() - obj.date_joined).days
[cite]: http://www.python.org/dev/peps/pep-0020/ [cite]: http://www.python.org/dev/peps/pep-0020/
[FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS [FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS

View File

@ -34,7 +34,7 @@ Declaring a serializer looks very similar to declaring a form:
created = serializers.DateTimeField() created = serializers.DateTimeField()
def restore_object(self, attrs, instance=None): def restore_object(self, attrs, instance=None):
if instance: if instance is not None:
instance.title = attrs['title'] instance.title = attrs['title']
instance.content = attrs['content'] instance.content = attrs['content']
instance.created = attrs['created'] instance.created = attrs['created']

View File

@ -63,6 +63,7 @@ The following people have helped make REST framework great.
* Rob Romano - [robromano] * Rob Romano - [robromano]
* Eugene Mechanism - [mechanism] * Eugene Mechanism - [mechanism]
* Jonas Liljestrand - [jonlil] * Jonas Liljestrand - [jonlil]
* Justin Davis - [irrelative]
Many thanks to everyone who's contributed to the project. Many thanks to everyone who's contributed to the project.
@ -161,3 +162,4 @@ To contact the author directly:
[robromano]: https://github.com/robromano [robromano]: https://github.com/robromano
[mechanism]: https://github.com/mechanism [mechanism]: https://github.com/mechanism
[jonlil]: https://github.com/jonlil [jonlil]: https://github.com/jonlil
[irrelative]: https://github.com/irrelative

View File

@ -6,8 +6,11 @@
## Master ## Master
* Added `obtain_token_view` to get tokens when using `TokenAuthentication` * Added `RegexField`.
* Bugfix: Django 1.5 configurable user support for `TokenAuthentication` * 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 ## 2.1.3

View File

@ -1,6 +1,7 @@
import copy import copy
import datetime import datetime
import inspect import inspect
import re
import warnings import warnings
from io import BytesIO from io import BytesIO
@ -804,6 +805,34 @@ class EmailField(CharField):
return result 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): class DateField(WritableField):
type_name = 'DateField' type_name = 'DateField'
@ -1032,3 +1061,17 @@ class ImageField(FileField):
if hasattr(f, 'seek') and callable(f.seek): if hasattr(f, 'seek') and callable(f.seek):
f.seek(0) f.seek(0)
return f return f
class SerializerMethodField(Field):
"""
A field that gets its value by calling a method on the serializer it's attached to.
"""
def __init__(self, method_name):
self.method_name = method_name
super(SerializerMethodField, self).__init__()
def field_to_native(self, obj, field_name):
value = getattr(self.parent, self.method_name)(obj)
return self.to_native(value)

View File

@ -312,6 +312,7 @@ class BrowsableAPIRenderer(BaseRenderer):
serializers.DateTimeField: forms.DateTimeField, serializers.DateTimeField: forms.DateTimeField,
serializers.DateField: forms.DateField, serializers.DateField: forms.DateField,
serializers.EmailField: forms.EmailField, serializers.EmailField: forms.EmailField,
serializers.RegexField: forms.RegexField,
serializers.CharField: forms.CharField, serializers.CharField: forms.CharField,
serializers.ChoiceField: forms.ChoiceField, serializers.ChoiceField: forms.ChoiceField,
serializers.BooleanField: forms.BooleanField, serializers.BooleanField: forms.BooleanField,
@ -326,7 +327,7 @@ class BrowsableAPIRenderer(BaseRenderer):
} }
fields = {} fields = {}
for k, v in serializer.get_fields(True).items(): for k, v in serializer.get_fields().items():
if getattr(v, 'read_only', True): if getattr(v, 'read_only', True):
continue continue

View File

@ -104,6 +104,7 @@ class BaseSerializer(Field):
self.init_data = data self.init_data = data
self.init_files = files self.init_files = files
self.object = instance self.object = instance
self.default_fields = self.get_default_fields()
self._data = None self._data = None
self._files = None self._files = None
@ -112,18 +113,18 @@ class BaseSerializer(Field):
##### #####
# Methods to determine which fields to use when (de)serializing objects. # Methods to determine which fields to use when (de)serializing objects.
def default_fields(self, nested=False): def get_default_fields(self):
""" """
Return the complete set of default fields for the object, as a dict. Return the complete set of default fields for the object, as a dict.
""" """
return {} return {}
def get_fields(self, nested=False): def get_fields(self):
""" """
Returns the complete set of fields for the object as a dict. Returns the complete set of fields for the object as a dict.
This will be the set of any explicitly declared fields, This will be the set of any explicitly declared fields,
plus the set of fields returned by default_fields(). plus the set of fields returned by get_default_fields().
""" """
ret = SortedDict() ret = SortedDict()
@ -134,8 +135,7 @@ class BaseSerializer(Field):
field.initialize(parent=self, field_name=key) field.initialize(parent=self, field_name=key)
# Add in the default fields # Add in the default fields
fields = self.default_fields(nested) for key, val in self.default_fields.items():
for key, val in fields.items():
if key not in ret: if key not in ret:
ret[key] = val ret[key] = val
@ -182,7 +182,7 @@ class BaseSerializer(Field):
ret = self._dict_class() ret = self._dict_class()
ret.fields = {} ret.fields = {}
fields = self.get_fields(nested=bool(self.opts.depth)) fields = self.get_fields()
for field_name, field in fields.items(): for field_name, field in fields.items():
key = self.get_field_key(field_name) key = self.get_field_key(field_name)
value = field.field_to_native(obj, field_name) value = field.field_to_native(obj, field_name)
@ -195,7 +195,7 @@ class BaseSerializer(Field):
Core of deserialization, together with `restore_object`. Core of deserialization, together with `restore_object`.
Converts a dictionary of data into a dictionary of deserialized fields. Converts a dictionary of data into a dictionary of deserialized fields.
""" """
fields = self.get_fields(nested=bool(self.opts.depth)) fields = self.get_fields()
reverted_data = {} reverted_data = {}
for field_name, field in fields.items(): for field_name, field in fields.items():
try: try:
@ -210,7 +210,7 @@ class BaseSerializer(Field):
Run `validate_<fieldname>()` and `validate()` methods on the serializer Run `validate_<fieldname>()` and `validate()` methods on the serializer
""" """
# TODO: refactor this so we're not determining the fields again # TODO: refactor this so we're not determining the fields again
fields = self.get_fields(nested=bool(self.opts.depth)) fields = self.get_fields()
for field_name, field in fields.items(): for field_name, field in fields.items():
try: try:
@ -336,16 +336,10 @@ class ModelSerializer(Serializer):
""" """
_options_class = ModelSerializerOptions _options_class = ModelSerializerOptions
def default_fields(self, nested=False): def get_default_fields(self):
""" """
Return all the fields that should be serialized for the model. Return all the fields that should be serialized for the model.
""" """
# TODO: Modify this so that it's called on init, and drop
# serialize/obj/data arguments.
#
# We *could* provide a hook for dynamic fields, but
# it'd be nice if the default was to generate fields statically
# at the point of __init__
cls = self.opts.model cls = self.opts.model
opts = get_concrete_model(cls)._meta opts = get_concrete_model(cls)._meta
@ -357,6 +351,7 @@ class ModelSerializer(Serializer):
fields += [field for field in opts.many_to_many if field.serialize] fields += [field for field in opts.many_to_many if field.serialize]
ret = SortedDict() ret = SortedDict()
nested = bool(self.opts.depth)
is_pk = True # First field in the list is the pk is_pk = True # First field in the list is the pk
for model_field in fields: for model_field in fields:
@ -466,7 +461,7 @@ class ModelSerializer(Serializer):
""" """
self.m2m_data = {} self.m2m_data = {}
if instance: if instance is not None:
for key, val in attrs.items(): for key, val in attrs.items():
setattr(instance, key, val) setattr(instance, key, val)
return instance return instance

View File

@ -165,3 +165,8 @@ class BlankFieldModel(RESTFrameworkModel):
# Model for issue #380 # Model for issue #380
class OptionalRelationModel(RESTFrameworkModel): class OptionalRelationModel(RESTFrameworkModel):
other = models.ForeignKey('OptionalRelationModel', blank=True, null=True) other = models.ForeignKey('OptionalRelationModel', blank=True, null=True)
# Model for RegexField
class Book(RESTFrameworkModel):
isbn = models.CharField(max_length=13)

View File

@ -2,7 +2,7 @@ import datetime
from django.test import TestCase from django.test import TestCase
from rest_framework import serializers from rest_framework import serializers
from rest_framework.tests.models import (ActionItem, Anchor, BasicModel, from rest_framework.tests.models import (ActionItem, Anchor, BasicModel,
BlankFieldModel, BlogPost, CallableDefaultValueModel, DefaultValueModel, BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel,
ManyToManyModel, Person, ReadOnlyManyToManyModel) ManyToManyModel, Person, ReadOnlyManyToManyModel)
@ -40,6 +40,13 @@ class CommentSerializer(serializers.Serializer):
return instance 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 ActionItemSerializer(serializers.ModelSerializer):
class Meta: class Meta:
@ -270,6 +277,25 @@ class ValidationTests(TestCase):
self.assertEquals(serializer.errors, {'info': [u'Ensure this value has at most 12 characters (it has 13).']}) 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): class MetadataTests(TestCase):
def test_empty(self): def test_empty(self):
serializer = CommentSerializer() serializer = CommentSerializer()
@ -534,6 +560,40 @@ class ManyRelatedTests(TestCase):
self.assertEqual(serializer.data, expected) self.assertEqual(serializer.data, expected)
class SerializerMethodFieldTests(TestCase):
def setUp(self):
class BoopSerializer(serializers.Serializer):
beep = serializers.SerializerMethodField('get_beep')
boop = serializers.Field()
boop_count = serializers.SerializerMethodField('get_boop_count')
def get_beep(self, obj):
return 'hello!'
def get_boop_count(self, obj):
return len(obj.boop)
self.serializer_class = BoopSerializer
def test_serializer_method_field(self):
class MyModel(object):
boop = ['a', 'b', 'c']
source_data = MyModel()
serializer = self.serializer_class(source_data)
expected = {
'beep': u'hello!',
'boop': [u'a', u'b', u'c'],
'boop_count': 3,
}
self.assertEqual(serializer.data, expected)
# Test for issue #324 # Test for issue #324
class BlankFieldTests(TestCase): class BlankFieldTests(TestCase):
def setUp(self): def setUp(self):