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
c3e370b1
Commit
c3e370b1
authored
Oct 10, 2013
by
Tom Christie
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'html-form-rendering'
parents
afc9e9e0
9e29c638
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
97 additions
and
74 deletions
+97
-74
rest_framework/fields.py
+24
-0
rest_framework/renderers.py
+15
-63
rest_framework/serializers.py
+21
-2
rest_framework/templates/rest_framework/base.html
+2
-2
rest_framework/templates/rest_framework/form.html
+7
-5
rest_framework/templates/rest_framework/raw_data_form.html
+12
-0
rest_framework/tests/test_serializer.py
+1
-2
rest_framework/tests/test_serializer_empty.py
+15
-0
No files found.
rest_framework/fields.py
View file @
c3e370b1
...
...
@@ -123,6 +123,7 @@ class Field(object):
use_files
=
False
form_field_class
=
forms
.
CharField
type_label
=
'field'
widget
=
None
def
__init__
(
self
,
source
=
None
,
label
=
None
,
help_text
=
None
):
self
.
parent
=
None
...
...
@@ -134,9 +135,29 @@ class Field(object):
if
label
is
not
None
:
self
.
label
=
smart_text
(
label
)
else
:
self
.
label
=
None
if
help_text
is
not
None
:
self
.
help_text
=
strip_multiple_choice_msg
(
smart_text
(
help_text
))
else
:
self
.
help_text
=
None
self
.
_errors
=
[]
self
.
_value
=
None
self
.
_name
=
None
@property
def
errors
(
self
):
return
self
.
_errors
def
widget_html
(
self
):
if
not
self
.
widget
:
return
''
return
self
.
widget
.
render
(
self
.
_name
,
self
.
_value
)
def
label_tag
(
self
):
return
'<label for="
%
s">
%
s:</label>'
%
(
self
.
_name
,
self
.
label
)
def
initialize
(
self
,
parent
,
field_name
):
"""
...
...
@@ -757,6 +778,7 @@ class IntegerField(WritableField):
type_name
=
'IntegerField'
type_label
=
'integer'
form_field_class
=
forms
.
IntegerField
empty
=
0
default_error_messages
=
{
'invalid'
:
_
(
'Enter a whole number.'
),
...
...
@@ -788,6 +810,7 @@ class FloatField(WritableField):
type_name
=
'FloatField'
type_label
=
'float'
form_field_class
=
forms
.
FloatField
empty
=
0
default_error_messages
=
{
'invalid'
:
_
(
"'
%
s' value must be a float."
),
...
...
@@ -808,6 +831,7 @@ class DecimalField(WritableField):
type_name
=
'DecimalField'
type_label
=
'decimal'
form_field_class
=
forms
.
DecimalField
empty
=
Decimal
(
'0'
)
default_error_messages
=
{
'invalid'
:
_
(
'Enter a number.'
),
...
...
rest_framework/renderers.py
View file @
c3e370b1
...
...
@@ -336,71 +336,15 @@ class HTMLFormRenderer(BaseRenderer):
template
=
'rest_framework/form.html'
charset
=
'utf-8'
def
data_to_form_fields
(
self
,
data
):
fields
=
{}
for
key
,
val
in
data
.
fields
.
items
():
if
getattr
(
val
,
'read_only'
,
True
):
# Don't include read-only fields.
continue
if
getattr
(
val
,
'fields'
,
None
):
# Nested data not supported by HTML forms.
continue
kwargs
=
{}
kwargs
[
'required'
]
=
val
.
required
#if getattr(v, 'queryset', None):
# kwargs['queryset'] = v.queryset
if
getattr
(
val
,
'choices'
,
None
)
is
not
None
:
kwargs
[
'choices'
]
=
val
.
choices
if
getattr
(
val
,
'regex'
,
None
)
is
not
None
:
kwargs
[
'regex'
]
=
val
.
regex
if
getattr
(
val
,
'widget'
,
None
):
widget
=
copy
.
deepcopy
(
val
.
widget
)
kwargs
[
'widget'
]
=
widget
if
getattr
(
val
,
'default'
,
None
)
is
not
None
:
kwargs
[
'initial'
]
=
val
.
default
if
getattr
(
val
,
'label'
,
None
)
is
not
None
:
kwargs
[
'label'
]
=
val
.
label
if
getattr
(
val
,
'help_text'
,
None
)
is
not
None
:
kwargs
[
'help_text'
]
=
val
.
help_text
fields
[
key
]
=
val
.
form_field_class
(
**
kwargs
)
return
fields
def
render
(
self
,
data
,
accepted_media_type
=
None
,
renderer_context
=
None
):
"""
Render serializer data and return an HTML form, as a string.
"""
# The HTMLFormRenderer currently uses something of a hack to render
# the content, by translating each of the serializer fields into
# an html form field, creating a dynamic form using those fields,
# and then rendering that form.
# This isn't strictly neccessary, as we could render the serilizer
# fields to HTML directly. The implementation is historical and will
# likely change at some point.
self
.
renderer_context
=
renderer_context
or
{}
request
=
self
.
renderer_context
[
'request'
]
# Creating an on the fly form see:
# http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python
fields
=
self
.
data_to_form_fields
(
data
)
DynamicForm
=
type
(
str
(
'DynamicForm'
),
(
forms
.
Form
,),
fields
)
data
=
None
if
data
.
empty
else
data
renderer_context
=
renderer_context
or
{}
request
=
renderer_context
[
'request'
]
template
=
loader
.
get_template
(
self
.
template
)
context
=
RequestContext
(
request
,
{
'form'
:
DynamicForm
(
data
)})
context
=
RequestContext
(
request
,
{
'form'
:
data
})
return
template
.
render
(
context
)
...
...
@@ -475,6 +419,13 @@ class BrowsableAPIRenderer(BaseRenderer):
In the absence of the View having an associated form then return None.
"""
if
request
.
method
==
method
:
data
=
request
.
DATA
files
=
request
.
FILES
else
:
data
=
None
files
=
None
with
override_method
(
view
,
request
,
method
)
as
request
:
obj
=
getattr
(
view
,
'object'
,
None
)
if
not
self
.
show_form_for_method
(
view
,
method
,
request
,
obj
):
...
...
@@ -487,9 +438,10 @@ class BrowsableAPIRenderer(BaseRenderer):
or
not
any
(
is_form_media_type
(
parser
.
media_type
)
for
parser
in
view
.
parser_classes
)):
return
serializer
=
view
.
get_serializer
(
instance
=
obj
)
serializer
=
view
.
get_serializer
(
instance
=
obj
,
data
=
data
,
files
=
files
)
serializer
.
is_valid
()
data
=
serializer
.
data
form_renderer
=
self
.
form_renderer_class
()
return
form_renderer
.
render
(
data
,
self
.
accepted_media_type
,
self
.
renderer_context
)
...
...
@@ -581,6 +533,7 @@ class BrowsableAPIRenderer(BaseRenderer):
renderer
=
self
.
get_default_renderer
(
view
)
raw_data_post_form
=
self
.
get_raw_data_form
(
view
,
'POST'
,
request
)
raw_data_put_form
=
self
.
get_raw_data_form
(
view
,
'PUT'
,
request
)
raw_data_patch_form
=
self
.
get_raw_data_form
(
view
,
'PATCH'
,
request
)
raw_data_put_or_patch_form
=
raw_data_put_form
or
raw_data_patch_form
...
...
@@ -599,12 +552,11 @@ class BrowsableAPIRenderer(BaseRenderer):
'put_form'
:
self
.
get_rendered_html_form
(
view
,
'PUT'
,
request
),
'post_form'
:
self
.
get_rendered_html_form
(
view
,
'POST'
,
request
),
'patch_form'
:
self
.
get_rendered_html_form
(
view
,
'PATCH'
,
request
),
'delete_form'
:
self
.
get_rendered_html_form
(
view
,
'DELETE'
,
request
),
'options_form'
:
self
.
get_rendered_html_form
(
view
,
'OPTIONS'
,
request
),
'raw_data_put_form'
:
raw_data_put_form
,
'raw_data_post_form'
:
self
.
get_raw_data_form
(
view
,
'POST'
,
request
)
,
'raw_data_post_form'
:
raw_data_post_form
,
'raw_data_patch_form'
:
raw_data_patch_form
,
'raw_data_put_or_patch_form'
:
raw_data_put_or_patch_form
,
...
...
rest_framework/serializers.py
View file @
c3e370b1
...
...
@@ -32,6 +32,13 @@ from rest_framework.relations import *
from
rest_framework.fields
import
*
def
pretty_name
(
name
):
"""Converts 'first_name' to 'First name'"""
if
not
name
:
return
''
return
name
.
replace
(
'_'
,
' '
)
.
capitalize
()
class
RelationsList
(
list
):
_deleted
=
[]
...
...
@@ -301,14 +308,16 @@ class BaseSerializer(WritableField):
"""
ret
=
self
.
_dict_class
()
ret
.
fields
=
self
.
_dict_class
()
ret
.
empty
=
obj
is
None
for
field_name
,
field
in
self
.
fields
.
items
():
if
field
.
read_only
and
obj
is
None
:
continue
field
.
initialize
(
parent
=
self
,
field_name
=
field_name
)
key
=
self
.
get_field_key
(
field_name
)
value
=
field
.
field_to_native
(
obj
,
field_name
)
ret
[
key
]
=
value
ret
.
fields
[
key
]
=
field
ret
.
fields
[
key
]
=
self
.
augment_field
(
field
,
field_name
,
key
,
value
)
return
ret
def
from_native
(
self
,
data
,
files
):
...
...
@@ -316,6 +325,7 @@ class BaseSerializer(WritableField):
Deserialize primitives -> objects.
"""
self
.
_errors
=
{}
if
data
is
not
None
or
files
is
not
None
:
attrs
=
self
.
restore_fields
(
data
,
files
)
if
attrs
is
not
None
:
...
...
@@ -326,6 +336,15 @@ class BaseSerializer(WritableField):
if
not
self
.
_errors
:
return
self
.
restore_object
(
attrs
,
instance
=
getattr
(
self
,
'object'
,
None
))
def
augment_field
(
self
,
field
,
field_name
,
key
,
value
):
# This horrible stuff is to manage serializers rendering to HTML
field
.
_errors
=
self
.
_errors
.
get
(
key
)
if
self
.
_errors
else
None
field
.
_name
=
field_name
field
.
_value
=
self
.
init_data
.
get
(
key
)
if
self
.
_errors
and
self
.
init_data
else
value
if
not
field
.
label
:
field
.
label
=
pretty_name
(
key
)
return
field
def
field_to_native
(
self
,
obj
,
field_name
):
"""
Override default so that the serializer can be used as a nested field
...
...
rest_framework/templates/rest_framework/base.html
View file @
c3e370b1
...
...
@@ -151,7 +151,7 @@
{% with form=raw_data_post_form %}
<form
action=
"{{ request.get_full_path }}"
method=
"POST"
class=
"form-horizontal"
>
<fieldset>
{% include "rest_framework/form.html" %}
{% include "rest_framework/
raw_data_
form.html" %}
<div
class=
"form-actions"
>
<button
class=
"btn btn-primary"
title=
"Make a POST request on the {{ name }} resource"
>
POST
</button>
</div>
...
...
@@ -188,7 +188,7 @@
{% with form=raw_data_put_or_patch_form %}
<form
action=
"{{ request.get_full_path }}"
method=
"POST"
class=
"form-horizontal"
>
<fieldset>
{% include "rest_framework/form.html" %}
{% include "rest_framework/
raw_data_
form.html" %}
<div
class=
"form-actions"
>
{% if raw_data_put_form %}
<button
class=
"btn btn-primary js-tooltip"
name=
"{{ api_settings.FORM_METHOD_OVERRIDE }}"
value=
"PUT"
title=
"Make a PUT request on the {{ name }} resource"
>
PUT
</button>
...
...
rest_framework/templates/rest_framework/form.html
View file @
c3e370b1
{% load rest_framework %}
{% csrf_token %}
{{ form.non_field_errors }}
{% for field in form %}
<div
class=
"control-group"
>
<!--{% if field.errors %}error{% endif %}-->
{% for field in form.fields.values %}
{% if not field.read_only %}
<div
class=
"control-group {% if field.errors %}error{% endif %}"
>
{{ field.label_tag|add_class:"control-label" }}
<div
class=
"controls"
>
{{ field }}
<span
class=
"help-block"
>
{{ field.help_text }}
</span>
<!--{{ field.errors|add_class:"help-block" }}-->
{{ field
.widget_html
}}
{% if field.help_text %}
<span
class=
"help-block"
>
{{ field.help_text }}
</span>
{% endif %}
{% for error in field.errors %}
<span
class=
"help-block"
>
{{ error }}
</span>
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
rest_framework/templates/rest_framework/raw_data_form.html
0 → 100644
View file @
c3e370b1
{% load rest_framework %}
{% csrf_token %}
{{ form.non_field_errors }}
{% for field in form %}
<div
class=
"control-group"
>
{{ field.label_tag|add_class:"control-label" }}
<div
class=
"controls"
>
{{ field }}
<span
class=
"help-block"
>
{{ field.help_text }}
</span>
</div>
</div>
{% endfor %}
rest_framework/tests/test_serializer.py
View file @
c3e370b1
...
...
@@ -159,8 +159,7 @@ class BasicTests(TestCase):
expected
=
{
'email'
:
''
,
'content'
:
''
,
'created'
:
None
,
'sub_comment'
:
''
'created'
:
None
}
self
.
assertEqual
(
serializer
.
data
,
expected
)
...
...
rest_framework/tests/test_serializer_empty.py
0 → 100644
View file @
c3e370b1
from
django.test
import
TestCase
from
rest_framework
import
serializers
class
EmptySerializerTestCase
(
TestCase
):
def
test_empty_serializer
(
self
):
class
FooBarSerializer
(
serializers
.
Serializer
):
foo
=
serializers
.
IntegerField
()
bar
=
serializers
.
SerializerMethodField
(
'get_bar'
)
def
get_bar
(
self
,
obj
):
return
'bar'
serializer
=
FooBarSerializer
()
self
.
assertEquals
(
serializer
.
data
,
{
'foo'
:
0
})
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