Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
django-rest-framework
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
django-rest-framework
Commits
f22d0afc
Commit
f22d0afc
authored
Sep 23, 2014
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Tests for field choices
parent
5d80f7f9
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
149 additions
and
102 deletions
+149
-102
rest_framework/fields.py
+11
-10
rest_framework/serializers.py
+3
-0
rest_framework/utils/field_mapping.py
+30
-28
tests/test_field_options.py
+0
-55
tests/test_fields.py
+67
-0
tests/test_model_serializer.py
+38
-9
No files found.
rest_framework/fields.py
View file @
f22d0afc
...
...
@@ -102,6 +102,7 @@ class Field(object):
'null'
:
_
(
'This field may not be null.'
)
}
default_validators
=
[]
default_empty_html
=
None
def
__init__
(
self
,
read_only
=
False
,
write_only
=
False
,
required
=
None
,
default
=
empty
,
initial
=
None
,
source
=
None
,
...
...
@@ -185,6 +186,11 @@ class Field(object):
Given the *incoming* primative data, return the value for this field
that should be validated and transformed to a native value.
"""
if
html
.
is_html_input
(
dictionary
):
# HTML forms will represent empty fields as '', and cannot
# represent None or False values directly.
ret
=
dictionary
.
get
(
self
.
field_name
,
''
)
return
self
.
default_empty_html
if
(
ret
==
''
)
else
ret
return
dictionary
.
get
(
self
.
field_name
,
empty
)
def
get_attribute
(
self
,
instance
):
...
...
@@ -236,9 +242,6 @@ class Field(object):
Test the given value against all the validators on the field,
and either raise a `ValidationError` or simply return.
"""
if
value
in
(
None
,
''
,
[],
(),
{}):
return
errors
=
[]
for
validator
in
self
.
validators
:
try
:
...
...
@@ -282,16 +285,10 @@ class BooleanField(Field):
default_error_messages
=
{
'invalid'
:
_
(
'`{input}` is not a valid boolean.'
)
}
default_empty_html
=
False
TRUE_VALUES
=
set
((
't'
,
'T'
,
'true'
,
'True'
,
'TRUE'
,
'1'
,
1
,
True
))
FALSE_VALUES
=
set
((
'f'
,
'F'
,
'false'
,
'False'
,
'FALSE'
,
'0'
,
0
,
0.0
,
False
))
def
get_value
(
self
,
dictionary
):
if
html
.
is_html_input
(
dictionary
):
# HTML forms do not send a `False` value on an empty checkbox,
# so we override the default empty value to be False.
return
dictionary
.
get
(
self
.
field_name
,
False
)
return
dictionary
.
get
(
self
.
field_name
,
empty
)
def
to_internal_value
(
self
,
data
):
if
data
in
self
.
TRUE_VALUES
:
return
True
...
...
@@ -315,6 +312,7 @@ class CharField(Field):
default_error_messages
=
{
'blank'
:
_
(
'This field may not be blank.'
)
}
default_empty_html
=
''
def
__init__
(
self
,
**
kwargs
):
self
.
allow_blank
=
kwargs
.
pop
(
'allow_blank'
,
False
)
...
...
@@ -323,6 +321,9 @@ class CharField(Field):
super
(
CharField
,
self
)
.
__init__
(
**
kwargs
)
def
run_validation
(
self
,
data
=
empty
):
# Test for the empty string here so that it does not get validated,
# and so that subclasses do not need to handle it explicitly
# inside the `to_internal_value()` method.
if
data
==
''
:
if
not
self
.
allow_blank
:
self
.
fail
(
'blank'
)
...
...
rest_framework/serializers.py
View file @
f22d0afc
...
...
@@ -411,6 +411,9 @@ class ModelSerializer(Serializer):
# `ModelField`, which is used when no other typed field
# matched to the model field.
kwargs
.
pop
(
'model_field'
,
None
)
if
not
issubclass
(
field_cls
,
CharField
):
# `allow_blank` is only valid for textual fields.
kwargs
.
pop
(
'allow_blank'
,
None
)
elif
field_name
in
info
.
relations
:
# Create forward and reverse relationships.
...
...
rest_framework/utils/field_mapping.py
View file @
f22d0afc
...
...
@@ -49,8 +49,9 @@ def get_field_kwargs(field_name, model_field):
kwargs
=
{}
validator_kwarg
=
model_field
.
validators
if
model_field
.
null
or
model_field
.
blank
:
kwargs
[
'required'
]
=
False
# The following will only be used by ModelField classes.
# Gets removed for everything else.
kwargs
[
'model_field'
]
=
model_field
if
model_field
.
verbose_name
and
needs_label
(
model_field
,
field_name
):
kwargs
[
'label'
]
=
capfirst
(
model_field
.
verbose_name
)
...
...
@@ -59,23 +60,26 @@ def get_field_kwargs(field_name, model_field):
kwargs
[
'help_text'
]
=
model_field
.
help_text
if
isinstance
(
model_field
,
models
.
AutoField
)
or
not
model_field
.
editable
:
# If this field is read-only, then return early.
# Further keyword arguments are not valid.
kwargs
[
'read_only'
]
=
True
# Read only implies that the field is not required.
# We have a cleaner repr on the instance if we don't set it.
kwargs
.
pop
(
'required'
,
None
)
return
kwargs
if
model_field
.
has_default
():
kwargs
[
'default'
]
=
model_field
.
get_default
()
# Having a default implies that the field is not required.
# We have a cleaner repr on the instance if we don't set it.
kwargs
.
pop
(
'required'
,
None
)
kwargs
[
'required'
]
=
False
if
model_field
.
flatchoices
:
# If this model field contains choices, then return
now,
#
any f
urther keyword arguments are not valid.
# If this model field contains choices, then return
early.
#
F
urther keyword arguments are not valid.
kwargs
[
'choices'
]
=
model_field
.
flatchoices
return
kwargs
if
model_field
.
null
:
kwargs
[
'allow_null'
]
=
True
if
model_field
.
blank
:
kwargs
[
'allow_blank'
]
=
True
# Ensure that max_length is passed explicitly as a keyword arg,
# rather than as a validator.
max_length
=
getattr
(
model_field
,
'max_length'
,
None
)
...
...
@@ -88,7 +92,10 @@ def get_field_kwargs(field_name, model_field):
# Ensure that min_length is passed explicitly as a keyword arg,
# rather than as a validator.
min_length
=
getattr
(
model_field
,
'min_length'
,
None
)
min_length
=
next
((
validator
.
limit_value
for
validator
in
validator_kwarg
if
isinstance
(
validator
,
validators
.
MinLengthValidator
)
),
None
)
if
min_length
is
not
None
:
kwargs
[
'min_length'
]
=
min_length
validator_kwarg
=
[
...
...
@@ -153,20 +160,9 @@ def get_field_kwargs(field_name, model_field):
if
decimal_places
is
not
None
:
kwargs
[
'decimal_places'
]
=
decimal_places
if
isinstance
(
model_field
,
models
.
BooleanField
):
# models.BooleanField has `blank=True`, but *is* actually
# required *unless* a default is provided.
# Also note that Django<1.6 uses `default=False` for
# models.BooleanField, but Django>=1.6 uses `default=None`.
kwargs
.
pop
(
'required'
,
None
)
if
validator_kwarg
:
kwargs
[
'validators'
]
=
validator_kwarg
# The following will only be used by ModelField classes.
# Gets removed for everything else.
kwargs
[
'model_field'
]
=
model_field
return
kwargs
...
...
@@ -188,16 +184,22 @@ def get_relation_kwargs(field_name, relation_info):
kwargs
.
pop
(
'queryset'
,
None
)
if
model_field
:
if
model_field
.
null
or
model_field
.
blank
:
kwargs
[
'required'
]
=
False
if
model_field
.
verbose_name
and
needs_label
(
model_field
,
field_name
):
kwargs
[
'label'
]
=
capfirst
(
model_field
.
verbose_name
)
if
not
model_field
.
editable
:
kwargs
[
'read_only'
]
=
True
kwargs
.
pop
(
'queryset'
,
None
)
help_text
=
clean_manytomany_helptext
(
model_field
.
help_text
)
if
help_text
:
kwargs
[
'help_text'
]
=
help_text
if
not
model_field
.
editable
:
kwargs
[
'read_only'
]
=
True
kwargs
.
pop
(
'queryset'
,
None
)
if
kwargs
.
get
(
'read_only'
,
False
):
# If this field is read-only, then return early.
# No further keyword arguments are valid.
return
kwargs
if
model_field
.
has_default
():
kwargs
[
'required'
]
=
False
if
model_field
.
null
:
kwargs
[
'allow_null'
]
=
True
return
kwargs
...
...
tests/test_field_options.py
deleted
100644 → 0
View file @
5d80f7f9
from
rest_framework
import
fields
import
pytest
class
TestFieldOptions
:
def
test_required
(
self
):
"""
By default a field must be included in the input.
"""
field
=
fields
.
IntegerField
()
with
pytest
.
raises
(
fields
.
ValidationError
)
as
exc_info
:
field
.
run_validation
()
assert
exc_info
.
value
.
messages
==
[
'This field is required.'
]
def
test_not_required
(
self
):
"""
If `required=False` then a field may be omitted from the input.
"""
field
=
fields
.
IntegerField
(
required
=
False
)
with
pytest
.
raises
(
fields
.
SkipField
):
field
.
run_validation
()
def
test_disallow_null
(
self
):
"""
By default `None` is not a valid input.
"""
field
=
fields
.
IntegerField
()
with
pytest
.
raises
(
fields
.
ValidationError
)
as
exc_info
:
field
.
run_validation
(
None
)
assert
exc_info
.
value
.
messages
==
[
'This field may not be null.'
]
def
test_allow_null
(
self
):
"""
If `allow_null=True` then `None` is a valid input.
"""
field
=
fields
.
IntegerField
(
allow_null
=
True
)
output
=
field
.
run_validation
(
None
)
assert
output
is
None
def
test_disallow_blank
(
self
):
"""
By default '' is not a valid input.
"""
field
=
fields
.
CharField
()
with
pytest
.
raises
(
fields
.
ValidationError
)
as
exc_info
:
field
.
run_validation
(
''
)
assert
exc_info
.
value
.
messages
==
[
'This field may not be blank.'
]
def
test_allow_blank
(
self
):
"""
If `allow_blank=True` then '' is a valid input.
"""
field
=
fields
.
CharField
(
allow_blank
=
True
)
output
=
field
.
run_validation
(
''
)
assert
output
is
''
tests/test_field
_value
s.py
→
tests/test_fields.py
View file @
f22d0afc
...
...
@@ -6,6 +6,73 @@ import django
import
pytest
# Tests for field keyword arguments and core functionality.
# ---------------------------------------------------------
class
TestFieldOptions
:
def
test_required
(
self
):
"""
By default a field must be included in the input.
"""
field
=
fields
.
IntegerField
()
with
pytest
.
raises
(
fields
.
ValidationError
)
as
exc_info
:
field
.
run_validation
()
assert
exc_info
.
value
.
messages
==
[
'This field is required.'
]
def
test_not_required
(
self
):
"""
If `required=False` then a field may be omitted from the input.
"""
field
=
fields
.
IntegerField
(
required
=
False
)
with
pytest
.
raises
(
fields
.
SkipField
):
field
.
run_validation
()
def
test_disallow_null
(
self
):
"""
By default `None` is not a valid input.
"""
field
=
fields
.
IntegerField
()
with
pytest
.
raises
(
fields
.
ValidationError
)
as
exc_info
:
field
.
run_validation
(
None
)
assert
exc_info
.
value
.
messages
==
[
'This field may not be null.'
]
def
test_allow_null
(
self
):
"""
If `allow_null=True` then `None` is a valid input.
"""
field
=
fields
.
IntegerField
(
allow_null
=
True
)
output
=
field
.
run_validation
(
None
)
assert
output
is
None
def
test_disallow_blank
(
self
):
"""
By default '' is not a valid input.
"""
field
=
fields
.
CharField
()
with
pytest
.
raises
(
fields
.
ValidationError
)
as
exc_info
:
field
.
run_validation
(
''
)
assert
exc_info
.
value
.
messages
==
[
'This field may not be blank.'
]
def
test_allow_blank
(
self
):
"""
If `allow_blank=True` then '' is a valid input.
"""
field
=
fields
.
CharField
(
allow_blank
=
True
)
output
=
field
.
run_validation
(
''
)
assert
output
is
''
def
test_default
(
self
):
"""
If `default` is set, then omitted values get the default input.
"""
field
=
fields
.
IntegerField
(
default
=
123
)
output
=
field
.
run_validation
()
assert
output
is
123
# Tests for field input and output values.
# ----------------------------------------
def
get_items
(
mapping_or_list_of_two_tuples
):
# Tests accept either lists of two tuples, or dictionaries.
if
isinstance
(
mapping_or_list_of_two_tuples
,
dict
):
...
...
tests/test_model_serializer.py
View file @
f22d0afc
...
...
@@ -6,6 +6,7 @@ These tests deal with ensuring that we correctly map the model fields onto
an appropriate set of serializer fields for each case.
"""
from
django.core.exceptions
import
ImproperlyConfigured
from
django.core.validators
import
MaxValueValidator
,
MinValueValidator
,
MinLengthValidator
from
django.db
import
models
from
django.test
import
TestCase
from
rest_framework
import
serializers
...
...
@@ -15,7 +16,8 @@ def dedent(blocktext):
return
'
\n
'
.
join
([
line
[
12
:]
for
line
in
blocktext
.
splitlines
()[
1
:
-
1
]])
# Testing regular field mappings
# Tests for regular field mappings.
# ---------------------------------
class
CustomField
(
models
.
Field
):
"""
...
...
@@ -24,9 +26,6 @@ class CustomField(models.Field):
pass
COLOR_CHOICES
=
((
'red'
,
'Red'
),
(
'blue'
,
'Blue'
),
(
'green'
,
'Green'
))
class
RegularFieldsModel
(
models
.
Model
):
"""
A model class for testing regular flat fields.
...
...
@@ -35,7 +34,6 @@ class RegularFieldsModel(models.Model):
big_integer_field
=
models
.
BigIntegerField
()
boolean_field
=
models
.
BooleanField
(
default
=
False
)
char_field
=
models
.
CharField
(
max_length
=
100
)
choices_field
=
models
.
CharField
(
max_length
=
100
,
choices
=
COLOR_CHOICES
)
comma_seperated_integer_field
=
models
.
CommaSeparatedIntegerField
(
max_length
=
100
)
date_field
=
models
.
DateField
()
datetime_field
=
models
.
DateTimeField
()
...
...
@@ -57,6 +55,19 @@ class RegularFieldsModel(models.Model):
return
'method'
COLOR_CHOICES
=
((
'red'
,
'Red'
),
(
'blue'
,
'Blue'
),
(
'green'
,
'Green'
))
class
FieldOptionsModel
(
models
.
Model
):
value_limit_field
=
models
.
IntegerField
(
validators
=
[
MinValueValidator
(
1
),
MaxValueValidator
(
10
)])
length_limit_field
=
models
.
CharField
(
validators
=
[
MinLengthValidator
(
3
)],
max_length
=
12
)
blank_field
=
models
.
CharField
(
blank
=
True
,
max_length
=
10
)
null_field
=
models
.
IntegerField
(
null
=
True
)
default_field
=
models
.
IntegerField
(
default
=
0
)
descriptive_field
=
models
.
IntegerField
(
help_text
=
'Some help text'
,
verbose_name
=
'A label'
)
choices_field
=
models
.
CharField
(
max_length
=
100
,
choices
=
COLOR_CHOICES
)
class
TestRegularFieldMappings
(
TestCase
):
def
test_regular_fields
(
self
):
"""
...
...
@@ -70,9 +81,8 @@ class TestRegularFieldMappings(TestCase):
TestSerializer():
auto_field = IntegerField(read_only=True)
big_integer_field = IntegerField()
boolean_field = BooleanField(
default
=False)
boolean_field = BooleanField(
required
=False)
char_field = CharField(max_length=100)
choices_field = ChoiceField(choices=[('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')])
comma_seperated_integer_field = CharField(max_length=100, validators=[<django.core.validators.RegexValidator object>])
date_field = DateField()
datetime_field = DateTimeField()
...
...
@@ -80,7 +90,7 @@ class TestRegularFieldMappings(TestCase):
email_field = EmailField(max_length=100)
float_field = FloatField()
integer_field = IntegerField()
null_boolean_field = BooleanField(
required=Fals
e)
null_boolean_field = BooleanField(
allow_null=Tru
e)
positive_integer_field = IntegerField()
positive_small_integer_field = IntegerField()
slug_field = SlugField(max_length=100)
...
...
@@ -92,6 +102,24 @@ class TestRegularFieldMappings(TestCase):
"""
)
self
.
assertEqual
(
repr
(
TestSerializer
()),
expected
)
def
test_field_options
(
self
):
class
TestSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
FieldOptionsModel
expected
=
dedent
(
"""
TestSerializer():
id = IntegerField(label='ID', read_only=True)
value_limit_field = IntegerField(max_value=10, min_value=1)
length_limit_field = CharField(max_length=12, min_length=3)
blank_field = CharField(allow_blank=True, max_length=10)
null_field = IntegerField(allow_null=True)
default_field = IntegerField(required=False)
descriptive_field = IntegerField(help_text='Some help text', label='A label')
choices_field = ChoiceField(choices=[('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')])
"""
)
self
.
assertEqual
(
repr
(
TestSerializer
()),
expected
)
def
test_method_field
(
self
):
"""
Properties and methods on the model should be allowed as `Meta.fields`
...
...
@@ -178,7 +206,8 @@ class TestRegularFieldMappings(TestCase):
assert
str
(
excinfo
.
exception
)
==
expected
# Testing relational field mappings
# Tests for relational field mappings.
# ------------------------------------
class
ForeignKeyTargetModel
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
100
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment