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
0a769f26
Commit
0a769f26
authored
Oct 03, 2012
by
Tom Christie
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #277 from tomchristie/related-field-fixes
Related field fixes
parents
89ec0b27
a02707e1
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
107 additions
and
35 deletions
+107
-35
rest_framework/fields.py
+43
-28
rest_framework/renderers.py
+11
-3
rest_framework/serializers.py
+5
-2
rest_framework/templates/rest_framework/base.html
+6
-2
rest_framework/tests/serializer.py
+42
-0
No files found.
rest_framework/fields.py
View file @
0a769f26
...
@@ -7,7 +7,6 @@ from django.core import validators
...
@@ -7,7 +7,6 @@ from django.core import validators
from
django.core.exceptions
import
ValidationError
from
django.core.exceptions
import
ValidationError
from
django.conf
import
settings
from
django.conf
import
settings
from
django.db
import
DEFAULT_DB_ALIAS
from
django.db
import
DEFAULT_DB_ALIAS
from
django.db.models.related
import
RelatedObject
from
django.utils.encoding
import
is_protected_type
,
smart_unicode
from
django.utils.encoding
import
is_protected_type
,
smart_unicode
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
rest_framework.compat
import
parse_date
,
parse_datetime
from
rest_framework.compat
import
parse_date
,
parse_datetime
...
@@ -181,6 +180,9 @@ class RelatedField(Field):
...
@@ -181,6 +180,9 @@ class RelatedField(Field):
Subclass this and override `convert` to define custom behaviour when
Subclass this and override `convert` to define custom behaviour when
serializing related objects.
serializing related objects.
"""
"""
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
queryset
=
kwargs
.
pop
(
'queryset'
,
None
)
super
(
RelatedField
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
def
field_to_native
(
self
,
obj
,
field_name
):
def
field_to_native
(
self
,
obj
,
field_name
):
obj
=
getattr
(
obj
,
self
.
source
or
field_name
)
obj
=
getattr
(
obj
,
self
.
source
or
field_name
)
...
@@ -200,48 +202,61 @@ class RelatedField(Field):
...
@@ -200,48 +202,61 @@ class RelatedField(Field):
class
PrimaryKeyRelatedField
(
RelatedField
):
class
PrimaryKeyRelatedField
(
RelatedField
):
"""
"""
Serializes a
model related field or related manager
to a pk value.
Serializes a
related field or related object
to a pk value.
"""
"""
# Note the we use ModelRelatedField's implementation, as we want to get the
# raw database value directly, since that won't involve another
# database lookup.
#
# An alternative implementation would simply be this...
#
# class PrimaryKeyRelatedField(RelatedField):
# def to_native(self, obj):
# return obj.pk
def
to_native
(
self
,
pk
):
def
to_native
(
self
,
pk
):
"""
"""
Simply returns the object's pk. You can subclass this method to
You can subclass this method to provide different serialization
provide different serialization behavior of the pk.
behavior based on the pk.
(For example returning a URL based on the model's pk.)
"""
"""
return
pk
return
pk
def
field_to_native
(
self
,
obj
,
field_name
):
def
field_to_native
(
self
,
obj
,
field_name
):
# This is only implemented for performance reasons
#
# We could leave the default `RelatedField.field_to_native()` in place,
# and inside just implement `to_native()` as `return obj.pk`
#
# That would involve an extra database lookup.
try
:
try
:
obj
=
obj
.
serializable_value
(
self
.
source
or
field_name
)
pk
=
obj
.
serializable_value
(
self
.
source
or
field_name
)
except
AttributeError
:
except
AttributeError
:
field
=
obj
.
_meta
.
get_field_by_name
(
field_name
)[
0
]
# RelatedObject (reverse relationship)
obj
=
getattr
(
obj
,
self
.
source
or
field_name
)
obj
=
getattr
(
obj
,
self
.
source
or
field_name
)
if
obj
.
__class__
.
__name__
==
'RelatedManager'
:
return
self
.
to_native
(
obj
.
pk
)
return
[
self
.
to_native
(
item
.
pk
)
for
item
in
obj
.
all
()]
# Forward relationship
elif
isinstance
(
field
,
RelatedObject
):
return
self
.
to_native
(
pk
)
return
self
.
to_native
(
obj
.
pk
)
raise
if
obj
.
__class__
.
__name__
==
'ManyRelatedManager'
:
return
[
self
.
to_native
(
item
.
pk
)
for
item
in
obj
.
all
()]
return
self
.
to_native
(
obj
)
def
field_from_native
(
self
,
data
,
field_name
,
into
):
def
field_from_native
(
self
,
data
,
field_name
,
into
):
value
=
data
.
get
(
field_name
)
value
=
data
.
get
(
field_name
)
if
hasattr
(
value
,
'__iter__'
):
into
[
field_name
+
'_id'
]
=
self
.
from_native
(
value
)
into
[
field_name
]
=
[
self
.
from_native
(
item
)
for
item
in
value
]
class
ManyPrimaryKeyRelatedField
(
PrimaryKeyRelatedField
):
"""
Serializes a to-many related field or related manager to a pk value.
"""
def
field_to_native
(
self
,
obj
,
field_name
):
try
:
queryset
=
obj
.
serializable_value
(
self
.
source
or
field_name
)
except
AttributeError
:
# RelatedManager (reverse relationship)
queryset
=
getattr
(
obj
,
self
.
source
or
field_name
)
return
[
self
.
to_native
(
item
.
pk
)
for
item
in
queryset
.
all
()]
# Forward relationship
return
[
self
.
to_native
(
item
.
pk
)
for
item
in
queryset
.
all
()]
def
field_from_native
(
self
,
data
,
field_name
,
into
):
try
:
value
=
data
.
getlist
(
field_name
)
except
:
value
=
data
.
get
(
field_name
)
else
:
else
:
into
[
field_name
+
'_id'
]
=
self
.
from_native
(
value
)
if
value
==
[
''
]:
value
=
[]
into
[
field_name
]
=
[
self
.
from_native
(
item
)
for
item
in
value
]
class
NaturalKeyRelatedField
(
RelatedField
):
class
NaturalKeyRelatedField
(
RelatedField
):
...
...
rest_framework/renderers.py
View file @
0a769f26
...
@@ -246,7 +246,9 @@ class DocumentingHTMLRenderer(BaseRenderer):
...
@@ -246,7 +246,9 @@ class DocumentingHTMLRenderer(BaseRenderer):
serializers
.
DateField
:
forms
.
DateField
,
serializers
.
DateField
:
forms
.
DateField
,
serializers
.
EmailField
:
forms
.
EmailField
,
serializers
.
EmailField
:
forms
.
EmailField
,
serializers
.
CharField
:
forms
.
CharField
,
serializers
.
CharField
:
forms
.
CharField
,
serializers
.
BooleanField
:
forms
.
BooleanField
serializers
.
BooleanField
:
forms
.
BooleanField
,
serializers
.
PrimaryKeyRelatedField
:
forms
.
ModelChoiceField
,
serializers
.
ManyPrimaryKeyRelatedField
:
forms
.
ModelMultipleChoiceField
}
}
# Creating an on the fly form see: http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python
# Creating an on the fly form see: http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python
...
@@ -257,12 +259,18 @@ class DocumentingHTMLRenderer(BaseRenderer):
...
@@ -257,12 +259,18 @@ class DocumentingHTMLRenderer(BaseRenderer):
serializer
=
view
.
get_serializer
(
instance
=
obj
)
serializer
=
view
.
get_serializer
(
instance
=
obj
)
for
k
,
v
in
serializer
.
get_fields
(
True
)
.
items
():
for
k
,
v
in
serializer
.
get_fields
(
True
)
.
items
():
print
k
,
v
if
v
.
readonly
:
if
v
.
readonly
:
continue
continue
kwargs
=
{}
if
getattr
(
v
,
'queryset'
,
None
):
kwargs
[
'queryset'
]
=
getattr
(
v
,
'queryset'
,
None
)
try
:
try
:
fields
[
k
]
=
field_mapping
[
v
.
__class__
]()
fields
[
k
]
=
field_mapping
[
v
.
__class__
](
**
kwargs
)
except
KeyError
:
except
KeyError
:
fields
[
k
]
=
forms
.
CharField
fields
[
k
]
=
forms
.
CharField
()
OnTheFlyForm
=
type
(
"OnTheFlyForm"
,
(
forms
.
Form
,),
fields
)
OnTheFlyForm
=
type
(
"OnTheFlyForm"
,
(
forms
.
Form
,),
fields
)
if
obj
and
not
view
.
request
.
method
==
'DELETE'
:
# Don't fill in the form when the object is deleted
if
obj
and
not
view
.
request
.
method
==
'DELETE'
:
# Don't fill in the form when the object is deleted
...
...
rest_framework/serializers.py
View file @
0a769f26
...
@@ -351,7 +351,10 @@ class ModelSerializer(RelatedField, Serializer):
...
@@ -351,7 +351,10 @@ class ModelSerializer(RelatedField, Serializer):
"""
"""
Creates a default instance of a flat relational field.
Creates a default instance of a flat relational field.
"""
"""
return
PrimaryKeyRelatedField
()
queryset
=
model_field
.
rel
.
to
.
_default_manager
# .using(db).complex_filter(self.rel.limit_choices_to)
if
isinstance
(
model_field
,
models
.
fields
.
related
.
ManyToManyField
):
return
ManyPrimaryKeyRelatedField
(
queryset
=
queryset
)
return
PrimaryKeyRelatedField
(
queryset
=
queryset
)
def
get_field
(
self
,
model_field
):
def
get_field
(
self
,
model_field
):
"""
"""
...
@@ -365,7 +368,7 @@ class ModelSerializer(RelatedField, Serializer):
...
@@ -365,7 +368,7 @@ class ModelSerializer(RelatedField, Serializer):
models
.
EmailField
:
EmailField
,
models
.
EmailField
:
EmailField
,
models
.
CharField
:
CharField
,
models
.
CharField
:
CharField
,
models
.
CommaSeparatedIntegerField
:
CharField
,
models
.
CommaSeparatedIntegerField
:
CharField
,
models
.
BooleanField
:
BooleanField
models
.
BooleanField
:
BooleanField
,
}
}
try
:
try
:
return
field_mapping
[
model_field
.
__class__
]()
return
field_mapping
[
model_field
.
__class__
]()
...
...
rest_framework/templates/rest_framework/base.html
View file @
0a769f26
...
@@ -122,6 +122,7 @@
...
@@ -122,6 +122,7 @@
{% if response.status_code != 403 %}
{% if response.status_code != 403 %}
{% if post_form %}
{% if post_form %}
<div
class=
"well"
>
<form
action=
"{{ request.get_full_path }}"
method=
"POST"
{%
if
post_form
.
is_multipart
%}
enctype=
"multipart/form-data"
{%
endif
%}
class=
"form-horizontal"
>
<form
action=
"{{ request.get_full_path }}"
method=
"POST"
{%
if
post_form
.
is_multipart
%}
enctype=
"multipart/form-data"
{%
endif
%}
class=
"form-horizontal"
>
<fieldset>
<fieldset>
<h2>
POST: {{ name }}
</h2>
<h2>
POST: {{ name }}
</h2>
...
@@ -131,7 +132,7 @@
...
@@ -131,7 +132,7 @@
<div
class=
"control-group {% if field.errors %}error{% endif %}"
>
<div
class=
"control-group {% if field.errors %}error{% endif %}"
>
{{ field.label_tag|add_class:"control-label" }}
{{ field.label_tag|add_class:"control-label" }}
<div
class=
"controls"
>
<div
class=
"controls"
>
{{ field }}
{{ field
|add_class:"input-xlarge"
}}
<span
class=
"help-inline"
>
{{ field.help_text }}
</span>
<span
class=
"help-inline"
>
{{ field.help_text }}
</span>
{{ field.errors|add_class:"help-block" }}
{{ field.errors|add_class:"help-block" }}
</div>
</div>
...
@@ -142,9 +143,11 @@
...
@@ -142,9 +143,11 @@
</div>
</div>
</fieldset>
</fieldset>
</form>
</form>
</div>
{% endif %}
{% endif %}
{% if put_form %}
{% if put_form %}
<div
class=
"well"
>
<form
action=
"{{ request.get_full_path }}"
method=
"POST"
{%
if
put_form
.
is_multipart
%}
enctype=
"multipart/form-data"
{%
endif
%}
class=
"form-horizontal"
>
<form
action=
"{{ request.get_full_path }}"
method=
"POST"
{%
if
put_form
.
is_multipart
%}
enctype=
"multipart/form-data"
{%
endif
%}
class=
"form-horizontal"
>
<fieldset>
<fieldset>
<h2>
PUT: {{ name }}
</h2>
<h2>
PUT: {{ name }}
</h2>
...
@@ -155,7 +158,7 @@
...
@@ -155,7 +158,7 @@
<div
class=
"control-group {% if field.errors %}error{% endif %}"
>
<div
class=
"control-group {% if field.errors %}error{% endif %}"
>
{{ field.label_tag|add_class:"control-label" }}
{{ field.label_tag|add_class:"control-label" }}
<div
class=
"controls"
>
<div
class=
"controls"
>
{{ field }}
{{ field
|add_class:"input-xlarge"
}}
<span
class=
'help-inline'
>
{{ field.help_text }}
</span>
<span
class=
'help-inline'
>
{{ field.help_text }}
</span>
{{ field.errors|add_class:"help-block" }}
{{ field.errors|add_class:"help-block" }}
</div>
</div>
...
@@ -167,6 +170,7 @@
...
@@ -167,6 +170,7 @@
</fieldset>
</fieldset>
</form>
</form>
</div>
{% endif %}
{% endif %}
{% endif %}
{% endif %}
...
...
rest_framework/tests/serializer.py
View file @
0a769f26
...
@@ -160,6 +160,48 @@ class ManyToManyTests(TestCase):
...
@@ -160,6 +160,48 @@ class ManyToManyTests(TestCase):
self
.
assertEquals
(
instance
.
pk
,
1
)
self
.
assertEquals
(
instance
.
pk
,
1
)
self
.
assertEquals
(
list
(
instance
.
rel
.
all
()),
[
self
.
anchor
,
new_anchor
])
self
.
assertEquals
(
list
(
instance
.
rel
.
all
()),
[
self
.
anchor
,
new_anchor
])
def
test_create_empty_relationship
(
self
):
"""
Create an instance of a model with a ManyToMany relationship,
containing no items.
"""
data
=
{
'rel'
:
[]}
serializer
=
self
.
serializer_class
(
data
)
self
.
assertEquals
(
serializer
.
is_valid
(),
True
)
instance
=
serializer
.
save
()
self
.
assertEquals
(
len
(
ManyToManyModel
.
objects
.
all
()),
2
)
self
.
assertEquals
(
instance
.
pk
,
2
)
self
.
assertEquals
(
list
(
instance
.
rel
.
all
()),
[])
def
test_update_empty_relationship
(
self
):
"""
Update an instance of a model with a ManyToMany relationship,
containing no items.
"""
new_anchor
=
Anchor
()
new_anchor
.
save
()
data
=
{
'rel'
:
[]}
serializer
=
self
.
serializer_class
(
data
,
instance
=
self
.
instance
)
self
.
assertEquals
(
serializer
.
is_valid
(),
True
)
instance
=
serializer
.
save
()
self
.
assertEquals
(
len
(
ManyToManyModel
.
objects
.
all
()),
1
)
self
.
assertEquals
(
instance
.
pk
,
1
)
self
.
assertEquals
(
list
(
instance
.
rel
.
all
()),
[])
def
test_create_empty_relationship_flat_data
(
self
):
"""
Create an instance of a model with a ManyToMany relationship,
containing no items, using a representation that does not support
lists (eg form data).
"""
data
=
{
'rel'
:
''
}
serializer
=
self
.
serializer_class
(
data
)
self
.
assertEquals
(
serializer
.
is_valid
(),
True
)
instance
=
serializer
.
save
()
self
.
assertEquals
(
len
(
ManyToManyModel
.
objects
.
all
()),
2
)
self
.
assertEquals
(
instance
.
pk
,
2
)
self
.
assertEquals
(
list
(
instance
.
rel
.
all
()),
[])
# def test_deserialization_for_update(self):
# def test_deserialization_for_update(self):
# serializer = self.serializer_class(self.data, instance=self.instance)
# serializer = self.serializer_class(self.data, instance=self.instance)
# expected = self.instance
# expected = self.instance
...
...
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