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
af46fd6b
Commit
af46fd6b
authored
Sep 22, 2014
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Field tests and associated cleanup
parent
cf72b9a8
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
365 additions
and
33 deletions
+365
-33
rest_framework/fields.py
+31
-33
tests/test_fields.py
+334
-0
No files found.
rest_framework/fields.py
View file @
af46fd6b
...
@@ -12,7 +12,6 @@ from rest_framework.utils import html, representation, humanize_datetime
...
@@ -12,7 +12,6 @@ from rest_framework.utils import html, representation, humanize_datetime
import
datetime
import
datetime
import
decimal
import
decimal
import
inspect
import
inspect
import
warnings
class
empty
:
class
empty
:
...
@@ -395,7 +394,7 @@ class IntegerField(Field):
...
@@ -395,7 +394,7 @@ class IntegerField(Field):
class
FloatField
(
Field
):
class
FloatField
(
Field
):
default_error_messages
=
{
default_error_messages
=
{
'invalid'
:
_
(
"
'
%
s' value must be a float
."
),
'invalid'
:
_
(
"
A valid number is required
."
),
}
}
def
__init__
(
self
,
**
kwargs
):
def
__init__
(
self
,
**
kwargs
):
...
@@ -410,20 +409,20 @@ class FloatField(Field):
...
@@ -410,20 +409,20 @@ class FloatField(Field):
def
to_internal_value
(
self
,
value
):
def
to_internal_value
(
self
,
value
):
if
value
is
None
:
if
value
is
None
:
return
None
return
None
return
float
(
value
)
try
:
return
float
(
value
)
except
(
TypeError
,
ValueError
):
self
.
fail
(
'invalid'
)
def
to_representation
(
self
,
value
):
def
to_representation
(
self
,
value
):
if
value
is
None
:
if
value
is
None
:
return
None
return
None
try
:
return
float
(
value
)
return
float
(
value
)
except
(
TypeError
,
ValueError
):
self
.
fail
(
'invalid'
,
value
=
value
)
class
DecimalField
(
Field
):
class
DecimalField
(
Field
):
default_error_messages
=
{
default_error_messages
=
{
'invalid'
:
_
(
'
Enter a number
.'
),
'invalid'
:
_
(
'
A valid number is required
.'
),
'max_value'
:
_
(
'Ensure this value is less than or equal to {max_value}.'
),
'max_value'
:
_
(
'Ensure this value is less than or equal to {max_value}.'
),
'min_value'
:
_
(
'Ensure this value is greater than or equal to {min_value}.'
),
'min_value'
:
_
(
'Ensure this value is greater than or equal to {min_value}.'
),
'max_digits'
:
_
(
'Ensure that there are no more than {max_digits} digits in total.'
),
'max_digits'
:
_
(
'Ensure that there are no more than {max_digits} digits in total.'
),
...
@@ -485,7 +484,7 @@ class DecimalField(Field):
...
@@ -485,7 +484,7 @@ class DecimalField(Field):
if
self
.
decimal_places
is
not
None
and
decimals
>
self
.
decimal_places
:
if
self
.
decimal_places
is
not
None
and
decimals
>
self
.
decimal_places
:
self
.
fail
(
'max_decimal_places'
,
max_decimal_places
=
self
.
decimal_places
)
self
.
fail
(
'max_decimal_places'
,
max_decimal_places
=
self
.
decimal_places
)
if
self
.
max_digits
is
not
None
and
self
.
decimal_places
is
not
None
and
whole_digits
>
(
self
.
max_digits
-
self
.
decimal_places
):
if
self
.
max_digits
is
not
None
and
self
.
decimal_places
is
not
None
and
whole_digits
>
(
self
.
max_digits
-
self
.
decimal_places
):
self
.
fail
(
'max_whole_digits'
,
max_wh
i
le_digits
=
self
.
max_digits
-
self
.
decimal_places
)
self
.
fail
(
'max_whole_digits'
,
max_wh
o
le_digits
=
self
.
max_digits
-
self
.
decimal_places
)
return
value
return
value
...
@@ -511,6 +510,7 @@ class DecimalField(Field):
...
@@ -511,6 +510,7 @@ class DecimalField(Field):
class
DateField
(
Field
):
class
DateField
(
Field
):
default_error_messages
=
{
default_error_messages
=
{
'invalid'
:
_
(
'Date has wrong format. Use one of these formats instead: {format}'
),
'invalid'
:
_
(
'Date has wrong format. Use one of these formats instead: {format}'
),
'datetime'
:
_
(
'Expected a date but got a datetime.'
),
}
}
format
=
api_settings
.
DATE_FORMAT
format
=
api_settings
.
DATE_FORMAT
input_formats
=
api_settings
.
DATE_INPUT_FORMATS
input_formats
=
api_settings
.
DATE_INPUT_FORMATS
...
@@ -525,12 +525,7 @@ class DateField(Field):
...
@@ -525,12 +525,7 @@ class DateField(Field):
return
None
return
None
if
isinstance
(
value
,
datetime
.
datetime
):
if
isinstance
(
value
,
datetime
.
datetime
):
if
timezone
and
settings
.
USE_TZ
and
timezone
.
is_aware
(
value
):
self
.
fail
(
'datetime'
)
# Convert aware datetimes to the default time zone
# before casting them to dates (#17742).
default_timezone
=
timezone
.
get_default_timezone
()
value
=
timezone
.
make_naive
(
value
,
default_timezone
)
return
value
.
date
()
if
isinstance
(
value
,
datetime
.
date
):
if
isinstance
(
value
,
datetime
.
date
):
return
value
return
value
...
@@ -570,35 +565,38 @@ class DateField(Field):
...
@@ -570,35 +565,38 @@ class DateField(Field):
class
DateTimeField
(
Field
):
class
DateTimeField
(
Field
):
default_error_messages
=
{
default_error_messages
=
{
'invalid'
:
_
(
'Datetime has wrong format. Use one of these formats instead: {format}'
),
'invalid'
:
_
(
'Datetime has wrong format. Use one of these formats instead: {format}'
),
'date'
:
_
(
'Expected a datetime but got a date.'
),
}
}
format
=
api_settings
.
DATETIME_FORMAT
format
=
api_settings
.
DATETIME_FORMAT
input_formats
=
api_settings
.
DATETIME_INPUT_FORMATS
input_formats
=
api_settings
.
DATETIME_INPUT_FORMATS
default_timezone
=
timezone
.
get_default_timezone
()
if
settings
.
USE_TZ
else
None
def
__init__
(
self
,
format
=
None
,
input_formats
=
None
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
format
=
None
,
input_formats
=
None
,
default_timezone
=
None
,
*
args
,
**
kwargs
):
self
.
format
=
format
if
format
is
not
None
else
self
.
format
self
.
format
=
format
if
format
is
not
None
else
self
.
format
self
.
input_formats
=
input_formats
if
input_formats
is
not
None
else
self
.
input_formats
self
.
input_formats
=
input_formats
if
input_formats
is
not
None
else
self
.
input_formats
self
.
default_timezone
=
default_timezone
if
default_timezone
is
not
None
else
self
.
default_timezone
super
(
DateTimeField
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
super
(
DateTimeField
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
def
enforce_timezone
(
self
,
value
):
"""
When `self.default_timezone` is `None`, always return naive datetimes.
When `self.default_timezone` is not `None`, always return aware datetimes.
"""
if
(
self
.
default_timezone
is
not
None
)
and
not
timezone
.
is_aware
(
value
):
return
timezone
.
make_aware
(
value
,
self
.
default_timezone
)
elif
(
self
.
default_timezone
is
None
)
and
timezone
.
is_aware
(
value
):
return
timezone
.
make_naive
(
value
,
timezone
.
UTC
())
return
value
def
to_internal_value
(
self
,
value
):
def
to_internal_value
(
self
,
value
):
if
value
in
(
None
,
''
):
if
value
in
(
None
,
''
):
return
None
return
None
if
isinstance
(
value
,
datetime
.
datetime
):
if
isinstance
(
value
,
datetime
.
date
)
and
not
isinstance
(
value
,
datetime
.
date
time
):
return
value
self
.
fail
(
'date'
)
if
isinstance
(
value
,
datetime
.
date
):
if
isinstance
(
value
,
datetime
.
datetime
):
value
=
datetime
.
datetime
(
value
.
year
,
value
.
month
,
value
.
day
)
return
self
.
enforce_timezone
(
value
)
if
settings
.
USE_TZ
:
# For backwards compatibility, interpret naive datetimes in
# local time. This won't work during DST change, but we can't
# do much about it, so we let the exceptions percolate up the
# call stack.
warnings
.
warn
(
"DateTimeField received a naive datetime (
%
s)"
" while time zone support is active."
%
value
,
RuntimeWarning
)
default_timezone
=
timezone
.
get_default_timezone
()
value
=
timezone
.
make_aware
(
value
,
default_timezone
)
return
value
for
format
in
self
.
input_formats
:
for
format
in
self
.
input_formats
:
if
format
.
lower
()
==
ISO_8601
:
if
format
.
lower
()
==
ISO_8601
:
...
@@ -608,14 +606,14 @@ class DateTimeField(Field):
...
@@ -608,14 +606,14 @@ class DateTimeField(Field):
pass
pass
else
:
else
:
if
parsed
is
not
None
:
if
parsed
is
not
None
:
return
parsed
return
self
.
enforce_timezone
(
parsed
)
else
:
else
:
try
:
try
:
parsed
=
datetime
.
datetime
.
strptime
(
value
,
format
)
parsed
=
datetime
.
datetime
.
strptime
(
value
,
format
)
except
(
ValueError
,
TypeError
):
except
(
ValueError
,
TypeError
):
pass
pass
else
:
else
:
return
parsed
return
self
.
enforce_timezone
(
parsed
)
humanized_format
=
humanize_datetime
.
datetime_formats
(
self
.
input_formats
)
humanized_format
=
humanize_datetime
.
datetime_formats
(
self
.
input_formats
)
self
.
fail
(
'invalid'
,
format
=
humanized_format
)
self
.
fail
(
'invalid'
,
format
=
humanized_format
)
...
...
tests/test_fields.py
View file @
af46fd6b
from
decimal
import
Decimal
from
django.utils
import
timezone
from
rest_framework
import
fields
import
datetime
import
pytest
class
ValidAndInvalidValues
:
"""
Base class for testing valid and invalid field values.
"""
def
test_valid_values
(
self
):
"""
Ensure that valid values return the expected validated data.
"""
for
input_value
,
expected_output
in
self
.
valid_mappings
.
items
():
assert
self
.
field
.
run_validation
(
input_value
)
==
expected_output
def
test_invalid_values
(
self
):
"""
Ensure that invalid values raise the expected validation error.
"""
for
input_value
,
expected_failure
in
self
.
invalid_mappings
.
items
():
with
pytest
.
raises
(
fields
.
ValidationError
)
as
exc_info
:
self
.
field
.
run_validation
(
input_value
)
assert
exc_info
.
value
.
messages
==
expected_failure
class
TestCharField
(
ValidAndInvalidValues
):
valid_mappings
=
{
1
:
'1'
,
'abc'
:
'abc'
}
invalid_mappings
=
{
''
:
[
'This field may not be blank.'
]
}
field
=
fields
.
CharField
()
class
TestBooleanField
(
ValidAndInvalidValues
):
valid_mappings
=
{
'true'
:
True
,
'false'
:
False
,
'1'
:
True
,
'0'
:
False
,
1
:
True
,
0
:
False
,
True
:
True
,
False
:
False
,
}
invalid_mappings
=
{
'foo'
:
[
'`foo` is not a valid boolean.'
]
}
field
=
fields
.
BooleanField
()
# Number types...
class
TestIntegerField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `IntegerField`.
"""
valid_mappings
=
{
'1'
:
1
,
'0'
:
0
,
1
:
1
,
0
:
0
,
1.0
:
1
,
0.0
:
0
}
invalid_mappings
=
{
'abc'
:
[
'A valid integer is required.'
]
}
field
=
fields
.
IntegerField
()
class
TestMinMaxIntegerField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `IntegerField` with min and max limits.
"""
valid_mappings
=
{
'1'
:
1
,
'3'
:
3
,
1
:
1
,
3
:
3
,
}
invalid_mappings
=
{
0
:
[
'Ensure this value is greater than or equal to 1.'
],
4
:
[
'Ensure this value is less than or equal to 3.'
],
'0'
:
[
'Ensure this value is greater than or equal to 1.'
],
'4'
:
[
'Ensure this value is less than or equal to 3.'
],
}
field
=
fields
.
IntegerField
(
min_value
=
1
,
max_value
=
3
)
class
TestFloatField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `FloatField`.
"""
valid_mappings
=
{
'1'
:
1.0
,
'0'
:
0.0
,
1
:
1.0
,
0
:
0.0
,
1.0
:
1.0
,
0.0
:
0.0
,
}
invalid_mappings
=
{
'abc'
:
[
"A valid number is required."
]
}
field
=
fields
.
FloatField
()
class
TestMinMaxFloatField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `FloatField` with min and max limits.
"""
valid_mappings
=
{
'1'
:
1
,
'3'
:
3
,
1
:
1
,
3
:
3
,
1.0
:
1.0
,
3.0
:
3.0
,
}
invalid_mappings
=
{
0.9
:
[
'Ensure this value is greater than or equal to 1.'
],
3.1
:
[
'Ensure this value is less than or equal to 3.'
],
'0.0'
:
[
'Ensure this value is greater than or equal to 1.'
],
'3.1'
:
[
'Ensure this value is less than or equal to 3.'
],
}
field
=
fields
.
FloatField
(
min_value
=
1
,
max_value
=
3
)
class
TestDecimalField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `DecimalField`.
"""
valid_mappings
=
{
'12.3'
:
Decimal
(
'12.3'
),
'0.1'
:
Decimal
(
'0.1'
),
10
:
Decimal
(
'10'
),
0
:
Decimal
(
'0'
),
12.3
:
Decimal
(
'12.3'
),
0.1
:
Decimal
(
'0.1'
),
}
invalid_mappings
=
{
'abc'
:
[
"A valid number is required."
],
Decimal
(
'Nan'
):
[
"A valid number is required."
],
Decimal
(
'Inf'
):
[
"A valid number is required."
],
'12.345'
:
[
"Ensure that there are no more than 3 digits in total."
],
'0.01'
:
[
"Ensure that there are no more than 1 decimal places."
],
123
:
[
"Ensure that there are no more than 2 digits before the decimal point."
]
}
field
=
fields
.
DecimalField
(
max_digits
=
3
,
decimal_places
=
1
)
class
TestMinMaxDecimalField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `DecimalField` with min and max limits.
"""
valid_mappings
=
{
'10.0'
:
10.0
,
'20.0'
:
20.0
,
}
invalid_mappings
=
{
'9.9'
:
[
'Ensure this value is greater than or equal to 10.'
],
'20.1'
:
[
'Ensure this value is less than or equal to 20.'
],
}
field
=
fields
.
DecimalField
(
max_digits
=
3
,
decimal_places
=
1
,
min_value
=
10
,
max_value
=
20
)
# Date & time fields...
class
TestDateField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `DateField`.
"""
valid_mappings
=
{
'2001-01-01'
:
datetime
.
date
(
2001
,
1
,
1
),
datetime
.
date
(
2001
,
1
,
1
):
datetime
.
date
(
2001
,
1
,
1
),
}
invalid_mappings
=
{
'abc'
:
[
'Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]]'
],
'2001-99-99'
:
[
'Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]]'
],
datetime
.
datetime
(
2001
,
1
,
1
,
12
,
00
):
[
'Expected a date but got a datetime.'
],
}
field
=
fields
.
DateField
()
class
TestCustomInputFormatDateField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `DateField` with a cutom input format.
"""
valid_mappings
=
{
'1 Jan 2001'
:
datetime
.
date
(
2001
,
1
,
1
),
}
invalid_mappings
=
{
'2001-01-01'
:
[
'Date has wrong format. Use one of these formats instead: DD [Jan-Dec] YYYY'
]
}
field
=
fields
.
DateField
(
input_formats
=
[
'
%
d
%
b
%
Y'
])
class
TestDateTimeField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `DateTimeField`.
"""
valid_mappings
=
{
'2001-01-01 13:00'
:
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
00
,
tzinfo
=
timezone
.
UTC
()),
'2001-01-01T13:00'
:
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
00
,
tzinfo
=
timezone
.
UTC
()),
'2001-01-01T13:00Z'
:
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
00
,
tzinfo
=
timezone
.
UTC
()),
'2001-01-01T14:00+0100'
:
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
00
,
tzinfo
=
timezone
.
UTC
()),
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
00
):
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
00
,
tzinfo
=
timezone
.
UTC
()),
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
00
,
tzinfo
=
timezone
.
UTC
()):
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
00
,
tzinfo
=
timezone
.
UTC
()),
}
invalid_mappings
=
{
'abc'
:
[
'Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]'
],
'2001-99-99T99:00'
:
[
'Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]'
],
datetime
.
date
(
2001
,
1
,
1
):
[
'Expected a datetime but got a date.'
],
}
field
=
fields
.
DateTimeField
(
default_timezone
=
timezone
.
UTC
())
class
TestCustomInputFormatDateTimeField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `DateTimeField` with a cutom input format.
"""
valid_mappings
=
{
'1:35pm, 1 Jan 2001'
:
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
35
,
tzinfo
=
timezone
.
UTC
()),
}
invalid_mappings
=
{
'2001-01-01T20:50'
:
[
'Datetime has wrong format. Use one of these formats instead: hh:mm[AM|PM], DD [Jan-Dec] YYYY'
]
}
field
=
fields
.
DateTimeField
(
default_timezone
=
timezone
.
UTC
(),
input_formats
=
[
'
%
I:
%
M
%
p,
%
d
%
b
%
Y'
])
class
TestNaiveDateTimeField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `DateTimeField` with naive datetimes.
"""
valid_mappings
=
{
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
00
,
tzinfo
=
timezone
.
UTC
()):
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
00
),
'2001-01-01 13:00'
:
datetime
.
datetime
(
2001
,
1
,
1
,
13
,
00
),
}
invalid_mappings
=
{}
field
=
fields
.
DateTimeField
(
default_timezone
=
None
)
# Choice types...
class
TestChoiceField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `ChoiceField`.
"""
valid_mappings
=
{
'poor'
:
'poor'
,
'medium'
:
'medium'
,
'good'
:
'good'
,
}
invalid_mappings
=
{
'awful'
:
[
'`awful` is not a valid choice.'
]
}
field
=
fields
.
ChoiceField
(
choices
=
[
(
'poor'
,
'Poor quality'
),
(
'medium'
,
'Medium quality'
),
(
'good'
,
'Good quality'
),
]
)
class
TestChoiceFieldWithType
(
ValidAndInvalidValues
):
"""
Valid and invalid values for a `Choice` field that uses an integer type,
instead of a char type.
"""
valid_mappings
=
{
'1'
:
1
,
3
:
3
,
}
invalid_mappings
=
{
5
:
[
'`5` is not a valid choice.'
],
'abc'
:
[
'`abc` is not a valid choice.'
]
}
field
=
fields
.
ChoiceField
(
choices
=
[
(
1
,
'Poor quality'
),
(
2
,
'Medium quality'
),
(
3
,
'Good quality'
),
]
)
class
TestChoiceFieldWithListChoices
(
ValidAndInvalidValues
):
"""
Valid and invalid values for a `Choice` field that uses a flat list for the
choices, rather than a list of pairs of (`value`, `description`).
"""
valid_mappings
=
{
'poor'
:
'poor'
,
'medium'
:
'medium'
,
'good'
:
'good'
,
}
invalid_mappings
=
{
'awful'
:
[
'`awful` is not a valid choice.'
]
}
field
=
fields
.
ChoiceField
(
choices
=
(
'poor'
,
'medium'
,
'good'
))
class
TestMultipleChoiceField
(
ValidAndInvalidValues
):
"""
Valid and invalid values for `MultipleChoiceField`.
"""
valid_mappings
=
{
():
set
(),
(
'aircon'
,):
set
([
'aircon'
]),
(
'aircon'
,
'manual'
):
set
([
'aircon'
,
'manual'
]),
}
invalid_mappings
=
{
'abc'
:
[
'Expected a list of items but got type `str`'
],
(
'aircon'
,
'incorrect'
):
[
'`incorrect` is not a valid choice.'
]
}
field
=
fields
.
MultipleChoiceField
(
choices
=
[
(
'aircon'
,
'AirCon'
),
(
'manual'
,
'Manual drive'
),
(
'diesel'
,
'Diesel'
),
]
)
# """
# """
# General serializer field tests.
# General serializer field tests.
# """
# """
...
...
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