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
fb3b5780
Commit
fb3b5780
authored
Mar 15, 2013
by
Tom Christie
Browse files
Options
Browse Files
Download
Plain Diff
one 2 one nested relationships
parents
22a389d0
3006e382
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
441 additions
and
105 deletions
+441
-105
rest_framework/serializers.py
+113
-33
rest_framework/tests/relations_nested.py
+82
-72
rest_framework/tests/serializer_nested.py
+246
-0
No files found.
rest_framework/serializers.py
View file @
fb3b5780
...
@@ -20,6 +20,25 @@ from rest_framework.relations import *
...
@@ -20,6 +20,25 @@ from rest_framework.relations import *
from
rest_framework.fields
import
*
from
rest_framework.fields
import
*
class
NestedValidationError
(
ValidationError
):
"""
The default ValidationError behavior is to stringify each item in the list
if the messages are a list of error messages.
In the case of nested serializers, where the parent has many children,
then the child's `serializer.errors` will be a list of dicts. In the case
of a single child, the `serializer.errors` will be a dict.
We need to override the default behavior to get properly nested error dicts.
"""
def
__init__
(
self
,
message
):
if
isinstance
(
message
,
dict
):
self
.
messages
=
[
message
]
else
:
self
.
messages
=
message
class
DictWithMetadata
(
dict
):
class
DictWithMetadata
(
dict
):
"""
"""
A dict-like object, that can have additional properties attached.
A dict-like object, that can have additional properties attached.
...
@@ -98,7 +117,7 @@ class SerializerOptions(object):
...
@@ -98,7 +117,7 @@ class SerializerOptions(object):
self
.
exclude
=
getattr
(
meta
,
'exclude'
,
())
self
.
exclude
=
getattr
(
meta
,
'exclude'
,
())
class
BaseSerializer
(
Field
):
class
BaseSerializer
(
Writable
Field
):
"""
"""
This is the Serializer implementation.
This is the Serializer implementation.
We need to implement it as `BaseSerializer` due to metaclass magicks.
We need to implement it as `BaseSerializer` due to metaclass magicks.
...
@@ -128,6 +147,7 @@ class BaseSerializer(Field):
...
@@ -128,6 +147,7 @@ class BaseSerializer(Field):
self
.
_data
=
None
self
.
_data
=
None
self
.
_files
=
None
self
.
_files
=
None
self
.
_errors
=
None
self
.
_errors
=
None
self
.
_delete
=
False
#####
#####
# Methods to determine which fields to use when (de)serializing objects.
# Methods to determine which fields to use when (de)serializing objects.
...
@@ -296,40 +316,77 @@ class BaseSerializer(Field):
...
@@ -296,40 +316,77 @@ class BaseSerializer(Field):
def
field_to_native
(
self
,
obj
,
field_name
):
def
field_to_native
(
self
,
obj
,
field_name
):
"""
"""
Override default so that
we can apply ModelSerializer as a neste
d
Override default so that
the serializer can be used as a nested fiel
d
field to
relationships.
across
relationships.
"""
"""
if
self
.
source
==
'*'
:
if
self
.
source
==
'*'
:
return
self
.
to_native
(
obj
)
return
self
.
to_native
(
obj
)
try
:
try
:
if
self
.
source
:
source
=
self
.
source
or
field_name
for
component
in
self
.
source
.
split
(
'.'
):
value
=
obj
obj
=
getattr
(
obj
,
component
)
if
is_simple_callable
(
obj
):
for
component
in
source
.
split
(
'.'
):
obj
=
obj
()
value
=
get_component
(
value
,
component
)
else
:
if
value
is
None
:
obj
=
getattr
(
obj
,
field_name
)
break
if
is_simple_callable
(
obj
):
obj
=
obj
()
except
ObjectDoesNotExist
:
except
ObjectDoesNotExist
:
return
None
return
None
# If the object has an "all" method, assume it's a relationship
if
is_simple_callable
(
getattr
(
value
,
'all'
,
None
)):
if
is_simple_callable
(
getattr
(
obj
,
'all'
,
None
)):
return
[
self
.
to_native
(
item
)
for
item
in
value
.
all
()]
return
[
self
.
to_native
(
item
)
for
item
in
obj
.
all
()]
if
obj
is
None
:
if
value
is
None
:
return
None
return
None
if
self
.
many
is
not
None
:
if
self
.
many
is
not
None
:
many
=
self
.
many
many
=
self
.
many
else
:
else
:
many
=
hasattr
(
obj
,
'__iter__'
)
and
not
isinstance
(
obj
,
(
Page
,
dict
,
six
.
text_type
))
many
=
hasattr
(
value
,
'__iter__'
)
and
not
isinstance
(
value
,
(
Page
,
dict
,
six
.
text_type
))
if
many
:
if
many
:
return
[
self
.
to_native
(
item
)
for
item
in
obj
]
return
[
self
.
to_native
(
item
)
for
item
in
value
]
return
self
.
to_native
(
obj
)
return
self
.
to_native
(
value
)
def
field_from_native
(
self
,
data
,
files
,
field_name
,
into
):
"""
Override default so that the serializer can be used as a writable
nested field across relationships.
"""
if
self
.
read_only
:
return
try
:
value
=
data
[
field_name
]
except
KeyError
:
if
self
.
required
:
raise
ValidationError
(
self
.
error_messages
[
'required'
])
return
# Set the serializer object if it exists
obj
=
getattr
(
self
.
parent
.
object
,
field_name
)
if
self
.
parent
.
object
else
None
if
value
in
(
None
,
''
):
into
[(
self
.
source
or
field_name
)]
=
None
else
:
kwargs
=
{
'instance'
:
obj
,
'data'
:
value
,
'context'
:
self
.
context
,
'partial'
:
self
.
partial
,
'many'
:
self
.
many
}
serializer
=
self
.
__class__
(
**
kwargs
)
if
serializer
.
is_valid
():
if
isinstance
(
serializer
,
ModelSerializer
):
into
[
self
.
source
or
field_name
]
=
serializer
else
:
into
[
self
.
source
or
field_name
]
=
serializer
.
object
# into[self.source or field_name] = serializer.object
else
:
# Propagate errors up to our parent
raise
NestedValidationError
(
serializer
.
errors
)
@property
@property
def
errors
(
self
):
def
errors
(
self
):
...
@@ -584,25 +641,25 @@ class ModelSerializer(Serializer):
...
@@ -584,25 +641,25 @@ class ModelSerializer(Serializer):
"""
"""
Restore the model instance.
Restore the model instance.
"""
"""
self
.
m2m_data
=
{}
m2m_data
=
{}
self
.
related_data
=
{}
related_data
=
{}
# Reverse fk relations
# Reverse fk relations
for
(
obj
,
model
)
in
self
.
opts
.
model
.
_meta
.
get_all_related_objects_with_model
():
for
(
obj
,
model
)
in
self
.
opts
.
model
.
_meta
.
get_all_related_objects_with_model
():
field_name
=
obj
.
field
.
related_query_name
()
field_name
=
obj
.
field
.
related_query_name
()
if
field_name
in
attrs
:
if
field_name
in
attrs
:
self
.
related_data
[
field_name
]
=
attrs
.
pop
(
field_name
)
related_data
[
field_name
]
=
attrs
.
pop
(
field_name
)
# Reverse m2m relations
# Reverse m2m relations
for
(
obj
,
model
)
in
self
.
opts
.
model
.
_meta
.
get_all_related_m2m_objects_with_model
():
for
(
obj
,
model
)
in
self
.
opts
.
model
.
_meta
.
get_all_related_m2m_objects_with_model
():
field_name
=
obj
.
field
.
related_query_name
()
field_name
=
obj
.
field
.
related_query_name
()
if
field_name
in
attrs
:
if
field_name
in
attrs
:
self
.
m2m_data
[
field_name
]
=
attrs
.
pop
(
field_name
)
m2m_data
[
field_name
]
=
attrs
.
pop
(
field_name
)
# Forward m2m relations
# Forward m2m relations
for
field
in
self
.
opts
.
model
.
_meta
.
many_to_many
:
for
field
in
self
.
opts
.
model
.
_meta
.
many_to_many
:
if
field
.
name
in
attrs
:
if
field
.
name
in
attrs
:
self
.
m2m_data
[
field
.
name
]
=
attrs
.
pop
(
field
.
name
)
m2m_data
[
field
.
name
]
=
attrs
.
pop
(
field
.
name
)
if
instance
is
not
None
:
if
instance
is
not
None
:
for
key
,
val
in
attrs
.
items
():
for
key
,
val
in
attrs
.
items
():
...
@@ -611,6 +668,9 @@ class ModelSerializer(Serializer):
...
@@ -611,6 +668,9 @@ class ModelSerializer(Serializer):
else
:
else
:
instance
=
self
.
opts
.
model
(
**
attrs
)
instance
=
self
.
opts
.
model
(
**
attrs
)
instance
.
_related_data
=
related_data
instance
.
_m2m_data
=
m2m_data
return
instance
return
instance
def
from_native
(
self
,
data
,
files
):
def
from_native
(
self
,
data
,
files
):
...
@@ -621,21 +681,41 @@ class ModelSerializer(Serializer):
...
@@ -621,21 +681,41 @@ class ModelSerializer(Serializer):
if
instance
:
if
instance
:
return
self
.
full_clean
(
instance
)
return
self
.
full_clean
(
instance
)
def
save_object
(
self
,
obj
,
**
kwargs
):
# def save_object(self, obj, **kwargs):
# """
# Save the deserialized object and return it.
# """
# obj.save(**kwargs)
# =======
def
save_object
(
self
,
obj
,
parent
=
None
,
fk_field
=
None
,
**
kwargs
):
"""
"""
Save the deserialized object and return it.
Save the deserialized object and return it.
"""
"""
obj
.
save
(
**
kwargs
)
if
parent
and
fk_field
:
setattr
(
self
.
object
,
fk_field
,
parent
)
if
getattr
(
self
,
'm2m_data'
,
None
):
obj
.
save
(
**
kwargs
)
for
accessor_name
,
object_list
in
self
.
m2m_data
.
items
():
setattr
(
self
.
object
,
accessor_name
,
object_list
)
self
.
m2m_data
=
{}
if
getattr
(
self
,
'related
_data'
,
None
):
if
getattr
(
obj
,
'_m2m
_data'
,
None
):
for
accessor_name
,
object_list
in
self
.
related
_data
.
items
():
for
accessor_name
,
object_list
in
obj
.
_m2m
_data
.
items
():
setattr
(
self
.
object
,
accessor_name
,
object_list
)
setattr
(
self
.
object
,
accessor_name
,
object_list
)
self
.
related_data
=
{}
obj
.
_m2m_data
=
{}
if
getattr
(
obj
,
'_related_data'
,
None
):
for
accessor_name
,
related
in
obj
.
_related_data
.
items
():
if
related
is
None
:
previous
=
getattr
(
self
.
object
,
accessor_name
,
related
)
previous
.
delete
()
elif
isinstance
(
related
,
ModelSerializer
):
# print related.object
# print related.related_data, related.m2m_data
fk_field
=
obj
.
_meta
.
get_field_by_name
(
accessor_name
)[
0
]
.
field
.
name
related
.
save_object
(
related
.
object
,
parent
=
self
.
object
,
fk_field
=
fk_field
)
# setattr(related, fk_field, obj)
# related.save(**kwargs)
else
:
setattr
(
self
.
object
,
accessor_name
,
related
)
obj
.
_related_data
=
{}
class
HyperlinkedModelSerializerOptions
(
ModelSerializerOptions
):
class
HyperlinkedModelSerializerOptions
(
ModelSerializerOptions
):
...
...
rest_framework/tests/relations_nested.py
View file @
fb3b5780
from
__future__
import
unicode_literals
from
__future__
import
unicode_literals
from
django.db
import
models
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
ForeignKeyTarget
,
ForeignKeySource
,
NullableForeignKeySource
,
OneToOneTarget
,
NullableOneToOneSource
class
ForeignKeySourceSerializer
(
serializers
.
ModelSerializer
):
class
OneToOneTarget
(
models
.
Model
):
class
Meta
:
name
=
models
.
CharField
(
max_length
=
100
)
depth
=
1
model
=
ForeignKeySource
class
FlatForeignKeySourceSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
ForeignKeySource
class
OneToOneTargetSource
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
100
)
target
=
models
.
OneToOneField
(
OneToOneTarget
,
null
=
True
,
blank
=
True
,
related_name
=
'target_source'
)
class
ForeignKeyTargetSerializer
(
serializers
.
ModelSerializer
):
sources
=
FlatForeignKeySourceSerializer
(
many
=
True
)
class
Meta
:
class
OneToOneSource
(
models
.
Model
):
model
=
ForeignKeyTarget
name
=
models
.
CharField
(
max_length
=
100
)
target_source
=
models
.
OneToOneField
(
OneToOneTargetSource
,
related_name
=
'source'
)
class
NullableForeignKey
SourceSerializer
(
serializers
.
ModelSerializer
):
class
OneToOne
SourceSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
class
Meta
:
depth
=
1
model
=
OneToOneSource
model
=
NullableForeignKeySource
exclude
=
(
'target_source'
,
)
class
NullableOneToOneSourceSerializer
(
serializers
.
ModelSerializer
):
class
OneToOneTargetSourceSerializer
(
serializers
.
ModelSerializer
):
source
=
OneToOneSourceSerializer
()
class
Meta
:
class
Meta
:
model
=
NullableOneToOneSource
model
=
OneToOneTargetSource
exclude
=
(
'target'
,
)
class
Nullable
OneToOneTargetSerializer
(
serializers
.
ModelSerializer
):
class
OneToOneTargetSerializer
(
serializers
.
ModelSerializer
):
nullable_source
=
NullableOneToOne
SourceSerializer
()
target_source
=
OneToOneTarget
SourceSerializer
()
class
Meta
:
class
Meta
:
model
=
OneToOneTarget
model
=
OneToOneTarget
class
ReverseForeignKey
Tests
(
TestCase
):
class
NestedOneToOne
Tests
(
TestCase
):
def
setUp
(
self
):
def
setUp
(
self
):
target
=
ForeignKeyTarget
(
name
=
'target-1'
)
target
.
save
()
new_target
=
ForeignKeyTarget
(
name
=
'target-2'
)
new_target
.
save
()
for
idx
in
range
(
1
,
4
):
for
idx
in
range
(
1
,
4
):
source
=
ForeignKeySource
(
name
=
'source-
%
d'
%
idx
,
target
=
target
)
target
=
OneToOneTarget
(
name
=
'target-
%
d'
%
idx
)
target
.
save
()
target_source
=
OneToOneTargetSource
(
name
=
'target-source-
%
d'
%
idx
,
target
=
target
)
target_source
.
save
()
source
=
OneToOneSource
(
name
=
'source-
%
d'
%
idx
,
target_source
=
target_source
)
source
.
save
()
source
.
save
()
def
test_
foreign_key
_retrieve
(
self
):
def
test_
one_to_one
_retrieve
(
self
):
queryset
=
ForeignKeySource
.
objects
.
all
()
queryset
=
OneToOneTarget
.
objects
.
all
()
serializer
=
ForeignKeySourceSerializer
(
queryset
,
many
=
True
)
serializer
=
OneToOneTargetSerializer
(
queryset
)
expected
=
[
expected
=
[
{
'id'
:
1
,
'name'
:
'
source-1'
,
'target'
:
{
'id'
:
1
,
'name'
:
'target-1'
}},
{
'id'
:
1
,
'name'
:
'
target-1'
,
'target_source'
:
{
'id'
:
1
,
'name'
:
'target-source-1'
,
'source'
:
{
'id'
:
1
,
'name'
:
'source-1'
}
}},
{
'id'
:
2
,
'name'
:
'
source-2'
,
'target'
:
{
'id'
:
1
,
'name'
:
'target-1'
}},
{
'id'
:
2
,
'name'
:
'
target-2'
,
'target_source'
:
{
'id'
:
2
,
'name'
:
'target-source-2'
,
'source'
:
{
'id'
:
2
,
'name'
:
'source-2'
}
}},
{
'id'
:
3
,
'name'
:
'
source-3'
,
'target'
:
{
'id'
:
1
,
'name'
:
'target-1'
}},
{
'id'
:
3
,
'name'
:
'
target-3'
,
'target_source'
:
{
'id'
:
3
,
'name'
:
'target-source-3'
,
'source'
:
{
'id'
:
3
,
'name'
:
'source-3'
}}}
]
]
self
.
assertEqual
(
serializer
.
data
,
expected
)
self
.
assertEqual
(
serializer
.
data
,
expected
)
def
test_reverse_foreign_key_retrieve
(
self
):
def
test_one_to_one_create
(
self
):
queryset
=
ForeignKeyTarget
.
objects
.
all
()
data
=
{
'id'
:
4
,
'name'
:
'target-4'
,
'target_source'
:
{
'id'
:
4
,
'name'
:
'target-source-4'
,
'source'
:
{
'id'
:
4
,
'name'
:
'source-4'
}}}
serializer
=
ForeignKeyTargetSerializer
(
queryset
,
many
=
True
)
serializer
=
OneToOneTargetSerializer
(
data
=
data
)
self
.
assertTrue
(
serializer
.
is_valid
())
obj
=
serializer
.
save
()
self
.
assertEqual
(
serializer
.
data
,
data
)
self
.
assertEqual
(
obj
.
name
,
'target-4'
)
# Ensure (target 4, target_source 4, source 4) are added, and
# everything else is as expected.
queryset
=
OneToOneTarget
.
objects
.
all
()
serializer
=
OneToOneTargetSerializer
(
queryset
)
expected
=
[
expected
=
[
{
'id'
:
1
,
'name'
:
'target-1'
,
'sources'
:
[
{
'id'
:
1
,
'name'
:
'target-1'
,
'target_source'
:
{
'id'
:
1
,
'name'
:
'target-source-1'
,
'source'
:
{
'id'
:
1
,
'name'
:
'source-1'
}}},
{
'id'
:
1
,
'name'
:
'source-1'
,
'target'
:
1
},
{
'id'
:
2
,
'name'
:
'target-2'
,
'target_source'
:
{
'id'
:
2
,
'name'
:
'target-source-2'
,
'source'
:
{
'id'
:
2
,
'name'
:
'source-2'
}}},
{
'id'
:
2
,
'name'
:
'source-2'
,
'target'
:
1
},
{
'id'
:
3
,
'name'
:
'target-3'
,
'target_source'
:
{
'id'
:
3
,
'name'
:
'target-source-3'
,
'source'
:
{
'id'
:
3
,
'name'
:
'source-3'
}}},
{
'id'
:
3
,
'name'
:
'source-3'
,
'target'
:
1
},
{
'id'
:
4
,
'name'
:
'target-4'
,
'target_source'
:
{
'id'
:
4
,
'name'
:
'target-source-4'
,
'source'
:
{
'id'
:
4
,
'name'
:
'source-4'
}}}
]},
{
'id'
:
2
,
'name'
:
'target-2'
,
'sources'
:
[
]}
]
]
self
.
assertEqual
(
serializer
.
data
,
expected
)
self
.
assertEqual
(
serializer
.
data
,
expected
)
def
test_one_to_one_create_with_invalid_data
(
self
):
class
NestedNullableForeignKeyTests
(
TestCase
):
data
=
{
'id'
:
4
,
'name'
:
'target-4'
,
'target_source'
:
{
'id'
:
4
,
'name'
:
'target-source-4'
,
'source'
:
{
'id'
:
4
}}}
def
setUp
(
self
):
serializer
=
OneToOneTargetSerializer
(
data
=
data
)
target
=
ForeignKeyTarget
(
name
=
'target-1'
)
self
.
assertFalse
(
serializer
.
is_valid
())
target
.
save
()
self
.
assertEqual
(
serializer
.
errors
,
{
'target_source'
:
[{
'source'
:
[{
'name'
:
[
'This field is required.'
]}]}]})
for
idx
in
range
(
1
,
4
):
if
idx
==
3
:
def
test_one_to_one_update
(
self
):
target
=
None
data
=
{
'id'
:
3
,
'name'
:
'target-3-updated'
,
'target_source'
:
{
'id'
:
3
,
'name'
:
'target-source-3-updated'
,
'source'
:
{
'id'
:
3
,
'name'
:
'source-3-updated'
}}}
source
=
NullableForeignKeySource
(
name
=
'source-
%
d'
%
idx
,
target
=
target
)
instance
=
OneToOneTarget
.
objects
.
get
(
pk
=
3
)
source
.
save
()
serializer
=
OneToOneTargetSerializer
(
instance
,
data
=
data
)
self
.
assertTrue
(
serializer
.
is_valid
())
def
test_foreign_key_retrieve_with_null
(
self
):
obj
=
serializer
.
save
()
queryset
=
NullableForeignKeySource
.
objects
.
all
()
self
.
assertEqual
(
serializer
.
data
,
data
)
serializer
=
NullableForeignKeySourceSerializer
(
queryset
,
many
=
True
)
self
.
assertEqual
(
obj
.
name
,
'target-3-updated'
)
# Ensure (target 3, target_source 3, source 3) are updated,
# and everything else is as expected.
queryset
=
OneToOneTarget
.
objects
.
all
()
serializer
=
OneToOneTargetSerializer
(
queryset
)
expected
=
[
expected
=
[
{
'id'
:
1
,
'name'
:
'
source-1'
,
'target'
:
{
'id'
:
1
,
'name'
:
'target-1'
}},
{
'id'
:
1
,
'name'
:
'
target-1'
,
'target_source'
:
{
'id'
:
1
,
'name'
:
'target-source-1'
,
'source'
:
{
'id'
:
1
,
'name'
:
'source-1'
}
}},
{
'id'
:
2
,
'name'
:
'
source-2'
,
'target'
:
{
'id'
:
1
,
'name'
:
'target-1'
}},
{
'id'
:
2
,
'name'
:
'
target-2'
,
'target_source'
:
{
'id'
:
2
,
'name'
:
'target-source-2'
,
'source'
:
{
'id'
:
2
,
'name'
:
'source-2'
}
}},
{
'id'
:
3
,
'name'
:
'
source-3'
,
'target'
:
None
},
{
'id'
:
3
,
'name'
:
'
target-3-updated'
,
'target_source'
:
{
'id'
:
3
,
'name'
:
'target-source-3-updated'
,
'source'
:
{
'id'
:
3
,
'name'
:
'source-3-updated'
}}}
]
]
self
.
assertEqual
(
serializer
.
data
,
expected
)
self
.
assertEqual
(
serializer
.
data
,
expected
)
def
test_one_to_one_delete
(
self
):
data
=
{
'id'
:
3
,
'name'
:
'target-3'
,
'target_source'
:
None
}
instance
=
OneToOneTarget
.
objects
.
get
(
pk
=
3
)
serializer
=
OneToOneTargetSerializer
(
instance
,
data
=
data
)
self
.
assertTrue
(
serializer
.
is_valid
())
serializer
.
save
()
class
NestedNullableOneToOneTests
(
TestCase
):
# Ensure (target_source 3, source 3) are deleted,
def
setUp
(
self
):
# and everything else is as expected.
target
=
OneToOneTarget
(
name
=
'target-1'
)
target
.
save
()
new_target
=
OneToOneTarget
(
name
=
'target-2'
)
new_target
.
save
()
source
=
NullableOneToOneSource
(
name
=
'source-1'
,
target
=
target
)
source
.
save
()
def
test_reverse_foreign_key_retrieve_with_null
(
self
):
queryset
=
OneToOneTarget
.
objects
.
all
()
queryset
=
OneToOneTarget
.
objects
.
all
()
serializer
=
NullableOneToOneTargetSerializer
(
queryset
,
many
=
True
)
serializer
=
OneToOneTargetSerializer
(
queryset
)
expected
=
[
expected
=
[
{
'id'
:
1
,
'name'
:
'target-1'
,
'nullable_source'
:
{
'id'
:
1
,
'name'
:
'source-1'
,
'target'
:
1
}},
{
'id'
:
1
,
'name'
:
'target-1'
,
'target_source'
:
{
'id'
:
1
,
'name'
:
'target-source-1'
,
'source'
:
{
'id'
:
1
,
'name'
:
'source-1'
}}},
{
'id'
:
2
,
'name'
:
'target-2'
,
'nullable_source'
:
None
},
{
'id'
:
2
,
'name'
:
'target-2'
,
'target_source'
:
{
'id'
:
2
,
'name'
:
'target-source-2'
,
'source'
:
{
'id'
:
2
,
'name'
:
'source-2'
}}},
{
'id'
:
3
,
'name'
:
'target-3'
,
'target_source'
:
None
}
]
]
self
.
assertEqual
(
serializer
.
data
,
expected
)
self
.
assertEqual
(
serializer
.
data
,
expected
)
rest_framework/tests/serializer_nested.py
0 → 100644
View file @
fb3b5780
"""
Tests to cover nested serializers.
Doesn't cover model serializers.
"""
from
__future__
import
unicode_literals
from
django.test
import
TestCase
from
rest_framework
import
serializers
class
WritableNestedSerializerBasicTests
(
TestCase
):
"""
Tests for deserializing nested entities.
Basic tests that use serializers that simply restore to dicts.
"""
def
setUp
(
self
):
class
TrackSerializer
(
serializers
.
Serializer
):
order
=
serializers
.
IntegerField
()
title
=
serializers
.
CharField
(
max_length
=
100
)
duration
=
serializers
.
IntegerField
()
class
AlbumSerializer
(
serializers
.
Serializer
):
album_name
=
serializers
.
CharField
(
max_length
=
100
)
artist
=
serializers
.
CharField
(
max_length
=
100
)
tracks
=
TrackSerializer
(
many
=
True
)
self
.
AlbumSerializer
=
AlbumSerializer
def
test_nested_validation_success
(
self
):
"""
Correct nested serialization should return the input data.
"""
data
=
{
'album_name'
:
'Discovery'
,
'artist'
:
'Daft Punk'
,
'tracks'
:
[
{
'order'
:
1
,
'title'
:
'One More Time'
,
'duration'
:
235
},
{
'order'
:
2
,
'title'
:
'Aerodynamic'
,
'duration'
:
184
},
{
'order'
:
3
,
'title'
:
'Digital Love'
,
'duration'
:
239
}
]
}
serializer
=
self
.
AlbumSerializer
(
data
=
data
)
self
.
assertEqual
(
serializer
.
is_valid
(),
True
)
self
.
assertEqual
(
serializer
.
object
,
data
)
def
test_nested_validation_error
(
self
):
"""
Incorrect nested serialization should return appropriate error data.
"""
data
=
{
'album_name'
:
'Discovery'
,
'artist'
:
'Daft Punk'
,
'tracks'
:
[
{
'order'
:
1
,
'title'
:
'One More Time'
,
'duration'
:
235
},
{
'order'
:
2
,
'title'
:
'Aerodynamic'
,
'duration'
:
184
},
{
'order'
:
3
,
'title'
:
'Digital Love'
,
'duration'
:
'foobar'
}
]
}
expected_errors
=
{
'tracks'
:
[
{},
{},
{
'duration'
:
[
'Enter a whole number.'
]}
]
}
serializer
=
self
.
AlbumSerializer
(
data
=
data
)
self
.
assertEqual
(
serializer
.
is_valid
(),
False
)
self
.
assertEqual
(
serializer
.
errors
,
expected_errors
)
def
test_many_nested_validation_error
(
self
):
"""
Incorrect nested serialization should return appropriate error data
when multiple entities are being deserialized.
"""
data
=
[
{
'album_name'
:
'Russian Red'
,
'artist'
:
'I Love Your Glasses'
,
'tracks'
:
[
{
'order'
:
1
,
'title'
:
'Cigarettes'
,
'duration'
:
121
},
{
'order'
:
2
,
'title'
:
'No Past Land'
,
'duration'
:
198
},
{
'order'
:
3
,
'title'
:
'They Don
\'
t Believe'
,
'duration'
:
191
}
]
},
{
'album_name'
:
'Discovery'
,
'artist'
:
'Daft Punk'
,
'tracks'
:
[
{
'order'
:
1
,
'title'
:
'One More Time'
,
'duration'
:
235
},
{
'order'
:
2
,
'title'
:
'Aerodynamic'
,
'duration'
:
184
},
{
'order'
:
3
,
'title'
:
'Digital Love'
,
'duration'
:
'foobar'
}
]
}
]
expected_errors
=
[
{},
{
'tracks'
:
[
{},
{},
{
'duration'
:
[
'Enter a whole number.'
]}
]
}
]
serializer
=
self
.
AlbumSerializer
(
data
=
data
)
self
.
assertEqual
(
serializer
.
is_valid
(),
False
)
self
.
assertEqual
(
serializer
.
errors
,
expected_errors
)
class
WritableNestedSerializerObjectTests
(
TestCase
):
"""
Tests for deserializing nested entities.
These tests use serializers that restore to concrete objects.
"""
def
setUp
(
self
):
# Couple of concrete objects that we're going to deserialize into
class
Track
(
object
):
def
__init__
(
self
,
order
,
title
,
duration
):
self
.
order
,
self
.
title
,
self
.
duration
=
order
,
title
,
duration
def
__eq__
(
self
,
other
):
return
(
self
.
order
==
other
.
order
and
self
.
title
==
other
.
title
and
self
.
duration
==
other
.
duration
)
class
Album
(
object
):
def
__init__
(
self
,
album_name
,
artist
,
tracks
):
self
.
album_name
,
self
.
artist
,
self
.
tracks
=
album_name
,
artist
,
tracks
def
__eq__
(
self
,
other
):
return
(
self
.
album_name
==
other
.
album_name
and
self
.
artist
==
other
.
artist
and
self
.
tracks
==
other
.
tracks
)
# And their corresponding serializers
class
TrackSerializer
(
serializers
.
Serializer
):
order
=
serializers
.
IntegerField
()
title
=
serializers
.
CharField
(
max_length
=
100
)
duration
=
serializers
.
IntegerField
()
def
restore_object
(
self
,
attrs
,
instance
=
None
):
return
Track
(
attrs
[
'order'
],
attrs
[
'title'
],
attrs
[
'duration'
])
class
AlbumSerializer
(
serializers
.
Serializer
):
album_name
=
serializers
.
CharField
(
max_length
=
100
)
artist
=
serializers
.
CharField
(
max_length
=
100
)
tracks
=
TrackSerializer
(
many
=
True
)
def
restore_object
(
self
,
attrs
,
instance
=
None
):
return
Album
(
attrs
[
'album_name'
],
attrs
[
'artist'
],
attrs
[
'tracks'
])
self
.
Album
,
self
.
Track
=
Album
,
Track
self
.
AlbumSerializer
=
AlbumSerializer
def
test_nested_validation_success
(
self
):
"""
Correct nested serialization should return a restored object
that corresponds to the input data.
"""
data
=
{
'album_name'
:
'Discovery'
,
'artist'
:
'Daft Punk'
,
'tracks'
:
[
{
'order'
:
1
,
'title'
:
'One More Time'
,
'duration'
:
235
},
{
'order'
:
2
,
'title'
:
'Aerodynamic'
,
'duration'
:
184
},
{
'order'
:
3
,
'title'
:
'Digital Love'
,
'duration'
:
239
}
]
}
expected_object
=
self
.
Album
(
album_name
=
'Discovery'
,
artist
=
'Daft Punk'
,
tracks
=
[
self
.
Track
(
order
=
1
,
title
=
'One More Time'
,
duration
=
235
),
self
.
Track
(
order
=
2
,
title
=
'Aerodynamic'
,
duration
=
184
),
self
.
Track
(
order
=
3
,
title
=
'Digital Love'
,
duration
=
239
),
]
)
serializer
=
self
.
AlbumSerializer
(
data
=
data
)
self
.
assertEqual
(
serializer
.
is_valid
(),
True
)
self
.
assertEqual
(
serializer
.
object
,
expected_object
)
def
test_many_nested_validation_success
(
self
):
"""
Correct nested serialization should return multiple restored objects
that corresponds to the input data when multiple objects are
being deserialized.
"""
data
=
[
{
'album_name'
:
'Russian Red'
,
'artist'
:
'I Love Your Glasses'
,
'tracks'
:
[
{
'order'
:
1
,
'title'
:
'Cigarettes'
,
'duration'
:
121
},
{
'order'
:
2
,
'title'
:
'No Past Land'
,
'duration'
:
198
},
{
'order'
:
3
,
'title'
:
'They Don
\'
t Believe'
,
'duration'
:
191
}
]
},
{
'album_name'
:
'Discovery'
,
'artist'
:
'Daft Punk'
,
'tracks'
:
[
{
'order'
:
1
,
'title'
:
'One More Time'
,
'duration'
:
235
},
{
'order'
:
2
,
'title'
:
'Aerodynamic'
,
'duration'
:
184
},
{
'order'
:
3
,
'title'
:
'Digital Love'
,
'duration'
:
239
}
]
}
]
expected_object
=
[
self
.
Album
(
album_name
=
'Russian Red'
,
artist
=
'I Love Your Glasses'
,
tracks
=
[
self
.
Track
(
order
=
1
,
title
=
'Cigarettes'
,
duration
=
121
),
self
.
Track
(
order
=
2
,
title
=
'No Past Land'
,
duration
=
198
),
self
.
Track
(
order
=
3
,
title
=
'They Don
\'
t Believe'
,
duration
=
191
),
]
),
self
.
Album
(
album_name
=
'Discovery'
,
artist
=
'Daft Punk'
,
tracks
=
[
self
.
Track
(
order
=
1
,
title
=
'One More Time'
,
duration
=
235
),
self
.
Track
(
order
=
2
,
title
=
'Aerodynamic'
,
duration
=
184
),
self
.
Track
(
order
=
3
,
title
=
'Digital Love'
,
duration
=
239
),
]
)
]
serializer
=
self
.
AlbumSerializer
(
data
=
data
)
self
.
assertEqual
(
serializer
.
is_valid
(),
True
)
self
.
assertEqual
(
serializer
.
object
,
expected_object
)
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