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
0b84c5a0
Commit
0b84c5a0
authored
May 18, 2013
by
Ryan Kaskel
Browse files
Options
Browse Files
Download
Plain Diff
Merge latest changes from master.
parents
770ed3de
a0e3c44c
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
447 additions
and
50 deletions
+447
-50
.travis.yml
+9
-8
docs/topics/credits.md
+6
-0
rest_framework/fields.py
+10
-2
rest_framework/relations.py
+12
-4
rest_framework/renderers.py
+33
-9
rest_framework/runtests/settings.py
+2
-0
rest_framework/serializers.py
+2
-3
rest_framework/tests/fields.py
+42
-4
rest_framework/tests/generics.py
+33
-1
rest_framework/tests/relations.py
+54
-1
rest_framework/tests/relations_hyperlink.py
+71
-0
rest_framework/tests/relations_pk.py
+57
-1
rest_framework/tests/serializer.py
+116
-17
No files found.
.travis.yml
View file @
0b84c5a0
...
...
@@ -7,9 +7,9 @@ python:
-
"
3.3"
env
:
-
DJANGO="django==1.5 --use-mirrors"
-
DJANGO="django==1.4.
3
--use-mirrors"
-
DJANGO="django==1.3.
5
--use-mirrors"
-
DJANGO="django==1.5
.1
--use-mirrors"
-
DJANGO="django==1.4.
5
--use-mirrors"
-
DJANGO="django==1.3.
7
--use-mirrors"
install
:
-
pip install $DJANGO
...
...
@@ -18,7 +18,7 @@ install:
-
"
if
[[
${TRAVIS_PYTHON_VERSION::1}
!=
'3'
]];
then
pip
install
django-oauth-plus==2.0
--use-mirrors;
fi"
-
"
if
[[
${TRAVIS_PYTHON_VERSION::1}
!=
'3'
]];
then
pip
install
django-oauth2-provider==0.2.3
--use-mirrors;
fi"
-
"
if
[[
${DJANGO::11}
==
'django==1.3'
]];
then
pip
install
django-filter==0.5.4
--use-mirrors;
fi"
-
"
if
[[
${DJANGO::11}
!=
'django==1.3'
]];
then
pip
install
django-filter==0.6
a1
--use-mirrors;
fi"
-
"
if
[[
${DJANGO::11}
!=
'django==1.3'
]];
then
pip
install
django-filter==0.6
--use-mirrors;
fi"
-
export PYTHONPATH=.
script
:
...
...
@@ -27,10 +27,11 @@ script:
matrix
:
exclude
:
-
python
:
"
3.2"
env
:
DJANGO="django==1.4.
3
--use-mirrors"
env
:
DJANGO="django==1.4.
5
--use-mirrors"
-
python
:
"
3.2"
env
:
DJANGO="django==1.3.
5
--use-mirrors"
env
:
DJANGO="django==1.3.
7
--use-mirrors"
-
python
:
"
3.3"
env
:
DJANGO="django==1.4.
3
--use-mirrors"
env
:
DJANGO="django==1.4.
5
--use-mirrors"
-
python
:
"
3.3"
env
:
DJANGO="django==1.3.5 --use-mirrors"
env
:
DJANGO="django==1.3.7 --use-mirrors"
docs/topics/credits.md
View file @
0b84c5a0
...
...
@@ -124,6 +124,9 @@ The following people have helped make REST framework great.
*
Marlon Bailey -
[
avinash240
]
*
James Summerfield -
[
jsummerfield
]
*
Andy Freeland -
[
rouge8
]
*
Craig de Stigter -
[
craigds
]
*
Pablo Recio -
[
pyriku
]
*
Brian Zambrano -
[
brianz
]
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
[
jsummerfield
]:
https://github.com/jsummerfield
[
rouge8
]:
https://github.com/rouge8
[
craigds
]:
https://github.com/craigds
[
pyriku
]:
https://github.com/pyriku
[
brianz
]:
https://github.com/brianz
rest_framework/fields.py
View file @
0b84c5a0
...
...
@@ -15,10 +15,12 @@ import warnings
from
django.core
import
validators
from
django.core.exceptions
import
ValidationError
from
django.conf
import
settings
from
django.db.models.fields
import
BLANK_CHOICE_DASH
from
django
import
forms
from
django.forms
import
widgets
from
django.utils.encoding
import
is_protected_type
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.datastructures
import
SortedDict
from
rest_framework
import
ISO_8601
from
rest_framework.compat
import
timezone
,
parse_date
,
parse_datetime
,
parse_time
...
...
@@ -50,7 +52,7 @@ def get_component(obj, attr_name):
return that attribute on the object.
"""
if
isinstance
(
obj
,
dict
):
val
=
obj
[
attr_name
]
val
=
obj
.
get
(
attr_name
)
else
:
val
=
getattr
(
obj
,
attr_name
)
...
...
@@ -170,7 +172,11 @@ class Field(object):
elif
hasattr
(
value
,
'__iter__'
)
and
not
isinstance
(
value
,
(
dict
,
six
.
string_types
)):
return
[
self
.
to_native
(
item
)
for
item
in
value
]
elif
isinstance
(
value
,
dict
):
return
dict
(
map
(
self
.
to_native
,
(
k
,
v
))
for
k
,
v
in
value
.
items
())
# Make sure we preserve field ordering, if it exists
ret
=
SortedDict
()
for
key
,
val
in
value
.
items
():
ret
[
key
]
=
self
.
to_native
(
val
)
return
ret
return
smart_text
(
value
)
def
attributes
(
self
):
...
...
@@ -402,6 +408,8 @@ class ChoiceField(WritableField):
def
__init__
(
self
,
choices
=
(),
*
args
,
**
kwargs
):
super
(
ChoiceField
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
choices
=
choices
if
not
self
.
required
:
self
.
choices
=
BLANK_CHOICE_DASH
+
self
.
choices
def
_get_choices
(
self
):
return
self
.
_choices
...
...
rest_framework/relations.py
View file @
0b84c5a0
...
...
@@ -221,12 +221,20 @@ class PrimaryKeyRelatedField(RelatedField):
def
field_to_native
(
self
,
obj
,
field_name
):
if
self
.
many
:
# To-many relationship
try
:
queryset
=
None
if
not
self
.
source
:
# Prefer obj.serializable_value for performance reasons
queryset
=
obj
.
serializable_value
(
self
.
source
or
field_name
)
except
AttributeError
:
try
:
queryset
=
obj
.
serializable_value
(
field_name
)
except
AttributeError
:
pass
if
queryset
is
None
:
# RelatedManager (reverse relationship)
queryset
=
getattr
(
obj
,
self
.
source
or
field_name
)
source
=
self
.
source
or
field_name
queryset
=
obj
for
component
in
source
.
split
(
'.'
):
queryset
=
get_component
(
queryset
,
component
)
# Forward relationship
return
[
self
.
to_native
(
item
.
pk
)
for
item
in
queryset
.
all
()]
...
...
rest_framework/renderers.py
View file @
0b84c5a0
...
...
@@ -336,7 +336,7 @@ class BrowsableAPIRenderer(BaseRenderer):
return
# Cannot use form overloading
try
:
view
.
check_permissions
(
clone_request
(
request
,
method
)
)
view
.
check_permissions
(
request
)
except
exceptions
.
APIException
:
return
False
# Doesn't have permissions
return
True
...
...
@@ -372,6 +372,30 @@ class BrowsableAPIRenderer(BaseRenderer):
return
fields
def
_get_form
(
self
,
view
,
method
,
request
):
# We need to impersonate a request with the correct method,
# so that eg. any dynamic get_serializer_class methods return the
# correct form for each method.
restore
=
view
.
request
request
=
clone_request
(
request
,
method
)
view
.
request
=
request
try
:
return
self
.
get_form
(
view
,
method
,
request
)
finally
:
view
.
request
=
restore
def
_get_raw_data_form
(
self
,
view
,
method
,
request
,
media_types
):
# We need to impersonate a request with the correct method,
# so that eg. any dynamic get_serializer_class methods return the
# correct form for each method.
restore
=
view
.
request
request
=
clone_request
(
request
,
method
)
view
.
request
=
request
try
:
return
self
.
get_raw_data_form
(
view
,
method
,
request
,
media_types
)
finally
:
view
.
request
=
restore
def
get_form
(
self
,
view
,
method
,
request
):
"""
Get a form, possibly bound to either the input or output data.
...
...
@@ -465,15 +489,15 @@ class BrowsableAPIRenderer(BaseRenderer):
renderer
=
self
.
get_default_renderer
(
view
)
content
=
self
.
get_content
(
renderer
,
data
,
accepted_media_type
,
renderer_context
)
put_form
=
self
.
get_form
(
view
,
'PUT'
,
request
)
post_form
=
self
.
get_form
(
view
,
'POST'
,
request
)
patch_form
=
self
.
get_form
(
view
,
'PATCH'
,
request
)
delete_form
=
self
.
get_form
(
view
,
'DELETE'
,
request
)
options_form
=
self
.
get_form
(
view
,
'OPTIONS'
,
request
)
put_form
=
self
.
_
get_form
(
view
,
'PUT'
,
request
)
post_form
=
self
.
_
get_form
(
view
,
'POST'
,
request
)
patch_form
=
self
.
_
get_form
(
view
,
'PATCH'
,
request
)
delete_form
=
self
.
_
get_form
(
view
,
'DELETE'
,
request
)
options_form
=
self
.
_
get_form
(
view
,
'OPTIONS'
,
request
)
raw_data_put_form
=
self
.
get_raw_data_form
(
view
,
'PUT'
,
request
,
media_types
)
raw_data_post_form
=
self
.
get_raw_data_form
(
view
,
'POST'
,
request
,
media_types
)
raw_data_patch_form
=
self
.
get_raw_data_form
(
view
,
'PATCH'
,
request
,
media_types
)
raw_data_put_form
=
self
.
_
get_raw_data_form
(
view
,
'PUT'
,
request
,
media_types
)
raw_data_post_form
=
self
.
_
get_raw_data_form
(
view
,
'POST'
,
request
,
media_types
)
raw_data_patch_form
=
self
.
_
get_raw_data_form
(
view
,
'PATCH'
,
request
,
media_types
)
raw_data_put_or_patch_form
=
raw_data_put_form
or
raw_data_patch_form
name
=
self
.
get_name
(
view
)
...
...
rest_framework/runtests/settings.py
View file @
0b84c5a0
...
...
@@ -4,6 +4,8 @@ DEBUG = True
TEMPLATE_DEBUG
=
DEBUG
DEBUG_PROPAGATE_EXCEPTIONS
=
True
ALLOWED_HOSTS
=
[
'*'
]
ADMINS
=
(
# ('Your Name', 'your_email@domain.com'),
)
...
...
rest_framework/serializers.py
View file @
0b84c5a0
...
...
@@ -722,15 +722,14 @@ class ModelSerializer(Serializer):
Creates a default instance of a basic non-relational field.
"""
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
if
isinstance
(
model_field
,
models
.
AutoField
)
or
not
model_field
.
editable
:
kwargs
[
'read_only'
]
=
True
if
has_default
:
if
model_field
.
has_default
()
:
kwargs
[
'default'
]
=
model_field
.
get_default
()
if
issubclass
(
model_field
.
__class__
,
models
.
TextField
):
...
...
rest_framework/tests/fields.py
View file @
0b84c5a0
...
...
@@ -2,13 +2,12 @@
General serializer field tests.
"""
from
__future__
import
unicode_literals
from
django.utils.datastructures
import
SortedDict
import
datetime
from
decimal
import
Decimal
from
django.db
import
models
from
django.test
import
TestCase
from
django.core
import
validators
from
rest_framework
import
serializers
from
rest_framework.serializers
import
Serializer
...
...
@@ -63,6 +62,20 @@ class BasicFieldTests(TestCase):
serializer
=
CharPrimaryKeyModelSerializer
()
self
.
assertEqual
(
serializer
.
fields
[
'id'
]
.
read_only
,
False
)
def
test_dict_field_ordering
(
self
):
"""
Field should preserve dictionary ordering, if it exists.
See: https://github.com/tomchristie/django-rest-framework/issues/832
"""
ret
=
SortedDict
()
ret
[
'c'
]
=
1
ret
[
'b'
]
=
1
ret
[
'a'
]
=
1
ret
[
'z'
]
=
1
field
=
serializers
.
Field
()
keys
=
list
(
field
.
to_native
(
ret
)
.
keys
())
self
.
assertEqual
(
keys
,
[
'c'
,
'b'
,
'a'
,
'z'
])
class
DateFieldTest
(
TestCase
):
"""
...
...
@@ -645,4 +658,30 @@ class DecimalFieldTest(TestCase):
s
=
DecimalSerializer
(
data
=
{
'decimal_field'
:
'12345.6'
})
self
.
assertFalse
(
s
.
is_valid
())
self
.
assertEqual
(
s
.
errors
,
{
'decimal_field'
:
[
'Ensure that there are no more than 4 digits in total.'
]})
\ No newline at end of file
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
)
rest_framework/tests/generics.py
View file @
0b84c5a0
...
...
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
from
django.db
import
models
from
django.shortcuts
import
get_object_or_404
from
django.test
import
TestCase
from
rest_framework
import
generics
,
serializers
,
status
from
rest_framework
import
generics
,
renderers
,
serializers
,
status
from
rest_framework.tests.utils
import
RequestFactory
from
rest_framework.tests.models
import
BasicModel
,
Comment
,
SlugBasedModel
from
rest_framework.compat
import
six
...
...
@@ -476,3 +476,35 @@ class TestFilterBackendAppliedToViews(TestCase):
response
=
instance_view
(
request
,
pk
=
1
)
.
render
()
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
.
data
,
{
'id'
:
1
,
'text'
:
'foo'
})
class
TwoFieldModel
(
models
.
Model
):
field_a
=
models
.
CharField
(
max_length
=
100
)
field_b
=
models
.
CharField
(
max_length
=
100
)
class
DynamicSerializerView
(
generics
.
ListCreateAPIView
):
model
=
TwoFieldModel
renderer_classes
=
(
renderers
.
BrowsableAPIRenderer
,
renderers
.
JSONRenderer
)
def
get_serializer_class
(
self
):
if
self
.
request
.
method
==
'POST'
:
class
DynamicSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
TwoFieldModel
fields
=
(
'field_b'
,)
return
DynamicSerializer
return
super
(
DynamicSerializerView
,
self
)
.
get_serializer_class
()
class
TestFilterBackendAppliedToViews
(
TestCase
):
def
test_dynamic_serializer_form_in_browsable_api
(
self
):
"""
GET requests to ListCreateAPIView should return filtered list.
"""
view
=
DynamicSerializerView
.
as_view
()
request
=
factory
.
get
(
'/'
)
response
=
view
(
request
)
.
render
()
self
.
assertContains
(
response
,
'field_b'
)
self
.
assertNotContains
(
response
,
'field_a'
)
rest_framework/tests/relations.py
View file @
0b84c5a0
...
...
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
from
django.db
import
models
from
django.test
import
TestCase
from
rest_framework
import
serializers
from
rest_framework.tests.models
import
BlogPost
class
NullModel
(
models
.
Model
):
...
...
@@ -33,7 +34,7 @@ class FieldTests(TestCase):
self
.
assertRaises
(
serializers
.
ValidationError
,
field
.
from_native
,
[])
class
TestManyRelateMixin
(
TestCase
):
class
TestManyRelate
d
Mixin
(
TestCase
):
def
test_missing_many_to_many_related_field
(
self
):
'''
Regression test for #632
...
...
@@ -45,3 +46,55 @@ class TestManyRelateMixin(TestCase):
into
=
{}
field
.
field_from_native
({},
None
,
'field_name'
,
into
)
self
.
assertEqual
(
into
[
'field_name'
],
[])
# Regression tests for #694 (`source` attribute on related fields)
class
RelatedFieldSourceTests
(
TestCase
):
def
test_related_manager_source
(
self
):
"""
Relational fields should be able to use manager-returning methods as their source.
"""
BlogPost
.
objects
.
create
(
title
=
'blah'
)
field
=
serializers
.
RelatedField
(
many
=
True
,
source
=
'get_blogposts_manager'
)
class
ClassWithManagerMethod
(
object
):
def
get_blogposts_manager
(
self
):
return
BlogPost
.
objects
obj
=
ClassWithManagerMethod
()
value
=
field
.
field_to_native
(
obj
,
'field_name'
)
self
.
assertEqual
(
value
,
[
'BlogPost object'
])
def
test_related_queryset_source
(
self
):
"""
Relational fields should be able to use queryset-returning methods as their source.
"""
BlogPost
.
objects
.
create
(
title
=
'blah'
)
field
=
serializers
.
RelatedField
(
many
=
True
,
source
=
'get_blogposts_queryset'
)
class
ClassWithQuerysetMethod
(
object
):
def
get_blogposts_queryset
(
self
):
return
BlogPost
.
objects
.
all
()
obj
=
ClassWithQuerysetMethod
()
value
=
field
.
field_to_native
(
obj
,
'field_name'
)
self
.
assertEqual
(
value
,
[
'BlogPost object'
])
def
test_dotted_source
(
self
):
"""
Source argument should support dotted.source notation.
"""
BlogPost
.
objects
.
create
(
title
=
'blah'
)
field
=
serializers
.
RelatedField
(
many
=
True
,
source
=
'a.b.c'
)
class
ClassWithQuerysetMethod
(
object
):
a
=
{
'b'
:
{
'c'
:
BlogPost
.
objects
.
all
()
}
}
obj
=
ClassWithQuerysetMethod
()
value
=
field
.
field_to_native
(
obj
,
'field_name'
)
self
.
assertEqual
(
value
,
[
'BlogPost object'
])
rest_framework/tests/relations_hyperlink.py
View file @
0b84c5a0
...
...
@@ -4,6 +4,7 @@ from django.test.client import RequestFactory
from
rest_framework
import
serializers
from
rest_framework.compat
import
patterns
,
url
from
rest_framework.tests.models
import
(
BlogPost
,
ManyToManyTarget
,
ManyToManySource
,
ForeignKeyTarget
,
ForeignKeySource
,
NullableForeignKeySource
,
OneToOneTarget
,
NullableOneToOneSource
)
...
...
@@ -16,6 +17,7 @@ def dummy_view(request, pk):
pass
urlpatterns
=
patterns
(
''
,
url
(
r'^dummyurl/(?P<pk>[0-9]+)/$'
,
dummy_view
,
name
=
'dummy-url'
),
url
(
r'^manytomanysource/(?P<pk>[0-9]+)/$'
,
dummy_view
,
name
=
'manytomanysource-detail'
),
url
(
r'^manytomanytarget/(?P<pk>[0-9]+)/$'
,
dummy_view
,
name
=
'manytomanytarget-detail'
),
url
(
r'^foreignkeysource/(?P<pk>[0-9]+)/$'
,
dummy_view
,
name
=
'foreignkeysource-detail'
),
...
...
@@ -451,3 +453,72 @@ class HyperlinkedNullableOneToOneTests(TestCase):
{
'url'
:
'http://testserver/onetoonetarget/2/'
,
'name'
:
'target-2'
,
'nullable_source'
:
None
},
]
self
.
assertEqual
(
serializer
.
data
,
expected
)
# Regression tests for #694 (`source` attribute on related fields)
class
HyperlinkedRelatedFieldSourceTests
(
TestCase
):
urls
=
'rest_framework.tests.relations_hyperlink'
def
test_related_manager_source
(
self
):
"""
Relational fields should be able to use manager-returning methods as their source.
"""
BlogPost
.
objects
.
create
(
title
=
'blah'
)
field
=
serializers
.
HyperlinkedRelatedField
(
many
=
True
,
source
=
'get_blogposts_manager'
,
view_name
=
'dummy-url'
,
)
field
.
context
=
{
'request'
:
request
}
class
ClassWithManagerMethod
(
object
):
def
get_blogposts_manager
(
self
):
return
BlogPost
.
objects
obj
=
ClassWithManagerMethod
()
value
=
field
.
field_to_native
(
obj
,
'field_name'
)
self
.
assertEqual
(
value
,
[
'http://testserver/dummyurl/1/'
])
def
test_related_queryset_source
(
self
):
"""
Relational fields should be able to use queryset-returning methods as their source.
"""
BlogPost
.
objects
.
create
(
title
=
'blah'
)
field
=
serializers
.
HyperlinkedRelatedField
(
many
=
True
,
source
=
'get_blogposts_queryset'
,
view_name
=
'dummy-url'
,
)
field
.
context
=
{
'request'
:
request
}
class
ClassWithQuerysetMethod
(
object
):
def
get_blogposts_queryset
(
self
):
return
BlogPost
.
objects
.
all
()
obj
=
ClassWithQuerysetMethod
()
value
=
field
.
field_to_native
(
obj
,
'field_name'
)
self
.
assertEqual
(
value
,
[
'http://testserver/dummyurl/1/'
])
def
test_dotted_source
(
self
):
"""
Source argument should support dotted.source notation.
"""
BlogPost
.
objects
.
create
(
title
=
'blah'
)
field
=
serializers
.
HyperlinkedRelatedField
(
many
=
True
,
source
=
'a.b.c'
,
view_name
=
'dummy-url'
,
)
field
.
context
=
{
'request'
:
request
}
class
ClassWithQuerysetMethod
(
object
):
a
=
{
'b'
:
{
'c'
:
BlogPost
.
objects
.
all
()
}
}
obj
=
ClassWithQuerysetMethod
()
value
=
field
.
field_to_native
(
obj
,
'field_name'
)
self
.
assertEqual
(
value
,
[
'http://testserver/dummyurl/1/'
])
rest_framework/tests/relations_pk.py
View file @
0b84c5a0
...
...
@@ -2,7 +2,10 @@ from __future__ import unicode_literals
from
django.db
import
models
from
django.test
import
TestCase
from
rest_framework
import
serializers
from
rest_framework.tests.models
import
ManyToManyTarget
,
ManyToManySource
,
ForeignKeyTarget
,
ForeignKeySource
,
NullableForeignKeySource
,
OneToOneTarget
,
NullableOneToOneSource
from
rest_framework.tests.models
import
(
BlogPost
,
ManyToManyTarget
,
ManyToManySource
,
ForeignKeyTarget
,
ForeignKeySource
,
NullableForeignKeySource
,
OneToOneTarget
,
NullableOneToOneSource
,
)
from
rest_framework.compat
import
six
...
...
@@ -484,3 +487,56 @@ class PKManyToManyThroughTests(TestCase):
obj
=
serializer
.
save
()
self
.
assertEqual
(
obj
.
name
,
'target-2'
)
self
.
assertEqual
(
obj
.
sources
.
count
(),
0
)
# Regression tests for #694 (`source` attribute on related fields)
class
PrimaryKeyRelatedFieldSourceTests
(
TestCase
):
def
test_related_manager_source
(
self
):
"""
Relational fields should be able to use manager-returning methods as their source.
"""
BlogPost
.
objects
.
create
(
title
=
'blah'
)
field
=
serializers
.
PrimaryKeyRelatedField
(
many
=
True
,
source
=
'get_blogposts_manager'
)
class
ClassWithManagerMethod
(
object
):
def
get_blogposts_manager
(
self
):
return
BlogPost
.
objects
obj
=
ClassWithManagerMethod
()
value
=
field
.
field_to_native
(
obj
,
'field_name'
)
self
.
assertEqual
(
value
,
[
1
])
def
test_related_queryset_source
(
self
):
"""
Relational fields should be able to use queryset-returning methods as their source.
"""
BlogPost
.
objects
.
create
(
title
=
'blah'
)
field
=
serializers
.
PrimaryKeyRelatedField
(
many
=
True
,
source
=
'get_blogposts_queryset'
)
class
ClassWithQuerysetMethod
(
object
):
def
get_blogposts_queryset
(
self
):
return
BlogPost
.
objects
.
all
()
obj
=
ClassWithQuerysetMethod
()
value
=
field
.
field_to_native
(
obj
,
'field_name'
)
self
.
assertEqual
(
value
,
[
1
])
def
test_dotted_source
(
self
):
"""
Source argument should support dotted.source notation.
"""
BlogPost
.
objects
.
create
(
title
=
'blah'
)
field
=
serializers
.
PrimaryKeyRelatedField
(
many
=
True
,
source
=
'a.b.c'
)
class
ClassWithQuerysetMethod
(
object
):
a
=
{
'b'
:
{
'c'
:
BlogPost
.
objects
.
all
()
}
}
obj
=
ClassWithQuerysetMethod
()
value
=
field
.
field_to_native
(
obj
,
'field_name'
)
self
.
assertEqual
(
value
,
[
1
])
rest_framework/tests/serializer.py
View file @
0b84c5a0
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.test
import
TestCase
from
rest_framework
import
serializers
...
...
@@ -43,6 +45,17 @@ class CommentSerializer(serializers.Serializer):
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
):
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
.
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
):
serializer
=
CommentSerializer
(
self
.
comment
,
data
=
self
.
data
)
expected
=
self
.
comment
...
...
@@ -871,23 +920,6 @@ class RelatedTraversalTest(TestCase):
self
.
assertEqual
(
serializer
.
data
,
expected
)
def
test_queryset_nested_traversal
(
self
):
"""
Relational fields should be able to use methods as their source.
"""
BlogPost
.
objects
.
create
(
title
=
'blah'
)
class
QuerysetMethodSerializer
(
serializers
.
Serializer
):
blogposts
=
serializers
.
RelatedField
(
many
=
True
,
source
=
'get_all_blogposts'
)
class
ClassWithQuerysetMethod
(
object
):
def
get_all_blogposts
(
self
):
return
BlogPost
.
objects
obj
=
ClassWithQuerysetMethod
()
serializer
=
QuerysetMethodSerializer
(
obj
)
self
.
assertEqual
(
serializer
.
data
,
{
'blogposts'
:
[
'BlogPost object'
]})
class
SerializerMethodFieldTests
(
TestCase
):
def
setUp
(
self
):
...
...
@@ -1018,6 +1050,73 @@ class SerializerPickleTests(TestCase):
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
):
def
test_implicit_nesting
(
self
):
...
...
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