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
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
93 additions
and
35 deletions
+93
-35
docs/api-guide/permissions.md
+0
-1
rest_framework/generics.py
+2
-1
rest_framework/mixins.py
+2
-2
rest_framework/renderers.py
+49
-22
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 `.
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
[
authentication
]:
authentication.md
[
throttling
]:
throttling.md
...
...
rest_framework/generics.py
View file @
ee36e4ab
...
...
@@ -42,7 +42,8 @@ class SingleObjectBaseView(SingleObjectMixin, BaseView):
Override default to add support for object-level permissions.
"""
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
...
...
rest_framework/mixins.py
View file @
ee36e4ab
...
...
@@ -88,8 +88,8 @@ class MetadataMixin(object):
'parses'
:
[
parser
.
media_type
for
parser
in
self
.
parser_classes
],
}
# TODO: Add 'fields', from serializer info.
#
form = self.get_bound_form
()
# if
form
is not None:
#
serializer = self.get_serializer
()
# if
serializer
is not None:
# field_name_types = {}
# for name, field in form.fields.iteritems():
# 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-
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.
"""
import
copy
import
string
from
django
import
forms
from
django.template
import
RequestContext
,
loader
...
...
@@ -193,7 +194,7 @@ class DocumentingHTMLRenderer(BaseRenderer):
format
=
'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.
...
...
@@ -214,14 +215,31 @@ class DocumentingHTMLRenderer(BaseRenderer):
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.
In the absence on of the Resource having an associated form then
provide a form that can be used to submit arbitrary content.
"""
if
not
hasattr
(
self
.
view
,
'get_serializer'
):
# No serializer, no form.
return
if
not
method
in
view
.
allowed_methods
:
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.
field_mapping
=
dict
([
[
serializers
.
FloatField
.
__name__
,
forms
.
FloatField
],
...
...
@@ -236,20 +254,20 @@ class DocumentingHTMLRenderer(BaseRenderer):
# Creating an on the fly form see: http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python
fields
=
{}
object
,
data
=
None
,
None
if
hasattr
(
self
.
view
,
'object'
):
object
=
self
.
view
.
object
serializer
=
self
.
view
.
get_serializer
(
instance
=
object
)
if
getattr
(
view
,
'object'
,
None
):
object
=
view
.
object
serializer
=
view
.
get_serializer
(
instance
=
object
)
for
k
,
v
in
serializer
.
fields
.
items
():
if
v
.
readonly
:
continue
fields
[
k
]
=
field_mapping
[
v
.
__class__
.
__name__
]()
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
form_instance
=
OnTheFlyForm
(
data
)
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
(Which are typically application/x-www-form-urlencoded)
...
...
@@ -257,7 +275,8 @@ class DocumentingHTMLRenderer(BaseRenderer):
# 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.
if
not
getattr
(
view
.
request
,
'_USE_FORM_OVERLOADING'
,
False
):
if
not
(
api_settings
.
FORM_CONTENT_OVERRIDE
and
api_settings
.
FORM_CONTENTTYPE_OVERRIDE
):
return
None
# NB. http://jacobian.org/writing/dynamic-form-generation/
...
...
@@ -272,11 +291,15 @@ class DocumentingHTMLRenderer(BaseRenderer):
contenttype_choices
=
[(
media_type
,
media_type
)
for
media_type
in
parsed_media_types
]
initial_contenttype
=
parsed_media_types
[
0
]
self
.
fields
[
request
.
_CONTENTTYPE_PARAM
]
=
forms
.
ChoiceField
(
label
=
'Content Type'
,
choices
=
contenttype_choices
,
initial
=
initial_contenttype
)
self
.
fields
[
request
.
_CONTENT_PARAM
]
=
forms
.
CharField
(
label
=
'Content'
,
widget
=
forms
.
Textarea
)
self
.
fields
[
api_settings
.
FORM_CONTENTTYPE_OVERRIDE
]
=
forms
.
ChoiceField
(
label
=
'Content Type'
,
choices
=
contenttype_choices
,
initial
=
initial_contenttype
)
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
self
.
view
.
request
.
_CONTENTTYPE_PARAM
is
None
or
self
.
view
.
request
.
_CONTENT_PARAM
is
None
:
...
...
@@ -310,10 +333,12 @@ class DocumentingHTMLRenderer(BaseRenderer):
request
=
view
.
request
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'
)
post_form_instance
=
self
.
_get_form_instance
(
self
.
view
,
'post'
)
put_form
=
self
.
get_form
(
view
,
'PUT'
,
request
)
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
()
description
=
self
.
get_description
()
...
...
@@ -330,10 +355,12 @@ class DocumentingHTMLRenderer(BaseRenderer):
'name'
:
name
,
'version'
:
VERSION
,
'breadcrumblist'
:
breadcrumb_list
,
'allowed_methods'
:
self
.
view
.
allowed_methods
,
'available_formats'
:
[
renderer
.
format
for
renderer
in
self
.
view
.
renderer_classes
],
'put_form'
:
put_form_instance
,
'post_form'
:
post_form_instance
,
'allowed_methods'
:
view
.
allowed_methods
,
'available_formats'
:
[
renderer
.
format
for
renderer
in
view
.
renderer_classes
],
'put_form'
:
put_form
,
'post_form'
:
post_form
,
'delete_form'
:
delete_form
,
'options_form'
:
options_form
,
'api_settings'
:
api_settings
})
...
...
rest_framework/templates/rest_framework/base.html
View file @
ee36e4ab
...
...
@@ -89,7 +89,7 @@
</form>
{% 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"
>
{% csrf_token %}
<input
type=
"hidden"
name=
"{{ api_settings.FORM_METHOD_OVERRIDE }}"
value=
"OPTIONS"
/>
...
...
@@ -97,7 +97,7 @@
</form>
{% 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"
>
{% csrf_token %}
<input
type=
"hidden"
name=
"{{ api_settings.FORM_METHOD_OVERRIDE }}"
value=
"DELETE"
/>
...
...
@@ -121,7 +121,7 @@
{% 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"
>
<fieldset>
<h2>
POST: {{ name }}
</h2>
...
...
@@ -144,7 +144,7 @@
</form>
{% 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"
>
<fieldset>
<h2>
PUT: {{ name }}
</h2>
...
...
rest_framework/tests/renderers.py
View file @
ee36e4ab
...
...
@@ -2,8 +2,9 @@ import re
from
django.conf.urls.defaults
import
patterns
,
url
,
include
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.response
import
Response
from
rest_framework.views
import
APIView
...
...
@@ -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
):
"""
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):
conneg
=
self
.
content_negotiation_class
()
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
():
if
not
permission
.
has_permission
(
request
,
obj
):
self
.
permission_denied
(
request
)
return
False
return
True
def
check_throttles
(
self
,
request
):
"""
...
...
@@ -197,7 +198,8 @@ class APIView(View):
Runs anything that needs to occur prior to calling the method handlers.
"""
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
.
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