mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-27 03:54:01 +03:00
Merge branch 'master' of git://github.com/tomchristie/django-rest-framework
This commit is contained in:
commit
50a8f114fc
|
@ -124,6 +124,9 @@ The following people have helped make REST framework great.
|
||||||
* Marlon Bailey - [avinash240]
|
* Marlon Bailey - [avinash240]
|
||||||
* James Summerfield - [jsummerfield]
|
* James Summerfield - [jsummerfield]
|
||||||
* Andy Freeland - [rouge8]
|
* Andy Freeland - [rouge8]
|
||||||
|
* Craig de Stigter - [craigds]
|
||||||
|
* Pablo Recio - [pyriku]
|
||||||
|
* Brian Zambrano - [brianz]
|
||||||
|
|
||||||
Many thanks to everyone who's contributed to the project.
|
Many thanks to everyone who's contributed to the project.
|
||||||
|
|
||||||
|
@ -284,3 +287,6 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
|
||||||
[avinash240]: https://github.com/avinash240
|
[avinash240]: https://github.com/avinash240
|
||||||
[jsummerfield]: https://github.com/jsummerfield
|
[jsummerfield]: https://github.com/jsummerfield
|
||||||
[rouge8]: https://github.com/rouge8
|
[rouge8]: https://github.com/rouge8
|
||||||
|
[craigds]: https://github.com/craigds
|
||||||
|
[pyriku]: https://github.com/pyriku
|
||||||
|
[brianz]: https://github.com/brianz
|
||||||
|
|
|
@ -15,6 +15,7 @@ import warnings
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.models.fields import BLANK_CHOICE_DASH
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms import widgets
|
from django.forms import widgets
|
||||||
from django.utils.encoding import is_protected_type
|
from django.utils.encoding import is_protected_type
|
||||||
|
@ -51,7 +52,7 @@ def get_component(obj, attr_name):
|
||||||
return that attribute on the object.
|
return that attribute on the object.
|
||||||
"""
|
"""
|
||||||
if isinstance(obj, dict):
|
if isinstance(obj, dict):
|
||||||
val = obj[attr_name]
|
val = obj.get(attr_name)
|
||||||
else:
|
else:
|
||||||
val = getattr(obj, attr_name)
|
val = getattr(obj, attr_name)
|
||||||
|
|
||||||
|
@ -407,6 +408,8 @@ class ChoiceField(WritableField):
|
||||||
def __init__(self, choices=(), *args, **kwargs):
|
def __init__(self, choices=(), *args, **kwargs):
|
||||||
super(ChoiceField, self).__init__(*args, **kwargs)
|
super(ChoiceField, self).__init__(*args, **kwargs)
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
|
if not self.required:
|
||||||
|
self.choices = BLANK_CHOICE_DASH + self.choices
|
||||||
|
|
||||||
def _get_choices(self):
|
def _get_choices(self):
|
||||||
return self._choices
|
return self._choices
|
||||||
|
|
|
@ -705,15 +705,14 @@ class ModelSerializer(Serializer):
|
||||||
Creates a default instance of a basic non-relational field.
|
Creates a default instance of a basic non-relational field.
|
||||||
"""
|
"""
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
has_default = model_field.has_default()
|
|
||||||
|
|
||||||
if model_field.null or model_field.blank or has_default:
|
if model_field.null or model_field.blank:
|
||||||
kwargs['required'] = False
|
kwargs['required'] = False
|
||||||
|
|
||||||
if isinstance(model_field, models.AutoField) or not model_field.editable:
|
if isinstance(model_field, models.AutoField) or not model_field.editable:
|
||||||
kwargs['read_only'] = True
|
kwargs['read_only'] = True
|
||||||
|
|
||||||
if has_default:
|
if model_field.has_default():
|
||||||
kwargs['default'] = model_field.get_default()
|
kwargs['default'] = model_field.get_default()
|
||||||
|
|
||||||
if issubclass(model_field.__class__, models.TextField):
|
if issubclass(model_field.__class__, models.TextField):
|
||||||
|
|
|
@ -659,3 +659,29 @@ class DecimalFieldTest(TestCase):
|
||||||
|
|
||||||
self.assertFalse(s.is_valid())
|
self.assertFalse(s.is_valid())
|
||||||
self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 4 digits in total.']})
|
self.assertEqual(s.errors, {'decimal_field': ['Ensure that there are no more than 4 digits in total.']})
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceFieldTests(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for the ChoiceField options generator
|
||||||
|
"""
|
||||||
|
|
||||||
|
SAMPLE_CHOICES = [
|
||||||
|
('red', 'Red'),
|
||||||
|
('green', 'Green'),
|
||||||
|
('blue', 'Blue'),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_choices_required(self):
|
||||||
|
"""
|
||||||
|
Make sure proper choices are rendered if field is required
|
||||||
|
"""
|
||||||
|
f = serializers.ChoiceField(required=True, choices=self.SAMPLE_CHOICES)
|
||||||
|
self.assertEqual(f.choices, self.SAMPLE_CHOICES)
|
||||||
|
|
||||||
|
def test_choices_not_required(self):
|
||||||
|
"""
|
||||||
|
Make sure proper choices (plus blank) are rendered if the field isn't required
|
||||||
|
"""
|
||||||
|
f = serializers.ChoiceField(required=False, choices=self.SAMPLE_CHOICES)
|
||||||
|
self.assertEqual(f.choices, models.fields.BLANK_CHOICE_DASH + self.SAMPLE_CHOICES)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models.fields import BLANK_CHOICE_DASH
|
||||||
from django.utils.datastructures import MultiValueDict
|
from django.utils.datastructures import MultiValueDict
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
@ -43,6 +45,17 @@ class CommentSerializer(serializers.Serializer):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class NamesSerializer(serializers.Serializer):
|
||||||
|
first = serializers.CharField()
|
||||||
|
last = serializers.CharField(required=False, default='')
|
||||||
|
initials = serializers.CharField(required=False, default='')
|
||||||
|
|
||||||
|
|
||||||
|
class PersonIdentifierSerializer(serializers.Serializer):
|
||||||
|
ssn = serializers.CharField()
|
||||||
|
names = NamesSerializer(source='names', required=False)
|
||||||
|
|
||||||
|
|
||||||
class BookSerializer(serializers.ModelSerializer):
|
class BookSerializer(serializers.ModelSerializer):
|
||||||
isbn = serializers.RegexField(regex=r'^[0-9]{13}$', error_messages={'invalid': 'isbn has to be exact 13 numbers'})
|
isbn = serializers.RegexField(regex=r'^[0-9]{13}$', error_messages={'invalid': 'isbn has to be exact 13 numbers'})
|
||||||
|
|
||||||
|
@ -153,6 +166,42 @@ class BasicTests(TestCase):
|
||||||
self.assertFalse(serializer.object is expected)
|
self.assertFalse(serializer.object is expected)
|
||||||
self.assertEqual(serializer.data['sub_comment'], 'And Merry Christmas!')
|
self.assertEqual(serializer.data['sub_comment'], 'And Merry Christmas!')
|
||||||
|
|
||||||
|
def test_create_nested(self):
|
||||||
|
"""Test a serializer with nested data."""
|
||||||
|
names = {'first': 'John', 'last': 'Doe', 'initials': 'jd'}
|
||||||
|
data = {'ssn': '1234567890', 'names': names}
|
||||||
|
serializer = PersonIdentifierSerializer(data=data)
|
||||||
|
|
||||||
|
self.assertEqual(serializer.is_valid(), True)
|
||||||
|
self.assertEqual(serializer.object, data)
|
||||||
|
self.assertFalse(serializer.object is data)
|
||||||
|
self.assertEqual(serializer.data['names'], names)
|
||||||
|
|
||||||
|
def test_create_partial_nested(self):
|
||||||
|
"""Test a serializer with nested data which has missing fields."""
|
||||||
|
names = {'first': 'John'}
|
||||||
|
data = {'ssn': '1234567890', 'names': names}
|
||||||
|
serializer = PersonIdentifierSerializer(data=data)
|
||||||
|
|
||||||
|
expected_names = {'first': 'John', 'last': '', 'initials': ''}
|
||||||
|
data['names'] = expected_names
|
||||||
|
|
||||||
|
self.assertEqual(serializer.is_valid(), True)
|
||||||
|
self.assertEqual(serializer.object, data)
|
||||||
|
self.assertFalse(serializer.object is expected_names)
|
||||||
|
self.assertEqual(serializer.data['names'], expected_names)
|
||||||
|
|
||||||
|
def test_null_nested(self):
|
||||||
|
"""Test a serializer with a nonexistent nested field"""
|
||||||
|
data = {'ssn': '1234567890'}
|
||||||
|
serializer = PersonIdentifierSerializer(data=data)
|
||||||
|
|
||||||
|
self.assertEqual(serializer.is_valid(), True)
|
||||||
|
self.assertEqual(serializer.object, data)
|
||||||
|
self.assertFalse(serializer.object is data)
|
||||||
|
expected = {'ssn': '1234567890', 'names': None}
|
||||||
|
self.assertEqual(serializer.data, expected)
|
||||||
|
|
||||||
def test_update(self):
|
def test_update(self):
|
||||||
serializer = CommentSerializer(self.comment, data=self.data)
|
serializer = CommentSerializer(self.comment, data=self.data)
|
||||||
expected = self.comment
|
expected = self.comment
|
||||||
|
@ -1001,6 +1050,73 @@ class SerializerPickleTests(TestCase):
|
||||||
repr(pickle.loads(pickle.dumps(data, 0)))
|
repr(pickle.loads(pickle.dumps(data, 0)))
|
||||||
|
|
||||||
|
|
||||||
|
# test for issue #725
|
||||||
|
class SeveralChoicesModel(models.Model):
|
||||||
|
color = models.CharField(
|
||||||
|
max_length=10,
|
||||||
|
choices=[('red', 'Red'), ('green', 'Green'), ('blue', 'Blue')],
|
||||||
|
blank=False
|
||||||
|
)
|
||||||
|
drink = models.CharField(
|
||||||
|
max_length=10,
|
||||||
|
choices=[('beer', 'Beer'), ('wine', 'Wine'), ('cider', 'Cider')],
|
||||||
|
blank=False,
|
||||||
|
default='beer'
|
||||||
|
)
|
||||||
|
os = models.CharField(
|
||||||
|
max_length=10,
|
||||||
|
choices=[('linux', 'Linux'), ('osx', 'OSX'), ('windows', 'Windows')],
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
music_genre = models.CharField(
|
||||||
|
max_length=10,
|
||||||
|
choices=[('rock', 'Rock'), ('metal', 'Metal'), ('grunge', 'Grunge')],
|
||||||
|
blank=True,
|
||||||
|
default='metal'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SerializerChoiceFields(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(SerializerChoiceFields, self).setUp()
|
||||||
|
|
||||||
|
class SeveralChoicesSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SeveralChoicesModel
|
||||||
|
fields = ('color', 'drink', 'os', 'music_genre')
|
||||||
|
|
||||||
|
self.several_choices_serializer = SeveralChoicesSerializer
|
||||||
|
|
||||||
|
def test_choices_blank_false_not_default(self):
|
||||||
|
serializer = self.several_choices_serializer()
|
||||||
|
self.assertEqual(
|
||||||
|
serializer.fields['color'].choices,
|
||||||
|
[('red', 'Red'), ('green', 'Green'), ('blue', 'Blue')]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_choices_blank_false_with_default(self):
|
||||||
|
serializer = self.several_choices_serializer()
|
||||||
|
self.assertEqual(
|
||||||
|
serializer.fields['drink'].choices,
|
||||||
|
[('beer', 'Beer'), ('wine', 'Wine'), ('cider', 'Cider')]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_choices_blank_true_not_default(self):
|
||||||
|
serializer = self.several_choices_serializer()
|
||||||
|
self.assertEqual(
|
||||||
|
serializer.fields['os'].choices,
|
||||||
|
BLANK_CHOICE_DASH + [('linux', 'Linux'), ('osx', 'OSX'), ('windows', 'Windows')]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_choices_blank_true_with_default(self):
|
||||||
|
serializer = self.several_choices_serializer()
|
||||||
|
self.assertEqual(
|
||||||
|
serializer.fields['music_genre'].choices,
|
||||||
|
BLANK_CHOICE_DASH + [('rock', 'Rock'), ('metal', 'Metal'), ('grunge', 'Grunge')]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DepthTest(TestCase):
|
class DepthTest(TestCase):
|
||||||
def test_implicit_nesting(self):
|
def test_implicit_nesting(self):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user