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
ee36e4ab
Commit
ee36e4ab
authored
Sep 27, 2012
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Only display forms when user has permissions. #159
parent
4d906938
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
92 additions
and
34 deletions
+92
-34
docs/api-guide/permissions.md
+0
-1
rest_framework/generics.py
+2
-1
rest_framework/mixins.py
+2
-2
rest_framework/renderers.py
+48
-21
rest_framework/templates/rest_framework/base.html
+4
-4
rest_framework/tests/renderers.py
+30
-1
rest_framework/views.py
+6
-4
No files found.
docs/api-guide/permissions.md
View file @
ee36e4ab
...
@@ -92,7 +92,6 @@ To implement a custom permission, override `BasePermission` and implement the `.
...
@@ -92,7 +92,6 @@ To implement a custom permission, override `BasePermission` and implement the `.
The method should return
`True`
if the request should be granted access, and
`False`
otherwise.
The method should return
`True`
if the request should be granted access, and
`False`
otherwise.
[
cite
]:
https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html
[
cite
]:
https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html
[
authentication
]:
authentication.md
[
authentication
]:
authentication.md
[
throttling
]:
throttling.md
[
throttling
]:
throttling.md
...
...
rest_framework/generics.py
View file @
ee36e4ab
...
@@ -42,7 +42,8 @@ class SingleObjectBaseView(SingleObjectMixin, BaseView):
...
@@ -42,7 +42,8 @@ class SingleObjectBaseView(SingleObjectMixin, BaseView):
Override default to add support for object-level permissions.
Override default to add support for object-level permissions.
"""
"""
obj
=
super
(
SingleObjectBaseView
,
self
)
.
get_object
()
obj
=
super
(
SingleObjectBaseView
,
self
)
.
get_object
()
self
.
check_permissions
(
self
.
request
,
obj
)
if
not
self
.
has_permission
(
self
.
request
,
obj
):
self
.
permission_denied
(
self
.
request
)
return
obj
return
obj
...
...
rest_framework/mixins.py
View file @
ee36e4ab
...
@@ -88,8 +88,8 @@ class MetadataMixin(object):
...
@@ -88,8 +88,8 @@ class MetadataMixin(object):
'parses'
:
[
parser
.
media_type
for
parser
in
self
.
parser_classes
],
'parses'
:
[
parser
.
media_type
for
parser
in
self
.
parser_classes
],
}
}
# TODO: Add 'fields', from serializer info.
# TODO: Add 'fields', from serializer info.
#
form = self.get_bound_form
()
#
serializer = self.get_serializer
()
# if
form
is not None:
# if
serializer
is not None:
# field_name_types = {}
# field_name_types = {}
# for name, field in form.fields.iteritems():
# for name, field in form.fields.iteritems():
# field_name_types[name] = field.__class__.__name__
# field_name_types[name] = field.__class__.__name__
...
...
rest_framework/renderers.py
View file @
ee36e4ab
...
@@ -5,6 +5,7 @@ Django REST framework also provides HTML and PlainText renderers that help self-
...
@@ -5,6 +5,7 @@ Django REST framework also provides HTML and PlainText renderers that help self-
by serializing the output along with documentation regarding the View, output status and headers,
by serializing the output along with documentation regarding the View, output status and headers,
and providing forms and links depending on the allowed methods, renderers and parsers on the View.
and providing forms and links depending on the allowed methods, renderers and parsers on the View.
"""
"""
import
copy
import
string
import
string
from
django
import
forms
from
django
import
forms
from
django.template
import
RequestContext
,
loader
from
django.template
import
RequestContext
,
loader
...
@@ -193,7 +194,7 @@ class DocumentingHTMLRenderer(BaseRenderer):
...
@@ -193,7 +194,7 @@ class DocumentingHTMLRenderer(BaseRenderer):
format
=
'html'
format
=
'html'
template
=
'rest_framework/api.html'
template
=
'rest_framework/api.html'
def
_
get_content
(
self
,
view
,
request
,
obj
,
media_type
):
def
get_content
(
self
,
view
,
request
,
obj
,
media_type
):
"""
"""
Get the content as if it had been rendered by a non-documenting renderer.
Get the content as if it had been rendered by a non-documenting renderer.
...
@@ -214,14 +215,31 @@ class DocumentingHTMLRenderer(BaseRenderer):
...
@@ -214,14 +215,31 @@ class DocumentingHTMLRenderer(BaseRenderer):
return
content
return
content
def
_get_form_instance
(
self
,
view
,
method
):
def
get_form
(
self
,
view
,
method
,
request
):
"""
"""
Get a form, possibly bound to either the input or output data.
Get a form, possibly bound to either the input or output data.
In the absence on of the Resource having an associated form then
In the absence on of the Resource having an associated form then
provide a form that can be used to submit arbitrary content.
provide a form that can be used to submit arbitrary content.
"""
"""
if
not
hasattr
(
self
.
view
,
'get_serializer'
):
# No serializer, no form.
if
not
method
in
view
.
allowed_methods
:
return
return
# Not a valid method
if
not
api_settings
.
FORM_METHOD_OVERRIDE
:
return
# Cannot use form overloading
temp
=
request
.
_method
request
.
_method
=
method
.
upper
()
if
not
view
.
has_permission
(
request
):
request
.
_method
=
temp
return
# Don't have permission
request
.
_method
=
temp
if
method
==
'DELETE'
or
method
==
'OPTIONS'
:
return
True
# Don't actually need to return a form
if
not
getattr
(
view
,
'get_serializer'
,
None
):
return
self
.
get_generic_content_form
(
view
)
# We need to map our Fields to Django's Fields.
# We need to map our Fields to Django's Fields.
field_mapping
=
dict
([
field_mapping
=
dict
([
[
serializers
.
FloatField
.
__name__
,
forms
.
FloatField
],
[
serializers
.
FloatField
.
__name__
,
forms
.
FloatField
],
...
@@ -236,20 +254,20 @@ class DocumentingHTMLRenderer(BaseRenderer):
...
@@ -236,20 +254,20 @@ class DocumentingHTMLRenderer(BaseRenderer):
# 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
fields
=
{}
fields
=
{}
object
,
data
=
None
,
None
object
,
data
=
None
,
None
if
hasattr
(
self
.
view
,
'object'
):
if
getattr
(
view
,
'object'
,
None
):
object
=
self
.
view
.
object
object
=
view
.
object
serializer
=
self
.
view
.
get_serializer
(
instance
=
object
)
serializer
=
view
.
get_serializer
(
instance
=
object
)
for
k
,
v
in
serializer
.
fields
.
items
():
for
k
,
v
in
serializer
.
fields
.
items
():
if
v
.
readonly
:
if
v
.
readonly
:
continue
continue
fields
[
k
]
=
field_mapping
[
v
.
__class__
.
__name__
]()
fields
[
k
]
=
field_mapping
[
v
.
__class__
.
__name__
]()
OnTheFlyForm
=
type
(
"OnTheFlyForm"
,
(
forms
.
Form
,),
fields
)
OnTheFlyForm
=
type
(
"OnTheFlyForm"
,
(
forms
.
Form
,),
fields
)
if
object
and
not
self
.
view
.
request
.
method
==
'DELETE'
:
# Don't fill in the form when the object is deleted
if
object
and
not
view
.
request
.
method
==
'DELETE'
:
# Don't fill in the form when the object is deleted
data
=
serializer
.
data
data
=
serializer
.
data
form_instance
=
OnTheFlyForm
(
data
)
form_instance
=
OnTheFlyForm
(
data
)
return
form_instance
return
form_instance
def
_
get_generic_content_form
(
self
,
view
):
def
get_generic_content_form
(
self
,
view
):
"""
"""
Returns a form that allows for arbitrary content types to be tunneled via standard HTML forms
Returns a form that allows for arbitrary content types to be tunneled via standard HTML forms
(Which are typically application/x-www-form-urlencoded)
(Which are typically application/x-www-form-urlencoded)
...
@@ -257,7 +275,8 @@ class DocumentingHTMLRenderer(BaseRenderer):
...
@@ -257,7 +275,8 @@ class DocumentingHTMLRenderer(BaseRenderer):
# If we're not using content overloading there's no point in supplying a generic form,
# If we're not using content overloading there's no point in supplying a generic form,
# as the view won't treat the form's value as the content of the request.
# as the view won't treat the form's value as the content of the request.
if
not
getattr
(
view
.
request
,
'_USE_FORM_OVERLOADING'
,
False
):
if
not
(
api_settings
.
FORM_CONTENT_OVERRIDE
and
api_settings
.
FORM_CONTENTTYPE_OVERRIDE
):
return
None
return
None
# NB. http://jacobian.org/writing/dynamic-form-generation/
# NB. http://jacobian.org/writing/dynamic-form-generation/
...
@@ -272,11 +291,15 @@ class DocumentingHTMLRenderer(BaseRenderer):
...
@@ -272,11 +291,15 @@ class DocumentingHTMLRenderer(BaseRenderer):
contenttype_choices
=
[(
media_type
,
media_type
)
for
media_type
in
parsed_media_types
]
contenttype_choices
=
[(
media_type
,
media_type
)
for
media_type
in
parsed_media_types
]
initial_contenttype
=
parsed_media_types
[
0
]
initial_contenttype
=
parsed_media_types
[
0
]
self
.
fields
[
request
.
_CONTENTTYPE_PARAM
]
=
forms
.
ChoiceField
(
label
=
'Content Type'
,
self
.
fields
[
api_settings
.
FORM_CONTENTTYPE_OVERRIDE
]
=
forms
.
ChoiceField
(
label
=
'Content Type'
,
choices
=
contenttype_choices
,
choices
=
contenttype_choices
,
initial
=
initial_contenttype
)
initial
=
initial_contenttype
self
.
fields
[
request
.
_CONTENT_PARAM
]
=
forms
.
CharField
(
label
=
'Content'
,
)
widget
=
forms
.
Textarea
)
self
.
fields
[
api_settings
.
FORM_CONTENT_OVERRIDE
]
=
forms
.
CharField
(
label
=
'Content'
,
widget
=
forms
.
Textarea
)
# If either of these reserved parameters are turned off then content tunneling is not possible
# If either of these reserved parameters are turned off then content tunneling is not possible
if
self
.
view
.
request
.
_CONTENTTYPE_PARAM
is
None
or
self
.
view
.
request
.
_CONTENT_PARAM
is
None
:
if
self
.
view
.
request
.
_CONTENTTYPE_PARAM
is
None
or
self
.
view
.
request
.
_CONTENT_PARAM
is
None
:
...
@@ -310,10 +333,12 @@ class DocumentingHTMLRenderer(BaseRenderer):
...
@@ -310,10 +333,12 @@ class DocumentingHTMLRenderer(BaseRenderer):
request
=
view
.
request
request
=
view
.
request
response
=
view
.
response
response
=
view
.
response
content
=
self
.
_
get_content
(
view
,
request
,
obj
,
media_type
)
content
=
self
.
get_content
(
view
,
request
,
obj
,
media_type
)
put_form_instance
=
self
.
_get_form_instance
(
self
.
view
,
'put'
)
put_form
=
self
.
get_form
(
view
,
'PUT'
,
request
)
post_form_instance
=
self
.
_get_form_instance
(
self
.
view
,
'post'
)
post_form
=
self
.
get_form
(
view
,
'POST'
,
request
)
delete_form
=
self
.
get_form
(
view
,
'DELETE'
,
request
)
options_form
=
self
.
get_form
(
view
,
'OPTIONS'
,
request
)
name
=
self
.
get_name
()
name
=
self
.
get_name
()
description
=
self
.
get_description
()
description
=
self
.
get_description
()
...
@@ -330,10 +355,12 @@ class DocumentingHTMLRenderer(BaseRenderer):
...
@@ -330,10 +355,12 @@ class DocumentingHTMLRenderer(BaseRenderer):
'name'
:
name
,
'name'
:
name
,
'version'
:
VERSION
,
'version'
:
VERSION
,
'breadcrumblist'
:
breadcrumb_list
,
'breadcrumblist'
:
breadcrumb_list
,
'allowed_methods'
:
self
.
view
.
allowed_methods
,
'allowed_methods'
:
view
.
allowed_methods
,
'available_formats'
:
[
renderer
.
format
for
renderer
in
self
.
view
.
renderer_classes
],
'available_formats'
:
[
renderer
.
format
for
renderer
in
view
.
renderer_classes
],
'put_form'
:
put_form_instance
,
'put_form'
:
put_form
,
'post_form'
:
post_form_instance
,
'post_form'
:
post_form
,
'delete_form'
:
delete_form
,
'options_form'
:
options_form
,
'api_settings'
:
api_settings
'api_settings'
:
api_settings
})
})
...
...
rest_framework/templates/rest_framework/base.html
View file @
ee36e4ab
...
@@ -89,7 +89,7 @@
...
@@ -89,7 +89,7 @@
</form>
</form>
{% endif %}
{% endif %}
{% if
'OPTIONS' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE
%}
{% if
options_form
%}
<form
class=
"button-form"
action=
"{{ request.get_full_path }}"
method=
"POST"
class=
"pull-right"
>
<form
class=
"button-form"
action=
"{{ request.get_full_path }}"
method=
"POST"
class=
"pull-right"
>
{% csrf_token %}
{% csrf_token %}
<input
type=
"hidden"
name=
"{{ api_settings.FORM_METHOD_OVERRIDE }}"
value=
"OPTIONS"
/>
<input
type=
"hidden"
name=
"{{ api_settings.FORM_METHOD_OVERRIDE }}"
value=
"OPTIONS"
/>
...
@@ -97,7 +97,7 @@
...
@@ -97,7 +97,7 @@
</form>
</form>
{% endif %}
{% endif %}
{% if
'DELETE' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE
%}
{% if
delete_form
%}
<form
class=
"button-form"
action=
"{{ request.get_full_path }}"
method=
"POST"
class=
"pull-right"
>
<form
class=
"button-form"
action=
"{{ request.get_full_path }}"
method=
"POST"
class=
"pull-right"
>
{% csrf_token %}
{% csrf_token %}
<input
type=
"hidden"
name=
"{{ api_settings.FORM_METHOD_OVERRIDE }}"
value=
"DELETE"
/>
<input
type=
"hidden"
name=
"{{ api_settings.FORM_METHOD_OVERRIDE }}"
value=
"DELETE"
/>
...
@@ -121,7 +121,7 @@
...
@@ -121,7 +121,7 @@
{% if response.status_code != 403 %}
{% if response.status_code != 403 %}
{% if
'POST' in allowed_methods
%}
{% if
post_form
%}
<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>
...
@@ -144,7 +144,7 @@
...
@@ -144,7 +144,7 @@
</form>
</form>
{% endif %}
{% endif %}
{% if
'PUT' in allowed_methods and api_settings.FORM_METHOD_OVERRIDE
%}
{% if
put_form
%}
<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>
...
...
rest_framework/tests/renderers.py
View file @
ee36e4ab
...
@@ -2,8 +2,9 @@ import re
...
@@ -2,8 +2,9 @@ import re
from
django.conf.urls.defaults
import
patterns
,
url
,
include
from
django.conf.urls.defaults
import
patterns
,
url
,
include
from
django.test
import
TestCase
from
django.test
import
TestCase
from
django.test.client
import
RequestFactory
from
rest_framework
import
status
from
rest_framework
import
status
,
permissions
from
rest_framework.compat
import
yaml
from
rest_framework.compat
import
yaml
from
rest_framework.response
import
Response
from
rest_framework.response
import
Response
from
rest_framework.views
import
APIView
from
rest_framework.views
import
APIView
...
@@ -89,6 +90,34 @@ urlpatterns = patterns('',
...
@@ -89,6 +90,34 @@ urlpatterns = patterns('',
)
)
class
POSTDeniedPermission
(
permissions
.
BasePermission
):
def
has_permission
(
self
,
request
,
obj
=
None
):
return
request
.
method
!=
'POST'
class
POSTDeniedView
(
APIView
):
renderer_classes
=
(
DocumentingHTMLRenderer
,)
permission_classes
=
(
POSTDeniedPermission
,)
def
get
(
self
,
request
):
return
Response
()
def
post
(
self
,
request
):
return
Response
()
def
put
(
self
,
request
):
return
Response
()
class
DocumentingRendererTests
(
TestCase
):
def
test_only_permitted_forms_are_displayed
(
self
):
view
=
POSTDeniedView
.
as_view
()
request
=
RequestFactory
()
.
get
(
'/'
)
response
=
view
(
request
)
.
render
()
self
.
assertNotContains
(
response
,
'>POST<'
)
self
.
assertContains
(
response
,
'>PUT<'
)
class
RendererEndToEndTests
(
TestCase
):
class
RendererEndToEndTests
(
TestCase
):
"""
"""
End-to-end testing of renderers using an RendererMixin on a generic view.
End-to-end testing of renderers using an RendererMixin on a generic view.
...
...
rest_framework/views.py
View file @
ee36e4ab
...
@@ -169,13 +169,14 @@ class APIView(View):
...
@@ -169,13 +169,14 @@ class APIView(View):
conneg
=
self
.
content_negotiation_class
()
conneg
=
self
.
content_negotiation_class
()
return
conneg
.
negotiate
(
request
,
renderers
,
self
.
format
,
force
)
return
conneg
.
negotiate
(
request
,
renderers
,
self
.
format
,
force
)
def
check_permissions
(
self
,
request
,
obj
=
None
):
def
has_permission
(
self
,
request
,
obj
=
None
):
"""
"""
Check if
request should be permitted.
Return `True` if the
request should be permitted.
"""
"""
for
permission
in
self
.
get_permissions
():
for
permission
in
self
.
get_permissions
():
if
not
permission
.
has_permission
(
request
,
obj
):
if
not
permission
.
has_permission
(
request
,
obj
):
self
.
permission_denied
(
request
)
return
False
return
True
def
check_throttles
(
self
,
request
):
def
check_throttles
(
self
,
request
):
"""
"""
...
@@ -197,7 +198,8 @@ class APIView(View):
...
@@ -197,7 +198,8 @@ class APIView(View):
Runs anything that needs to occur prior to calling the method handlers.
Runs anything that needs to occur prior to calling the method handlers.
"""
"""
self
.
format
=
self
.
get_format_suffix
(
**
kwargs
)
self
.
format
=
self
.
get_format_suffix
(
**
kwargs
)
self
.
check_permissions
(
request
)
if
not
self
.
has_permission
(
request
):
self
.
permission_denied
(
request
)
self
.
check_throttles
(
request
)
self
.
check_throttles
(
request
)
self
.
renderer
,
self
.
accepted_media_type
=
self
.
perform_content_negotiation
(
request
)
self
.
renderer
,
self
.
accepted_media_type
=
self
.
perform_content_negotiation
(
request
)
...
...
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