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
279fa0d3
Commit
279fa0d3
authored
Jan 31, 2012
by
Sébastien Piquemal
Browse files
Options
Browse Files
Download
Plain Diff
merge
parents
152c385f
b2fcfffb
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
280 additions
and
228 deletions
+280
-228
AUTHORS
+2
-0
CHANGELOG.rst
+40
-0
README.rst
+1
-1
djangorestframework/__init__.py
+1
-1
djangorestframework/authentication.py
+2
-15
djangorestframework/mixins.py
+0
-5
djangorestframework/permissions.py
+1
-1
djangorestframework/renderers.py
+17
-12
djangorestframework/runtests/settings.py
+6
-0
djangorestframework/static/css/djangorestframework.css
+0
-0
djangorestframework/status.py
+0
-1
djangorestframework/templates/renderer.html
+7
-18
djangorestframework/templates/renderer.txt
+2
-2
djangorestframework/templatetags/add_query_param.py
+1
-1
djangorestframework/tests/authentication.py
+13
-5
djangorestframework/tests/description.py
+27
-27
djangorestframework/tests/mixins.py
+2
-3
djangorestframework/tests/renderers.py
+10
-1
djangorestframework/tests/validators.py
+21
-26
djangorestframework/utils/breadcrumbs.py
+1
-3
djangorestframework/utils/description.py
+0
-88
djangorestframework/views.py
+96
-4
docs/index.rst
+2
-0
examples/permissionsexample/views.py
+15
-4
examples/requirements-epio.txt
+1
-1
examples/settings.py
+4
-9
examples/urls.py
+2
-0
tox.ini
+6
-0
No files found.
AUTHORS
View file @
279fa0d3
...
...
@@ -27,6 +27,8 @@ Natim <natim>
Sebastian Żurek <sebzur>
Benoit C <dzen>
Chris Pickett <bunchesofdonald>
Ben Timby <btimby>
Michele Lazzeri <michelelazzeri-nextage>
THANKS TO:
...
...
RELEASES
→
CHANGELOG.rst
View file @
279fa0d3
Release Notes
=============
development
-----------
* Saner template variable autoescaping.
* Use `staticfiles` for css files.
- Easier to override. Won't conflict with customised admin styles (eg grappelli)
* Drop implied 'pk' filter if last arg in urlconf is unnamed.
- Too magical. Explict is better than implicit.
* Bugfixes:
- Bug with PerUserThrottling when user contains unicode chars.
0.3.2
-----
* Bugfixes:
* Fix 403 for POST and PUT from the UI with UserLoggedInAuthentication (#115)
* serialize_model method in serializer.py may cause wrong value (#73)
* Fix Error when clicking OPTIONS button (#146)
* And many other fixes
* Remove short status codes
- Zen of Python: "There should be one-- and preferably only one --obvious way to do it."
* get_name, get_description become methods on the view - makes them overridable.
* Improved model mixin API - Hooks for build_query, get_instance_data, get_model, get_queryset, get_ordering
0.3.1
-----
* [not documented]
0.3.0
-----
* JSONP Support
* Bugfixes, including support for latest markdown release
0.2.4
-----
* Fix broken IsAdminUser permission.
* OPTIONS support.
...
...
@@ -11,20 +45,24 @@
* Drop mentions of Blog, BitBucket.
0.2.3
-----
* Fix some throttling bugs.
* ``X-Throttle`` header on throttling.
* Support for nesting resources on related models.
0.2.2
-----
* Throttling support complete.
0.2.1
-----
* Couple of simple bugfixes over 0.2.0
0.2.0
-----
* Big refactoring changes since 0.1.0, ask on the discussion group if anything isn't clear.
The public API has been massively cleaned up. Expect it to be fairly stable from here on in.
...
...
@@ -49,9 +87,11 @@
You can reuse these mixin classes individually without using the ``View`` class.
0.1.1
-----
* Final build before pulling in all the refactoring changes for 0.2, in case anyone needs to hang on to 0.1.
0.1.0
-----
* Initial release.
README.rst
View file @
279fa0d3
...
...
@@ -16,7 +16,7 @@ Full documentation for the project is available at http://django-rest-framework.
Issue tracking is on `GitHub <https://github.com/tomchristie/django-rest-framework/issues>`_.
General questions should be taken to the `discussion group <http://groups.google.com/group/django-rest-framework>`_.
We also have a `Jenkins service <http://jenkins.tibold.nl/job/djangorestframework/>`_ which runs our test suite.
We also have a `Jenkins service <http://jenkins.tibold.nl/job/djangorestframework
1
/>`_ which runs our test suite.
Requirements:
...
...
djangorestframework/__init__.py
View file @
279fa0d3
__version__
=
'0.3.
2
-dev'
__version__
=
'0.3.
3
-dev'
VERSION
=
__version__
# synonym
djangorestframework/authentication.py
View file @
279fa0d3
...
...
@@ -87,25 +87,12 @@ class UserLoggedInAuthentication(BaseAuthentication):
Returns a :obj:`User` if the request session currently has a logged in user.
Otherwise returns :const:`None`.
"""
# TODO: Might be cleaner to switch this back to using request.POST,
# and let FormParser/MultiPartParser deal with the consequences.
request
.
DATA
# Make sure our generic parsing runs first
if
getattr
(
request
,
'user'
,
None
)
and
request
.
user
.
is_active
:
# Enforce CSRF validation for session based authentication.
# Temporarily replace request.POST with .DATA, to use our generic parsing.
# If DATA is not dict-like, use an empty dict.
if
request
.
method
.
upper
()
==
'POST'
:
if
hasattr
(
request
.
DATA
,
'get'
):
request
.
_post
=
request
.
DATA
else
:
request
.
_post
=
{}
resp
=
CsrfViewMiddleware
()
.
process_view
(
request
,
None
,
(),
{})
# Replace request.POST
if
request
.
method
.
upper
()
==
'POST'
:
del
(
request
.
_post
)
if
resp
is
None
:
# csrf passed
return
request
.
user
return
None
...
...
djangorestframework/mixins.py
View file @
279fa0d3
...
...
@@ -384,11 +384,6 @@ class ModelMixin(object):
if
BaseRenderer
.
_FORMAT_QUERY_PARAM
in
tmp
:
del
tmp
[
BaseRenderer
.
_FORMAT_QUERY_PARAM
]
if
args
:
# If we have any no kwargs then assume the last arg represents the
# primrary key. Otherwise assume the kwargs uniquely identify the
# model.
tmp
.
update
({
'pk'
:
args
[
-
1
]})
return
Q
(
**
tmp
)
def
get_instance_data
(
self
,
model
,
content
,
**
kwargs
):
...
...
djangorestframework/permissions.py
View file @
279fa0d3
...
...
@@ -188,7 +188,7 @@ class PerUserThrottling(BaseThrottle):
def
get_cache_key
(
self
):
if
self
.
auth
.
is_authenticated
():
ident
=
s
tr
(
self
.
auth
)
ident
=
s
elf
.
auth
.
id
else
:
ident
=
self
.
view
.
request
.
META
.
get
(
'REMOTE_ADDR'
,
None
)
return
'throttle_user_
%
s'
%
ident
...
...
djangorestframework/renderers.py
View file @
279fa0d3
...
...
@@ -12,10 +12,9 @@ from django.template import RequestContext, loader
from
django.utils
import
simplejson
as
json
from
djangorestframework.compat
import
apply_markdown
,
yaml
from
djangorestframework.compat
import
yaml
from
djangorestframework.utils
import
dict2xml
,
url_resolves
from
djangorestframework.utils.breadcrumbs
import
get_breadcrumbs
from
djangorestframework.utils.description
import
get_name
,
get_description
from
djangorestframework.utils.mediatypes
import
get_media_type_params
,
add_media_type_param
,
media_type_matches
from
djangorestframework
import
VERSION
...
...
@@ -296,6 +295,20 @@ class DocumentingTemplateRenderer(BaseRenderer):
# Okey doke, let's do it
return
GenericContentForm
(
view
.
request
)
def
get_name
(
self
):
try
:
return
self
.
view
.
get_name
()
except
AttributeError
:
return
self
.
view
.
__doc__
def
get_description
(
self
,
html
=
None
):
if
html
is
None
:
html
=
bool
(
'html'
in
self
.
format
)
try
:
return
self
.
view
.
get_description
(
html
)
except
AttributeError
:
return
self
.
view
.
__doc__
def
render
(
self
,
obj
=
None
,
media_type
=
None
):
"""
Renders *obj* using the :attr:`template` set on the class.
...
...
@@ -316,15 +329,8 @@ class DocumentingTemplateRenderer(BaseRenderer):
login_url
=
None
logout_url
=
None
name
=
get_name
(
self
.
view
)
description
=
get_description
(
self
.
view
)
markeddown
=
None
if
apply_markdown
:
try
:
markeddown
=
apply_markdown
(
description
)
except
AttributeError
:
markeddown
=
None
name
=
self
.
get_name
()
description
=
self
.
get_description
()
breadcrumb_list
=
get_breadcrumbs
(
self
.
view
.
request
.
path
)
...
...
@@ -337,7 +343,6 @@ class DocumentingTemplateRenderer(BaseRenderer):
'description'
:
description
,
'name'
:
name
,
'version'
:
VERSION
,
'markeddown'
:
markeddown
,
'breadcrumblist'
:
breadcrumb_list
,
'available_formats'
:
self
.
view
.
_rendered_formats
,
'put_form'
:
put_form_instance
,
...
...
djangorestframework/runtests/settings.py
View file @
279fa0d3
...
...
@@ -97,6 +97,12 @@ INSTALLED_APPS = (
'djangorestframework'
,
)
import
django
if
django
.
VERSION
<
(
1
,
3
):
INSTALLED_APPS
+=
(
'staticfiles'
,)
# OAuth support is optional, so we only test oauth if it's installed.
try
:
import
oauth_provider
...
...
djangorestframework/static/css/djangorestframework.css
0 → 100644
View file @
279fa0d3
This diff is collapsed.
Click to expand it.
djangorestframework/status.py
View file @
279fa0d3
...
...
@@ -5,7 +5,6 @@ See RFC 2616 - Sec 10: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
Also see django.core.handlers.wsgi.STATUS_CODE_TEXT
"""
# Verbose format
HTTP_100_CONTINUE
=
100
HTTP_101_SWITCHING_PROTOCOLS
=
101
HTTP_200_OK
=
200
...
...
djangorestframework/templates/renderer.html
View file @
279fa0d3
{% load urlize_quoted_links %}{% load add_query_param %}
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
{% load urlize_quoted_links %}
{% load add_query_param %}
{% load static %}
<html
xmlns=
"http://www.w3.org/1999/xhtml"
>
<head>
<style>
/* Override some of the Django admin styling */
#site-name
a
{
color
:
#F4F379
!important
;}
.errorlist
{
display
:
inline
!important
}
.errorlist
li
{
display
:
inline
!important
;
background
:
white
!important
;
color
:
black
!important
;
border
:
0
!important
;}
/* Custom styles */
.version
{
font-size
:
8px
;}
</style>
{% if ADMIN_MEDIA_PREFIX %}
<link
rel=
"stylesheet"
type=
"text/css"
href=
'{{ADMIN_MEDIA_PREFIX}}css/base.css'
/>
<link
rel=
"stylesheet"
type=
"text/css"
href=
'{{ADMIN_MEDIA_PREFIX}}css/forms.css'
/>
{% else %}
<link
rel=
"stylesheet"
type=
"text/css"
href=
'{{STATIC_URL}}admin/css/base.css'
/>
<link
rel=
"stylesheet"
type=
"text/css"
href=
'{{STATIC_URL}}admin/css/forms.css'
/>
{% endif %}
<link
rel=
"stylesheet"
type=
"text/css"
href=
'{% get_static_prefix %}css/djangorestframework.css'
/>
<title>
Django REST framework - {{ name }}
</title>
</head>
<body>
...
...
@@ -34,7 +23,7 @@
<div
class=
"breadcrumbs"
>
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
<a
href=
"{{
breadcrumb_url}}"
>
{{breadcrumb_name
}}
</a>
{% if not forloop.last %}
›
{% endif %}
<a
href=
"{{
breadcrumb_url }}"
>
{{ breadcrumb_name
}}
</a>
{% if not forloop.last %}
›
{% endif %}
{% endfor %}
</div>
...
...
@@ -50,7 +39,7 @@
<div
class=
'content-main'
>
<h1>
{{ name }}
</h1>
<p>
{
% if markeddown %}{% autoescape off %}{{ markeddown }}{% endautoescape %}{% else %}{{ description|linebreaksbr }}{% endif %
}
</p>
<p>
{
{ description }
}
</p>
<div
class=
'module'
>
<pre><b>
{{ response.status }} {{ response.status_text }}
</b>
{% autoescape off %}
{% for key, val in response.headers.items %}
<b>
{{ key }}:
</b>
{{ val|urlize_quoted_links }}
...
...
djangorestframework/templates/renderer.txt
View file @
279fa0d3
{{ name }}
{
% autoescape off %}{
{ name }}
{{ description }}
{% autoescape off %}
HTTP/1.0 {{ response.status }} {{ response.status_text }}
HTTP/1.0 {{ response.status }} {{ response.status_text }}
{% for key, val in response.headers.items %}{{ key }}: {{ val }}
{% endfor %}
{{ content }}{% endautoescape %}
djangorestframework/templatetags/add_query_param.py
View file @
279fa0d3
...
...
@@ -5,7 +5,7 @@ register = Library()
def
add_query_param
(
url
,
param
):
(
key
,
sep
,
val
)
=
param
.
partition
(
'='
)
return
unicode
(
URLObject
(
url
)
&
(
key
,
val
))
return
unicode
(
URLObject
.
parse
(
url
)
&
(
key
,
val
))
register
.
filter
(
'add_query_param'
,
add_query_param
)
djangorestframework/tests/authentication.py
View file @
279fa0d3
...
...
@@ -11,7 +11,7 @@ import base64
class
MockView
(
View
):
permissions
=
(
permissions
.
IsAuthenticated
,
)
permissions
=
(
permissions
.
IsAuthenticated
,
)
def
post
(
self
,
request
):
return
{
'a'
:
1
,
'b'
:
2
,
'c'
:
3
}
...
...
@@ -74,24 +74,32 @@ class SessionAuthTests(TestCase):
self
.
csrf_client
.
logout
()
def
test_post_form_session_auth_failing_csrf
(
self
):
"""Ensure POSTing form over session authentication without CSRF token fails."""
"""
Ensure POSTing form over session authentication without CSRF token fails.
"""
self
.
csrf_client
.
login
(
username
=
self
.
username
,
password
=
self
.
password
)
response
=
self
.
csrf_client
.
post
(
'/'
,
{
'example'
:
'example'
})
self
.
assertEqual
(
response
.
status_code
,
403
)
def
test_post_form_session_auth_passing
(
self
):
"""Ensure POSTing form over session authentication with logged in user and CSRF token passes."""
"""
Ensure POSTing form over session authentication with logged in user and CSRF token passes.
"""
self
.
non_csrf_client
.
login
(
username
=
self
.
username
,
password
=
self
.
password
)
response
=
self
.
non_csrf_client
.
post
(
'/'
,
{
'example'
:
'example'
})
self
.
assertEqual
(
response
.
status_code
,
200
)
def
test_put_form_session_auth_passing
(
self
):
"""Ensure PUTting form over session authentication with logged in user and CSRF token passes."""
"""
Ensure PUTting form over session authentication with logged in user and CSRF token passes.
"""
self
.
non_csrf_client
.
login
(
username
=
self
.
username
,
password
=
self
.
password
)
response
=
self
.
non_csrf_client
.
put
(
'/'
,
{
'example'
:
'example'
})
self
.
assertEqual
(
response
.
status_code
,
200
)
def
test_post_form_session_auth_failing
(
self
):
"""Ensure POSTing form over session authentication without logged in user fails."""
"""
Ensure POSTing form over session authentication without logged in user fails.
"""
response
=
self
.
csrf_client
.
post
(
'/'
,
{
'example'
:
'example'
})
self
.
assertEqual
(
response
.
status_code
,
403
)
djangorestframework/tests/description.py
View file @
279fa0d3
from
django.test
import
TestCase
from
djangorestframework.views
import
View
from
djangorestframework.compat
import
apply_markdown
from
djangorestframework.utils.description
import
get_name
,
get_description
# We check that docstrings get nicely un-indented.
DESCRIPTION
=
"""an example docstring
...
...
@@ -51,15 +50,15 @@ class TestViewNamesAndDescriptions(TestCase):
"""Ensure Resource names are based on the classname by default."""
class
MockView
(
View
):
pass
self
.
assertEquals
(
get_name
(
MockView
()
),
'Mock'
)
self
.
assertEquals
(
MockView
()
.
get_name
(
),
'Mock'
)
# This has been turned off now.
#def test_resource_name_can_be_set_explicitly(self):
# """Ensure Resource names can be set using the 'name' class attribute."""
# example = 'Some Other Name'
# class MockView(View
):
# name =
example
# self.assertEquals(get_name(MockView()
), example)
def
test_resource_name_can_be_set_explicitly
(
self
):
"""Ensure Resource names can be set using the 'get_name' method."""
example
=
'Some Other Name'
class
MockView
(
View
):
def
get_name
(
self
):
return
example
self
.
assertEquals
(
MockView
()
.
get_name
(
),
example
)
def
test_resource_description_uses_docstring_by_default
(
self
):
"""Ensure Resource names are based on the docstring by default."""
...
...
@@ -79,29 +78,30 @@ class TestViewNamesAndDescriptions(TestCase):
# hash style header #"""
self
.
assertEquals
(
get_description
(
MockView
()),
DESCRIPTION
)
# This has been turned off now
#def test_resource_description_can_be_set_explicitly(self):
# """Ensure Resource descriptions can be set using the 'description' class attribute."""
# example = 'Some other description'
# class MockView(View):
# """docstring"""
# description = example
# self.assertEquals(get_description(MockView()), example)
self
.
assertEquals
(
MockView
()
.
get_description
(),
DESCRIPTION
)
#def test_resource_description_does_not_require_docstring(self):
# """Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'description' class attribute."""
# example = 'Some other description'
# class MockView(View):
# description = example
# self.assertEquals(get_description(MockView()), example)
def
test_resource_description_can_be_set_explicitly
(
self
):
"""Ensure Resource descriptions can be set using the 'get_description' method."""
example
=
'Some other description'
class
MockView
(
View
):
"""docstring"""
def
get_description
(
self
):
return
example
self
.
assertEquals
(
MockView
()
.
get_description
(),
example
)
def
test_resource_description_does_not_require_docstring
(
self
):
"""Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'get_description' method."""
example
=
'Some other description'
class
MockView
(
View
):
def
get_description
(
self
):
return
example
self
.
assertEquals
(
MockView
()
.
get_description
(),
example
)
def
test_resource_description_can_be_empty
(
self
):
"""Ensure that if a resource has no doctring or 'description' class attribute, then it's description is the empty string"""
"""Ensure that if a resource has no doctring or 'description' class attribute, then it's description is the empty string
.
"""
class
MockView
(
View
):
pass
self
.
assertEquals
(
get_description
(
MockView
()
),
''
)
self
.
assertEquals
(
MockView
()
.
get_description
(
),
''
)
def
test_markdown
(
self
):
"""Ensure markdown to HTML works as expected"""
...
...
djangorestframework/tests/mixins.py
View file @
279fa0d3
...
...
@@ -30,7 +30,7 @@ class TestModelRead(TestModelsTestCase):
mixin
=
ReadModelMixin
()
mixin
.
resource
=
GroupResource
response
=
mixin
.
get
(
request
,
group
.
id
)
response
=
mixin
.
get
(
request
,
id
=
group
.
id
)
self
.
assertEquals
(
group
.
name
,
response
.
name
)
def
test_read_404
(
self
):
...
...
@@ -41,8 +41,7 @@ class TestModelRead(TestModelsTestCase):
mixin
=
ReadModelMixin
()
mixin
.
resource
=
GroupResource
with
self
.
assertRaises
(
ErrorResponse
):
response
=
mixin
.
get
(
request
,
12345
)
self
.
assertRaises
(
ErrorResponse
,
mixin
.
get
,
request
,
id
=
12345
)
class
TestModelCreation
(
TestModelsTestCase
):
...
...
djangorestframework/tests/renderers.py
View file @
279fa0d3
import
re
from
django.conf.urls.defaults
import
patterns
,
url
from
django.test
import
TestCase
...
...
@@ -174,6 +176,12 @@ class RendererIntegrationTests(TestCase):
_flat_repr
=
'{"foo": ["bar", "baz"]}'
_indented_repr
=
'{
\n
"foo": [
\n
"bar",
\n
"baz"
\n
]
\n
}'
def
strip_trailing_whitespace
(
content
):
"""
Seems to be some inconsistencies re. trailing whitespace with
different versions of the json lib.
"""
return
re
.
sub
(
' +
\n
'
,
'
\n
'
,
content
)
class
JSONRendererTests
(
TestCase
):
"""
...
...
@@ -187,6 +195,7 @@ class JSONRendererTests(TestCase):
obj
=
{
'foo'
:
[
'bar'
,
'baz'
]}
renderer
=
JSONRenderer
(
None
)
content
=
renderer
.
render
(
obj
,
'application/json'
)
# Fix failing test case which depends on version of JSON library.
self
.
assertEquals
(
content
,
_flat_repr
)
def
test_with_content_type_args
(
self
):
...
...
@@ -196,7 +205,7 @@ class JSONRendererTests(TestCase):
obj
=
{
'foo'
:
[
'bar'
,
'baz'
]}
renderer
=
JSONRenderer
(
None
)
content
=
renderer
.
render
(
obj
,
'application/json; indent=2'
)
self
.
assertEquals
(
content
,
_indented_repr
)
self
.
assertEquals
(
strip_trailing_whitespace
(
content
)
,
_indented_repr
)
def
test_render_and_parse
(
self
):
"""
...
...
djangorestframework/tests/validators.py
View file @
279fa0d3
from
django
import
forms
from
django.db
import
models
from
django.test
import
TestCase
from
djangorestframework.compat
import
RequestFactory
from
djangorestframework.resources
import
Resource
,
FormResource
,
ModelResource
from
djangorestframework.resources
import
FormResource
,
ModelResource
from
djangorestframework.response
import
ErrorResponse
from
djangorestframework.views
import
View
from
djangorestframework.resources
import
Resource
class
TestDisabledValidations
(
TestCase
):
...
...
@@ -22,7 +19,7 @@ class TestDisabledValidations(TestCase):
resource
=
DisabledFormResource
view
=
MockView
()
content
=
{
'qwerty'
:
'uiop'
}
content
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
FormResource
(
view
)
.
validate_request
(
content
,
None
),
content
)
def
test_disabled_form_validator_get_bound_form_returns_none
(
self
):
...
...
@@ -35,10 +32,9 @@ class TestDisabledValidations(TestCase):
resource
=
DisabledFormResource
view
=
MockView
()
content
=
{
'qwerty'
:
'uiop'
}
content
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
FormResource
(
view
)
.
get_bound_form
(
content
),
None
)
def
test_disabled_model_form_validator_returns_content_unchanged
(
self
):
"""If the view's form is None and does not have a Resource with a model set then
ModelFormValidator(view).validate_request(content, None) should just return the content unmodified."""
...
...
@@ -47,8 +43,8 @@ class TestDisabledValidations(TestCase):
resource
=
ModelResource
view
=
DisabledModelFormView
()
content
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
ModelResource
(
view
)
.
get_bound_form
(
content
),
None
)
#
content
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
ModelResource
(
view
)
.
get_bound_form
(
content
),
None
)
def
test_disabled_model_form_validator_get_bound_form_returns_none
(
self
):
"""If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
...
...
@@ -56,9 +52,10 @@ class TestDisabledValidations(TestCase):
resource
=
ModelResource
view
=
DisabledModelFormView
()
content
=
{
'qwerty'
:
'uiop'
}
content
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
ModelResource
(
view
)
.
get_bound_form
(
content
),
None
)
class
TestNonFieldErrors
(
TestCase
):
"""Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
...
...
@@ -72,7 +69,7 @@ class TestNonFieldErrors(TestCase):
def
clean
(
self
):
if
'field1'
in
self
.
cleaned_data
and
'field2'
in
self
.
cleaned_data
:
raise
forms
.
ValidationError
(
self
.
ERROR_TEXT
)
return
self
.
cleaned_data
#pragma: no cover
return
self
.
cleaned_data
class
MockResource
(
FormResource
):
form
=
MockForm
...
...
@@ -87,7 +84,7 @@ class TestNonFieldErrors(TestCase):
except
ErrorResponse
,
exc
:
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'errors'
:
[
MockForm
.
ERROR_TEXT
]})
else
:
self
.
fail
(
'ErrorResponse was not raised'
)
#pragma: no cover
self
.
fail
(
'ErrorResponse was not raised'
)
class
TestFormValidation
(
TestCase
):
...
...
@@ -115,10 +112,9 @@ class TestFormValidation(TestCase):
self
.
MockFormView
=
MockFormView
self
.
MockModelFormView
=
MockModelFormView
def
validation_returns_content_unchanged_if_already_valid_and_clean
(
self
,
validator
):
"""If the content is already valid and clean then validate(content) should just return the content unmodified."""
content
=
{
'qwerty'
:
'uiop'
}
content
=
{
'qwerty'
:
'uiop'
}
self
.
assertEqual
(
validator
.
validate_request
(
content
,
None
),
content
)
def
validation_failure_raises_response_exception
(
self
,
validator
):
...
...
@@ -143,7 +139,9 @@ class TestFormValidation(TestCase):
raise errors on unexpected request data"""
content
=
{
'qwerty'
:
'uiop'
,
'extra'
:
'extra'
}
validator
.
allow_unknown_form_fields
=
True
self
.
assertDictEqual
({
'qwerty'
:
u'uiop'
},
validator
.
validate_request
(
content
,
None
),
"Resource didn't accept unknown fields."
)
self
.
assertEqual
({
'qwerty'
:
u'uiop'
},
validator
.
validate_request
(
content
,
None
),
"Resource didn't accept unknown fields."
)
validator
.
allow_unknown_form_fields
=
False
def
validation_does_not_require_extra_fields_if_explicitly_set
(
self
,
validator
):
...
...
@@ -159,7 +157,7 @@ class TestFormValidation(TestCase):
except
ErrorResponse
,
exc
:
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'field_errors'
:
{
'qwerty'
:
[
'This field is required.'
]}})
else
:
self
.
fail
(
'ResourceException was not raised'
)
#pragma: no cover
self
.
fail
(
'ResourceException was not raised'
)
def
validation_failed_due_to_field_error_returns_appropriate_message
(
self
,
validator
):
"""If validation fails due to a field error, ensure the response contains a single field error"""
...
...
@@ -169,7 +167,7 @@ class TestFormValidation(TestCase):
except
ErrorResponse
,
exc
:
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'field_errors'
:
{
'qwerty'
:
[
'This field is required.'
]}})
else
:
self
.
fail
(
'ResourceException was not raised'
)
#pragma: no cover
self
.
fail
(
'ResourceException was not raised'
)
def
validation_failed_due_to_invalid_field_returns_appropriate_message
(
self
,
validator
):
"""If validation fails due to an invalid field, ensure the response contains a single field error"""
...
...
@@ -179,7 +177,7 @@ class TestFormValidation(TestCase):
except
ErrorResponse
,
exc
:
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'field_errors'
:
{
'extra'
:
[
'This field does not exist.'
]}})
else
:
self
.
fail
(
'ResourceException was not raised'
)
#pragma: no cover
self
.
fail
(
'ResourceException was not raised'
)
def
validation_failed_due_to_multiple_errors_returns_appropriate_message
(
self
,
validator
):
"""If validation for multiple reasons, ensure the response contains each error"""
...
...
@@ -190,7 +188,7 @@ class TestFormValidation(TestCase):
self
.
assertEqual
(
exc
.
response
.
raw_content
,
{
'field_errors'
:
{
'qwerty'
:
[
'This field is required.'
],
'extra'
:
[
'This field does not exist.'
]}})
else
:
self
.
fail
(
'ResourceException was not raised'
)
#pragma: no cover
self
.
fail
(
'ResourceException was not raised'
)
# Tests on FormResource
...
...
@@ -294,22 +292,21 @@ class TestModelFormValidator(TestCase):
self
.
validator
=
MockResource
(
MockView
)
def
test_property_fields_are_allowed_on_model_forms
(
self
):
"""Validation on ModelForms may include property fields that exist on the Model to be included in the input."""
content
=
{
'qwerty'
:
'example'
,
'uiop'
:
'example'
,
'readonly'
:
'read only'
}
content
=
{
'qwerty'
:
'example'
,
'uiop'
:
'example'
,
'readonly'
:
'read only'
}
self
.
assertEqual
(
self
.
validator
.
validate_request
(
content
,
None
),
content
)
def
test_property_fields_are_not_required_on_model_forms
(
self
):
"""Validation on ModelForms does not require property fields that exist on the Model to be included in the input."""
content
=
{
'qwerty'
:
'example'
,
'uiop'
:
'example'
}
content
=
{
'qwerty'
:
'example'
,
'uiop'
:
'example'
}
self
.
assertEqual
(
self
.
validator
.
validate_request
(
content
,
None
),
content
)
def
test_extra_fields_not_allowed_on_model_forms
(
self
):
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
broken clients more easily (eg submitting content with a misnamed field)"""
content
=
{
'qwerty'
:
'example'
,
'uiop'
:
'example'
,
'readonly'
:
'read only'
,
'extra'
:
'extra'
}
content
=
{
'qwerty'
:
'example'
,
'uiop'
:
'example'
,
'readonly'
:
'read only'
,
'extra'
:
'extra'
}
self
.
assertRaises
(
ErrorResponse
,
self
.
validator
.
validate_request
,
content
,
None
)
def
test_validate_requires_fields_on_model_forms
(
self
):
...
...
@@ -321,10 +318,8 @@ class TestModelFormValidator(TestCase):
def
test_validate_does_not_require_blankable_fields_on_model_forms
(
self
):
"""Test standard ModelForm validation behaviour - fields with blank=True are not required."""
content
=
{
'qwerty'
:
'example'
,
'readonly'
:
'read only'
}
content
=
{
'qwerty'
:
'example'
,
'readonly'
:
'read only'
}
self
.
validator
.
validate_request
(
content
,
None
)
def
test_model_form_validator_uses_model_forms
(
self
):
self
.
assertTrue
(
isinstance
(
self
.
validator
.
get_bound_form
(),
forms
.
ModelForm
))
djangorestframework/utils/breadcrumbs.py
View file @
279fa0d3
from
django.core.urlresolvers
import
resolve
from
djangorestframework.utils.description
import
get_name
def
get_breadcrumbs
(
url
):
"""Given a url returns a list of breadcrumbs, which are each a tuple of (name, url)."""
...
...
@@ -17,7 +15,7 @@ def get_breadcrumbs(url):
else
:
# Check if this is a REST framework view, and if so add it to the breadcrumbs
if
isinstance
(
getattr
(
view
,
'cls_instance'
,
None
),
View
):
breadcrumbs_list
.
insert
(
0
,
(
get_name
(
view
),
url
))
breadcrumbs_list
.
insert
(
0
,
(
view
.
cls_instance
.
get_name
(
),
url
))
if
url
==
''
:
# All done
...
...
djangorestframework/utils/description.py
deleted
100644 → 0
View file @
152c385f
"""
Get a descriptive name and description for a view.
"""
import
re
from
djangorestframework.resources
import
Resource
,
FormResource
,
ModelResource
# These a a bit Grungy, but they do the job.
def
get_name
(
view
):
"""
Return a name for the view.
If view has a name attribute, use that, otherwise use the view's class name, with 'CamelCaseNames' converted to 'Camel Case Names'.
"""
# If we're looking up the name of a view callable, as found by reverse,
# grok the class instance that we stored when as_view was called.
if
getattr
(
view
,
'cls_instance'
,
None
):
view
=
view
.
cls_instance
# If this view has a resource that's been overridden, then use that resource for the name
if
getattr
(
view
,
'resource'
,
None
)
not
in
(
None
,
Resource
,
FormResource
,
ModelResource
):
name
=
view
.
resource
.
__name__
# Chomp of any non-descriptive trailing part of the resource class name
if
name
.
endswith
(
'Resource'
)
and
name
!=
'Resource'
:
name
=
name
[:
-
len
(
'Resource'
)]
# If the view has a descriptive suffix, eg '*** List', '*** Instance'
if
getattr
(
view
,
'_suffix'
,
None
):
name
+=
view
.
_suffix
# Otherwise if it's a function view use the function's name
elif
getattr
(
view
,
'__name__'
,
None
)
is
not
None
:
name
=
view
.
__name__
# If it's a view class with no resource then grok the name from the class name
elif
getattr
(
view
,
'__class__'
,
None
)
is
not
None
:
name
=
view
.
__class__
.
__name__
# Chomp of any non-descriptive trailing part of the view class name
if
name
.
endswith
(
'View'
)
and
name
!=
'View'
:
name
=
name
[:
-
len
(
'View'
)]
# I ain't got nuthin fo' ya
else
:
return
''
return
re
.
sub
(
'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))'
,
'
\\
1'
,
name
)
.
strip
()
def
get_description
(
view
):
"""
Provide a description for the view.
By default this is the view's docstring with nice unindention applied.
"""
# If we're looking up the name of a view callable, as found by reverse,
# grok the class instance that we stored when as_view was called.
if
getattr
(
view
,
'cls_instance'
,
None
):
view
=
view
.
cls_instance
# If this view has a resource that's been overridden, then use the resource's doctring
if
getattr
(
view
,
'resource'
,
None
)
not
in
(
None
,
Resource
,
FormResource
,
ModelResource
):
doc
=
view
.
resource
.
__doc__
# Otherwise use the view doctring
elif
getattr
(
view
,
'__doc__'
,
None
):
doc
=
view
.
__doc__
# I ain't got nuthin fo' ya
else
:
return
''
if
not
doc
:
return
''
whitespace_counts
=
[
len
(
line
)
-
len
(
line
.
lstrip
(
' '
))
for
line
in
doc
.
splitlines
()[
1
:]
if
line
.
lstrip
()]
# unindent the docstring if needed
if
whitespace_counts
:
whitespace_pattern
=
'^'
+
(
' '
*
min
(
whitespace_counts
))
return
re
.
sub
(
re
.
compile
(
whitespace_pattern
,
re
.
MULTILINE
),
''
,
doc
)
# otherwise return it as-is
return
doc
djangorestframework/views.py
View file @
279fa0d3
...
...
@@ -5,15 +5,17 @@ be subclassing in your implementation.
By setting or modifying class attributes on your view, you change it's predefined behaviour.
"""
import
re
from
django.core.urlresolvers
import
set_script_prefix
,
get_script_prefix
from
django.http
import
HttpResponse
from
django.utils.html
import
escape
from
django.utils.safestring
import
mark_safe
from
django.views.decorators.csrf
import
csrf_exempt
from
djangorestframework.compat
import
View
as
DjangoView
from
djangorestframework.compat
import
View
as
DjangoView
,
apply_markdown
from
djangorestframework.response
import
Response
,
ErrorResponse
from
djangorestframework.mixins
import
*
from
djangorestframework
import
resources
,
renderers
,
parsers
,
authentication
,
permissions
,
status
from
djangorestframework.utils.description
import
get_name
,
get_description
__all__
=
(
...
...
@@ -25,6 +27,48 @@ __all__ = (
)
def
_remove_trailing_string
(
content
,
trailing
):
"""
Strip trailing component `trailing` from `content` if it exists.
Used when generating names from view/resource classes.
"""
if
content
.
endswith
(
trailing
)
and
content
!=
trailing
:
return
content
[:
-
len
(
trailing
)]
return
content
def
_remove_leading_indent
(
content
):
"""
Remove leading indent from a block of text.
Used when generating descriptions from docstrings.
"""
whitespace_counts
=
[
len
(
line
)
-
len
(
line
.
lstrip
(
' '
))
for
line
in
content
.
splitlines
()[
1
:]
if
line
.
lstrip
()]
# unindent the content if needed
if
whitespace_counts
:
whitespace_pattern
=
'^'
+
(
' '
*
min
(
whitespace_counts
))
return
re
.
sub
(
re
.
compile
(
whitespace_pattern
,
re
.
MULTILINE
),
''
,
content
)
return
content
def
_camelcase_to_spaces
(
content
):
"""
Translate 'CamelCaseNames' to 'Camel Case Names'.
Used when generating names from view/resource classes.
"""
camelcase_boundry
=
'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))'
return
re
.
sub
(
camelcase_boundry
,
'
\\
1'
,
content
)
.
strip
()
_resource_classes
=
(
None
,
resources
.
Resource
,
resources
.
FormResource
,
resources
.
ModelResource
)
class
View
(
ResourceMixin
,
RequestMixin
,
ResponseMixin
,
AuthMixin
,
DjangoView
):
"""
Handles incoming requests and maps them to REST operations.
...
...
@@ -76,6 +120,54 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
"""
return
[
method
.
upper
()
for
method
in
self
.
http_method_names
if
hasattr
(
self
,
method
)]
def
get_name
(
self
):
"""
Return the resource or view class name for use as this view's name.
Override to customize.
"""
# If this view has a resource that's been overridden, then use that resource for the name
if
getattr
(
self
,
'resource'
,
None
)
not
in
_resource_classes
:
name
=
self
.
resource
.
__name__
name
=
_remove_trailing_string
(
name
,
'Resource'
)
name
+=
getattr
(
self
,
'_suffix'
,
''
)
# If it's a view class with no resource then grok the name from the class name
else
:
name
=
self
.
__class__
.
__name__
name
=
_remove_trailing_string
(
name
,
'View'
)
return
_camelcase_to_spaces
(
name
)
def
get_description
(
self
,
html
=
False
):
"""
Return the resource or view docstring for use as this view's description.
Override to customize.
"""
description
=
None
# If this view has a resource that's been overridden,
# then try to use the resource's docstring
if
getattr
(
self
,
'resource'
,
None
)
not
in
_resource_classes
:
description
=
self
.
resource
.
__doc__
# Otherwise use the view docstring
if
not
description
:
description
=
self
.
__doc__
or
''
description
=
_remove_leading_indent
(
description
)
if
html
:
return
self
.
markup_description
(
description
)
return
description
def
markup_description
(
self
,
description
):
if
apply_markdown
:
description
=
apply_markdown
(
description
)
else
:
description
=
escape
(
description
)
.
replace
(
'
\n
'
,
'<br />'
)
return
mark_safe
(
description
)
def
http_method_not_allowed
(
self
,
request
,
*
args
,
**
kwargs
):
"""
Return an HTTP 405 error if an operation is called which does not have a handler method.
...
...
@@ -164,8 +256,8 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
def
options
(
self
,
request
,
*
args
,
**
kwargs
):
response_obj
=
{
'name'
:
get_name
(
self
),
'description'
:
get_description
(
self
),
'name'
:
self
.
get_name
(
),
'description'
:
self
.
get_description
(
),
'renders'
:
self
.
_rendered_media_types
,
'parses'
:
request
.
_parsed_media_types
,
}
...
...
docs/index.rst
View file @
279fa0d3
...
...
@@ -105,6 +105,8 @@ The following example exposes your `MyModel` model through an api. It will provi
contents
.. include:: ../CHANGELOG.rst
Indices and tables
------------------
...
...
examples/permissionsexample/views.py
View file @
279fa0d3
...
...
@@ -2,14 +2,23 @@ from djangorestframework.views import View
from
djangorestframework.permissions
import
PerUserThrottling
,
IsAuthenticated
from
django.core.urlresolvers
import
reverse
class
PermissionsExampleView
(
View
):
"""
A container view for permissions examples.
"""
def
get
(
self
,
request
):
return
[{
'name'
:
'Throttling Example'
,
'url'
:
reverse
(
'throttled-resource'
)},
{
'name'
:
'Logged in example'
,
'url'
:
reverse
(
'loggedin-resource'
)},]
return
[
{
'name'
:
'Throttling Example'
,
'url'
:
reverse
(
'throttled-resource'
)
},
{
'name'
:
'Logged in example'
,
'url'
:
reverse
(
'loggedin-resource'
)
},
]
class
ThrottlingExampleView
(
View
):
...
...
@@ -20,7 +29,7 @@ class ThrottlingExampleView(View):
throttle will be applied until 60 seconds have passed since the first request.
"""
permissions
=
(
PerUserThrottling
,
)
permissions
=
(
PerUserThrottling
,
)
throttle
=
'10/min'
def
get
(
self
,
request
):
...
...
@@ -29,6 +38,7 @@ class ThrottlingExampleView(View):
"""
return
"Successful response to GET request because throttle is not yet active."
class
LoggedInExampleView
(
View
):
"""
You can login with **'test', 'test'.** or use curl:
...
...
@@ -37,5 +47,6 @@ class LoggedInExampleView(View):
"""
permissions
=
(
IsAuthenticated
,
)
def
get
(
self
,
request
):
return
'
Logged in or not?
'
return
'
You have permission to view this resource
'
examples/requirements-epio.txt
View file @
279fa0d3
Pygments==1.4
Markdown==2.0.3
djangorestframework
git+git://github.com/tomchristie/django-rest-framework.git
examples/settings.py
View file @
279fa0d3
...
...
@@ -53,16 +53,10 @@ MEDIA_ROOT = os.path.join(os.getenv('EPIO_DATA_DIRECTORY', '.'), 'media')
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
# NOTE: None of the djangorestframework examples serve media content via MEDIA_URL.
MEDIA_URL
=
''
MEDIA_URL
=
'/uploads/'
STATIC_URL
=
'/static/'
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
# NOTE: djangorestframework does not require the admin app to be installed,
# but it does require the admin media be served. Django's test server will do
# this for you automatically, but in production you'll want to make sure you
# serve the admin media from somewhere.
ADMIN_MEDIA_PREFIX
=
'/static/admin'
# Make this unique, and don't share it with anybody.
SECRET_KEY
=
't&9mru2_k$t8e2-9uq-wu2a1)9v*us&j3i#lsqkt(lbx*vh1cu'
...
...
@@ -102,6 +96,7 @@ INSTALLED_APPS = (
'django.contrib.contenttypes'
,
'django.contrib.sessions'
,
'django.contrib.sites'
,
'django.contrib.staticfiles'
,
'django.contrib.messages'
,
'djangorestframework'
,
...
...
examples/urls.py
View file @
279fa0d3
from
django.conf.urls.defaults
import
patterns
,
include
,
url
from
django.conf
import
settings
from
sandbox.views
import
Sandbox
from
django.contrib.staticfiles.urls
import
staticfiles_urlpatterns
urlpatterns
=
patterns
(
''
,
(
r'^$'
,
Sandbox
.
as_view
()),
...
...
@@ -16,3 +17,4 @@ urlpatterns = patterns('',
(
r'^'
,
include
(
'djangorestframework.urls'
)),
)
urlpatterns
+=
staticfiles_urlpatterns
()
tox.ini
View file @
279fa0d3
...
...
@@ -32,6 +32,7 @@ commands=
basepython
=
python2.5
deps
=
django
=
=1.2.4
django-staticfiles>=1.1.2
coverage
=
=3.4
URLObject>=0.6.0
unittest-xml-reporting
=
=1.2
...
...
@@ -43,6 +44,7 @@ deps=
basepython
=
python2.6
deps
=
django
=
=1.2.4
django-staticfiles>=1.1.2
coverage
=
=3.4
URLObject>=0.6.0
unittest-xml-reporting
=
=1.2
...
...
@@ -54,6 +56,7 @@ deps=
basepython
=
python2.7
deps
=
django
=
=1.2.4
django-staticfiles>=1.1.2
coverage
=
=3.4
URLObject>=0.6.0
unittest-xml-reporting
=
=1.2
...
...
@@ -135,6 +138,7 @@ commands=
python
examples/runtests.py
deps
=
django
=
=1.2.4
django-staticfiles>=1.1.2
coverage
=
=3.4
URLObject>=0.6.0
wsgiref
=
=0.1.2
...
...
@@ -150,6 +154,7 @@ commands=
python
examples/runtests.py
deps
=
django
=
=1.2.4
django-staticfiles>=1.1.2
coverage
=
=3.4
URLObject>=0.6.0
wsgiref
=
=0.1.2
...
...
@@ -165,6 +170,7 @@ commands=
python
examples/runtests.py
deps
=
django
=
=1.2.4
django-staticfiles>=1.1.2
coverage
=
=3.4
URLObject>=0.6.0
wsgiref
=
=0.1.2
...
...
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