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`
|
||||
|
||||
## 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.
|
||||
|
@ -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`.
|
||||
* `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/
|
||||
[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()
|
||||
|
||||
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']
|
||||
|
|
|
@ -63,6 +63,7 @@ The following people have helped make REST framework great.
|
|||
* Rob Romano - [robromano]
|
||||
* Eugene Mechanism - [mechanism]
|
||||
* Jonas Liljestrand - [jonlil]
|
||||
* Justin Davis - [irrelative]
|
||||
|
||||
Many thanks to everyone who's contributed to the project.
|
||||
|
||||
|
@ -161,3 +162,4 @@ To contact the author directly:
|
|||
[robromano]: https://github.com/robromano
|
||||
[mechanism]: https://github.com/mechanism
|
||||
[jonlil]: https://github.com/jonlil
|
||||
[irrelative]: https://github.com/irrelative
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
|
||||
## Master
|
||||
|
||||
* Added `obtain_token_view` to get tokens when using `TokenAuthentication`
|
||||
* Bugfix: Django 1.5 configurable user support for `TokenAuthentication`
|
||||
* 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
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import copy
|
||||
import datetime
|
||||
import inspect
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from io import BytesIO
|
||||
|
@ -804,6 +805,34 @@ 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'
|
||||
|
||||
|
@ -1032,3 +1061,17 @@ class ImageField(FileField):
|
|||
if hasattr(f, 'seek') and callable(f.seek):
|
||||
f.seek(0)
|
||||
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.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
|
||||
|
||||
|
|
|
@ -104,6 +104,7 @@ class BaseSerializer(Field):
|
|||
self.init_data = data
|
||||
self.init_files = files
|
||||
self.object = instance
|
||||
self.default_fields = self.get_default_fields()
|
||||
|
||||
self._data = None
|
||||
self._files = None
|
||||
|
@ -112,18 +113,18 @@ class BaseSerializer(Field):
|
|||
#####
|
||||
# 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 {}
|
||||
|
||||
def get_fields(self, nested=False):
|
||||
def get_fields(self):
|
||||
"""
|
||||
Returns the complete set of fields for the object as a dict.
|
||||
|
||||
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()
|
||||
|
||||
|
@ -134,8 +135,7 @@ class BaseSerializer(Field):
|
|||
field.initialize(parent=self, field_name=key)
|
||||
|
||||
# Add in the default fields
|
||||
fields = self.default_fields(nested)
|
||||
for key, val in fields.items():
|
||||
for key, val in self.default_fields.items():
|
||||
if key not in ret:
|
||||
ret[key] = val
|
||||
|
||||
|
@ -182,7 +182,7 @@ class BaseSerializer(Field):
|
|||
ret = self._dict_class()
|
||||
ret.fields = {}
|
||||
|
||||
fields = self.get_fields(nested=bool(self.opts.depth))
|
||||
fields = self.get_fields()
|
||||
for field_name, field in fields.items():
|
||||
key = self.get_field_key(field_name)
|
||||
value = field.field_to_native(obj, field_name)
|
||||
|
@ -195,7 +195,7 @@ class BaseSerializer(Field):
|
|||
Core of deserialization, together with `restore_object`.
|
||||
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 = {}
|
||||
for field_name, field in fields.items():
|
||||
try:
|
||||
|
@ -210,7 +210,7 @@ class BaseSerializer(Field):
|
|||
Run `validate_<fieldname>()` and `validate()` methods on the serializer
|
||||
"""
|
||||
# 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():
|
||||
try:
|
||||
|
@ -336,16 +336,10 @@ class ModelSerializer(Serializer):
|
|||
"""
|
||||
_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.
|
||||
"""
|
||||
# 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
|
||||
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]
|
||||
|
||||
ret = SortedDict()
|
||||
nested = bool(self.opts.depth)
|
||||
is_pk = True # First field in the list is the pk
|
||||
|
||||
for model_field in fields:
|
||||
|
@ -466,7 +461,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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
@ -270,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()
|
||||
|
@ -534,6 +560,40 @@ class ManyRelatedTests(TestCase):
|
|||
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
|
||||
class BlankFieldTests(TestCase):
|
||||
def setUp(self):
|
||||
|
|
Loading…
Reference in New Issue
Block a user