mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-02 20:54:42 +03:00
Merge remote-tracking branch 'upstream/master' into partial-update
This commit is contained in:
commit
0876bed963
|
@ -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
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user