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
3006e382
Commit
3006e382
authored
Mar 12, 2013
by
Mark Aaron Shirley
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
One-to-one writable, nested serializer support
parent
b6b686d2
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
155 additions
and
8 deletions
+155
-8
rest_framework/serializers.py
+28
-6
rest_framework/tests/nesting.py
+125
-0
rest_framework/tests/serializer_nested.py
+2
-2
No files found.
rest_framework/serializers.py
View file @
3006e382
...
...
@@ -26,12 +26,16 @@ class NestedValidationError(ValidationError):
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.
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
...
...
@@ -143,6 +147,7 @@ class BaseSerializer(WritableField):
self
.
_data
=
None
self
.
_files
=
None
self
.
_errors
=
None
self
.
_delete
=
False
#####
# Methods to determine which fields to use when (de)serializing objects.
...
...
@@ -354,15 +359,19 @@ class BaseSerializer(WritableField):
raise
ValidationError
(
self
.
error_messages
[
'required'
])
return
if
self
.
parent
.
object
:
# Set the serializer object if it exists
obj
=
getattr
(
self
.
parent
.
object
,
field_name
)
self
.
object
=
obj
obj
=
getattr
(
self
.
parent
.
object
,
field_name
)
if
self
.
parent
.
object
else
None
if
value
in
(
None
,
''
):
if
isinstance
(
self
,
ModelSerializer
):
self
.
_delete
=
True
self
.
object
=
obj
into
[(
self
.
source
or
field_name
)]
=
self
else
:
into
[(
self
.
source
or
field_name
)]
=
None
else
:
kwargs
=
{
'instance'
:
obj
,
'data'
:
value
,
'context'
:
self
.
context
,
'partial'
:
self
.
partial
,
...
...
@@ -371,7 +380,9 @@ class BaseSerializer(WritableField):
serializer
=
self
.
__class__
(
**
kwargs
)
if
serializer
.
is_valid
():
self
.
object
=
serializer
.
object
if
isinstance
(
serializer
,
ModelSerializer
):
into
[
self
.
source
or
field_name
]
=
serializer
else
:
into
[
self
.
source
or
field_name
]
=
serializer
.
object
else
:
# Propagate errors up to our parent
...
...
@@ -664,10 +675,17 @@ class ModelSerializer(Serializer):
if
instance
:
return
self
.
full_clean
(
instance
)
def
save_object
(
self
,
obj
):
def
save_object
(
self
,
obj
,
parent
=
None
,
fk_field
=
None
):
"""
Save the deserialized object and return it.
"""
if
self
.
_delete
:
obj
.
delete
()
return
if
parent
and
fk_field
:
setattr
(
self
.
object
,
fk_field
,
parent
)
obj
.
save
()
if
getattr
(
self
,
'm2m_data'
,
None
):
...
...
@@ -677,6 +695,10 @@ class ModelSerializer(Serializer):
if
getattr
(
self
,
'related_data'
,
None
):
for
accessor_name
,
object_list
in
self
.
related_data
.
items
():
if
isinstance
(
object_list
,
ModelSerializer
):
fk_field
=
self
.
object
.
_meta
.
get_field_by_name
(
accessor_name
)[
0
]
.
field
.
name
object_list
.
save_object
(
object_list
.
object
,
parent
=
self
.
object
,
fk_field
=
fk_field
)
else
:
setattr
(
self
.
object
,
accessor_name
,
object_list
)
self
.
related_data
=
{}
...
...
rest_framework/tests/nesting.py
0 → 100644
View file @
3006e382
from
__future__
import
unicode_literals
from
django.db
import
models
from
django.test
import
TestCase
from
rest_framework
import
serializers
class
OneToOneTarget
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
100
)
class
OneToOneTargetSource
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
100
)
target
=
models
.
OneToOneField
(
OneToOneTarget
,
null
=
True
,
blank
=
True
,
related_name
=
'target_source'
)
class
OneToOneSource
(
models
.
Model
):
name
=
models
.
CharField
(
max_length
=
100
)
target_source
=
models
.
OneToOneField
(
OneToOneTargetSource
,
related_name
=
'source'
)
class
OneToOneSourceSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
OneToOneSource
exclude
=
(
'target_source'
,
)
class
OneToOneTargetSourceSerializer
(
serializers
.
ModelSerializer
):
source
=
OneToOneSourceSerializer
()
class
Meta
:
model
=
OneToOneTargetSource
exclude
=
(
'target'
,
)
class
OneToOneTargetSerializer
(
serializers
.
ModelSerializer
):
target_source
=
OneToOneTargetSourceSerializer
()
class
Meta
:
model
=
OneToOneTarget
class
NestedOneToOneTests
(
TestCase
):
def
setUp
(
self
):
for
idx
in
range
(
1
,
4
):
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
()
def
test_one_to_one_retrieve
(
self
):
queryset
=
OneToOneTarget
.
objects
.
all
()
serializer
=
OneToOneTargetSerializer
(
queryset
)
expected
=
[
{
'id'
:
1
,
'name'
:
'target-1'
,
'target_source'
:
{
'id'
:
1
,
'name'
:
'target-source-1'
,
'source'
:
{
'id'
:
1
,
'name'
:
'source-1'
}}},
{
'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'
:
{
'id'
:
3
,
'name'
:
'target-source-3'
,
'source'
:
{
'id'
:
3
,
'name'
:
'source-3'
}}}
]
self
.
assertEqual
(
serializer
.
data
,
expected
)
def
test_one_to_one_create
(
self
):
data
=
{
'id'
:
4
,
'name'
:
'target-4'
,
'target_source'
:
{
'id'
:
4
,
'name'
:
'target-source-4'
,
'source'
:
{
'id'
:
4
,
'name'
:
'source-4'
}}}
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
=
[
{
'id'
:
1
,
'name'
:
'target-1'
,
'target_source'
:
{
'id'
:
1
,
'name'
:
'target-source-1'
,
'source'
:
{
'id'
:
1
,
'name'
:
'source-1'
}}},
{
'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'
:
{
'id'
:
3
,
'name'
:
'target-source-3'
,
'source'
:
{
'id'
:
3
,
'name'
:
'source-3'
}}},
{
'id'
:
4
,
'name'
:
'target-4'
,
'target_source'
:
{
'id'
:
4
,
'name'
:
'target-source-4'
,
'source'
:
{
'id'
:
4
,
'name'
:
'source-4'
}}}
]
self
.
assertEqual
(
serializer
.
data
,
expected
)
def
test_one_to_one_create_with_invalid_data
(
self
):
data
=
{
'id'
:
4
,
'name'
:
'target-4'
,
'target_source'
:
{
'id'
:
4
,
'name'
:
'target-source-4'
,
'source'
:
{
'id'
:
4
}}}
serializer
=
OneToOneTargetSerializer
(
data
=
data
)
self
.
assertFalse
(
serializer
.
is_valid
())
self
.
assertEqual
(
serializer
.
errors
,
{
'target_source'
:
[{
'source'
:
[{
'name'
:
[
'This field is required.'
]}]}]})
def
test_one_to_one_update
(
self
):
data
=
{
'id'
:
3
,
'name'
:
'target-3-updated'
,
'target_source'
:
{
'id'
:
3
,
'name'
:
'target-source-3-updated'
,
'source'
:
{
'id'
:
3
,
'name'
:
'source-3-updated'
}}}
instance
=
OneToOneTarget
.
objects
.
get
(
pk
=
3
)
serializer
=
OneToOneTargetSerializer
(
instance
,
data
=
data
)
self
.
assertTrue
(
serializer
.
is_valid
())
obj
=
serializer
.
save
()
self
.
assertEqual
(
serializer
.
data
,
data
)
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
=
[
{
'id'
:
1
,
'name'
:
'target-1'
,
'target_source'
:
{
'id'
:
1
,
'name'
:
'target-source-1'
,
'source'
:
{
'id'
:
1
,
'name'
:
'source-1'
}}},
{
'id'
:
2
,
'name'
:
'target-2'
,
'target_source'
:
{
'id'
:
2
,
'name'
:
'target-source-2'
,
'source'
:
{
'id'
:
2
,
'name'
:
'source-2'
}}},
{
'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
)
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
())
obj
=
serializer
.
save
()
# Ensure (target_source 3, source 3) are deleted,
# and everything else is as expected.
queryset
=
OneToOneTarget
.
objects
.
all
()
serializer
=
OneToOneTargetSerializer
(
queryset
)
expected
=
[
{
'id'
:
1
,
'name'
:
'target-1'
,
'target_source'
:
{
'id'
:
1
,
'name'
:
'target-source-1'
,
'source'
:
{
'id'
:
1
,
'name'
:
'source-1'
}}},
{
'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
)
rest_framework/tests/serializer_nested.py
View file @
3006e382
...
...
@@ -124,7 +124,7 @@ class WritableNestedSerializerObjectTests(TestCase):
def
__init__
(
self
,
order
,
title
,
duration
):
self
.
order
,
self
.
title
,
self
.
duration
=
order
,
title
,
duration
def
__
cmp
__
(
self
,
other
):
def
__
eq
__
(
self
,
other
):
return
(
self
.
order
==
other
.
order
and
self
.
title
==
other
.
title
and
...
...
@@ -135,7 +135,7 @@ class WritableNestedSerializerObjectTests(TestCase):
def
__init__
(
self
,
album_name
,
artist
,
tracks
):
self
.
album_name
,
self
.
artist
,
self
.
tracks
=
album_name
,
artist
,
tracks
def
__
cmp
__
(
self
,
other
):
def
__
eq
__
(
self
,
other
):
return
(
self
.
album_name
==
other
.
album_name
and
self
.
artist
==
other
.
artist
and
...
...
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