mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-26 05:31:07 +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