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
99799032
Commit
99799032
authored
Jan 17, 2011
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Mostly improving documentation
parent
b0ce3f92
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
391 additions
and
38 deletions
+391
-38
.hgignore
+3
-0
README.txt
+4
-0
docs/conf.py
+220
-0
docs/index.rst
+12
-0
src/rest/modelresource.py
+50
-2
src/rest/parsers.py
+5
-1
src/rest/resource.py
+46
-34
src/rest/status.py
+50
-0
src/testapp/tests.py
+1
-1
No files found.
.hgignore
View file @
99799032
...
...
@@ -3,5 +3,8 @@ syntax: glob
*.pyc
*.db
env
cache
html
.project
.pydevproject
.settings
README.txt
View file @
99799032
...
...
@@ -11,3 +11,7 @@ source ./env/bin/activate
pip install -r ./requirements.txt
python ./src/manage.py test
# To build the documentation...
sphinx-build -c docs -b html -d cache docs html
docs/conf.py
0 → 100644
View file @
99799032
# -*- coding: utf-8 -*-
#
# Asset Platform documentation build configuration file, created by
# sphinx-quickstart on Fri Nov 19 20:24:09 2010.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import
sys
,
os
sys
.
path
.
append
(
os
.
path
.
join
(
os
.
path
.
dirname
(
os
.
path
.
dirname
(
__file__
)),
'src'
))
import
settings
from
django.core.management
import
setup_environ
setup_environ
(
settings
)
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions
=
[
'sphinx.ext.autodoc'
,
'sphinx.ext.doctest'
,
'sphinx.ext.todo'
,
'sphinx.ext.coverage'
,
'sphinx.ext.viewcode'
]
# Add any paths that contain templates here, relative to this directory.
templates_path
=
[]
# The suffix of source filenames.
source_suffix
=
'.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc
=
'index'
# General information about the project.
project
=
u'FlyWheel'
copyright
=
u'2011, Tom Christie'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version
=
'0.1'
# The full version, including alpha/beta/rc tags.
release
=
'0.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns
=
[
'_build'
]
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style
=
'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme
=
'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path
=
[]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename
=
'restfulloggingdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents
=
[
(
'index'
,
'restfullogging.tex'
,
u'restful logging Documentation'
,
u'tom c'
,
'manual'
),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages
=
[
(
'index'
,
'restfullogging'
,
u'restful logging Documentation'
,
[
u'tom c'
],
1
)
]
docs/index.rst
0 → 100644
View file @
99799032
FlyWheel Documentation
======================
This is the online documentation for FlyWheel - A REST framework for Django.
Indices and tables
------------------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
src/rest/modelresource.py
View file @
99799032
...
...
@@ -12,10 +12,38 @@ import re
class
ModelResource
(
Resource
):
"""A specialized type of Resource, for RESTful resources that map directly to a Django Model.
Useful things this provides:
0. Default input validation based on ModelForms.
1. Nice serialization of returned Models and QuerySets.
2. A default set of create/read/update/delete operations."""
# The model attribute refers to the Django Model which this Resource maps to.
# (The Model's class, rather than an instance of the Model)
model
=
None
# By default the set of returned fields will be the set of:
#
# 0. All the fields on the model, excluding 'id'.
# 1. All the properties on the model.
# 2. The absolute_url of the model, if a get_absolute_url method exists for the model.
#
# If you wish to override this behaviour,
# you should explicitly set the fields attribute on your class.
fields
=
None
# By default the form used with be a ModelForm for self.model
# If you wish to override this behaviour or provide a sub-classed ModelForm
# you should explicitly set the form attribute on your class.
form
=
None
# By default the set of input fields will be the same as the set of output fields
# If you wish to override this behaviour you should explicitly set the
# form_fields attribute on your class.
form_fields
=
None
def
get_bound_form
(
self
,
data
=
None
,
is_response
=
False
):
"""Return a form that may be used in validation and/or rendering an html emitter"""
if
self
.
form
:
...
...
@@ -25,7 +53,7 @@ class ModelResource(Resource):
class
NewModelForm
(
ModelForm
):
class
Meta
:
model
=
self
.
model
fields
=
self
.
form_fields
if
self
.
form_fields
else
None
#self.fields
fields
=
self
.
form_fields
if
self
.
form_fields
else
None
if
data
and
not
is_response
:
return
NewModelForm
(
data
)
...
...
@@ -36,7 +64,27 @@ class ModelResource(Resource):
else
:
return
None
def
cleanup_request
(
self
,
data
,
form_instance
):
"""Override cleanup_request to drop read-only fields from the input prior to validation.
This ensures that we don't error out with 'non-existent field' when these fields are supplied,
and allows for a pragmatic approach to resources which include read-only elements.
I would actually like to be strict and verify the value of correctness of the values in these fields,
although that gets tricky as it involves validating at the point that we get the model instance.
See here for another example of this approach:
http://fedoraproject.org/wiki/Cloud_APIs_REST_Style_Guide
https://www.redhat.com/archives/rest-practices/2010-April/thread.html#00041"""
read_only_fields
=
set
(
self
.
fields
)
-
set
(
self
.
form_instance
.
fields
)
input_fields
=
set
(
data
.
keys
())
clean_data
=
{}
for
key
in
input_fields
-
read_only_fields
:
clean_data
[
key
]
=
data
[
key
]
return
super
(
ModelResource
,
self
)
.
cleanup_request
(
clean_data
,
form_instance
)
def
cleanup_response
(
self
,
data
):
...
...
src/rest/parsers.py
View file @
99799032
import
json
from
rest.status
import
ResourceException
,
Status
class
BaseParser
(
object
):
def
__init__
(
self
,
resource
):
...
...
@@ -10,7 +11,10 @@ class BaseParser(object):
class
JSONParser
(
BaseParser
):
def
parse
(
self
,
input
):
return
json
.
loads
(
input
)
try
:
return
json
.
loads
(
input
)
except
ValueError
,
exc
:
raise
ResourceException
(
Status
.
HTTP_400_BAD_REQUEST
,
{
'detail'
:
'JSON parse error -
%
s'
%
str
(
exc
)})
class
XMLParser
(
BaseParser
):
pass
...
...
src/rest/resource.py
View file @
99799032
from
django.http
import
HttpResponse
from
django.contrib.sites.models
import
Site
from
django.core.urlresolvers
import
reverse
from
django.core.handlers.wsgi
import
STATUS_CODE_TEXT
from
django.http
import
HttpResponse
from
rest
import
emitters
,
parsers
from
rest.status
import
Status
,
ResourceException
from
decimal
import
Decimal
import
re
# TODO: Authentication
# TODO: Display user login in top panel: http://stackoverflow.com/questions/806835/django-redirect-to-previous-page-after-login
# TODO: Return basic object, not tuple
# TODO: Return basic object, not tuple
of status code, content, headers
# TODO: Take request, not headers
# TODO: Remove self.blah munging (Add a ResponseContext object)
# TODO: Erroring on non-existent fields
# TODO: Standard exception classes and module for status codes
# TODO: Standard exception classes
# TODO: Figure how out references and named urls need to work nicely
# TODO: POST on existing 404 URL, PUT on existing 404 URL
# TODO: Authentication
#
# NEXT: Generic content form
# NEXT: Remove self.blah munging (Add a ResponseContext object?)
# NEXT: Caching cleverness
# NEXT: Test non-existent fields on ModelResources
#
# FUTURE: Erroring on read-only fields
# Documentation, Release
#
STATUS_400_BAD_REQUEST
=
400
STATUS_405_METHOD_NOT_ALLOWED
=
405
STATUS_406_NOT_ACCEPTABLE
=
406
STATUS_415_UNSUPPORTED_MEDIA_TYPE
=
415
STATUS_500_INTERNAL_SERVER_ERROR
=
500
STATUS_501_NOT_IMPLEMENTED
=
501
class
ResourceException
(
Exception
):
def
__init__
(
self
,
status
,
content
=
''
,
headers
=
{}):
self
.
status
=
status
self
.
content
=
content
self
.
headers
=
headers
class
Resource
(
object
):
...
...
@@ -110,13 +100,16 @@ class Resource(object):
def
reverse
(
self
,
view
,
*
args
,
**
kwargs
):
"""Return a fully qualified URI for a given view or resource.
Use
the Sites framework if possible, otherwise fallback to using the current request."""
Add the domain using
the Sites framework if possible, otherwise fallback to using the current request."""
return
self
.
add_domain
(
reverse
(
view
,
*
args
,
**
kwargs
))
def
add_domain
(
self
,
path
):
"""Given a path, return an fully qualified URI.
Use the Sites framework if possible, otherwise fallback to using the domain from the current request."""
# Note that out-of-the-box the Sites framework uses the reserved domain 'example.com'
# See RFC 2606 - http://www.faqs.org/rfcs/rfc2606.html
try
:
site
=
Site
.
objects
.
get_current
()
if
site
.
domain
and
site
.
domain
!=
'example.com'
:
...
...
@@ -150,7 +143,7 @@ class Resource(object):
def
not_implemented
(
self
,
operation
):
"""Return an HTTP 500 server error if an operation is called which has been allowed by
allowed_operations, but which has not been implemented."""
raise
ResourceException
(
S
TATUS
_500_INTERNAL_SERVER_ERROR
,
raise
ResourceException
(
S
tatus
.
HTTP
_500_INTERNAL_SERVER_ERROR
,
{
'detail'
:
'
%
s operation on this resource has not been implemented'
%
(
operation
,
)})
...
...
@@ -172,18 +165,18 @@ class Resource(object):
# if anon_user and not anon_allowed_operations raise PermissionDenied
# return
def
check_method_allowed
(
self
,
method
):
"""Ensure the request method is acceptable for this resource."""
if
not
method
in
self
.
CALLMAP
.
keys
():
raise
ResourceException
(
S
TATUS
_501_NOT_IMPLEMENTED
,
raise
ResourceException
(
S
tatus
.
HTTP
_501_NOT_IMPLEMENTED
,
{
'detail'
:
'Unknown or unsupported method
\'
%
s
\'
'
%
method
})
if
not
self
.
CALLMAP
[
method
]
in
self
.
allowed_operations
:
raise
ResourceException
(
S
TATUS
_405_METHOD_NOT_ALLOWED
,
raise
ResourceException
(
S
tatus
.
HTTP
_405_METHOD_NOT_ALLOWED
,
{
'detail'
:
'Method
\'
%
s
\'
not allowed on this resource.'
%
method
})
def
get_bound_form
(
self
,
data
=
None
,
is_response
=
False
):
"""Optionally return a Django Form instance, which may be used for validation
and/or rendered by an HTML/XHTML emitter.
...
...
@@ -207,16 +200,31 @@ class Resource(object):
By default this uses form validation to filter the basic input into the required types."""
if
form_instance
is
None
:
return
data
if
not
form_instance
.
is_valid
():
if
not
form_instance
.
errors
:
# Default form validation does not check for additional invalid fields
non_existent_fields
=
[]
for
key
in
set
(
data
.
keys
())
-
set
(
form_instance
.
fields
.
keys
()):
non_existent_fields
.
append
(
key
)
if
not
form_instance
.
is_valid
()
or
non_existent_fields
:
if
not
form_instance
.
errors
and
not
non_existent_fields
:
# If no data was supplied the errors property will be None
details
=
'No content was supplied'
else
:
# Add standard field errors
details
=
dict
((
key
,
map
(
unicode
,
val
))
for
(
key
,
val
)
in
form_instance
.
errors
.
iteritems
())
# Add any non-field errors
if
form_instance
.
non_field_errors
():
details
[
'
_extra
'
]
=
self
.
form
.
non_field_errors
()
details
[
'
errors
'
]
=
self
.
form
.
non_field_errors
()
raise
ResourceException
(
STATUS_400_BAD_REQUEST
,
{
'detail'
:
details
})
# Add any non-existent field errors
for
key
in
non_existent_fields
:
details
[
key
]
=
[
'This field does not exist'
]
# Bail. Note that we will still serialize this response with the appropriate content type
raise
ResourceException
(
Status
.
HTTP_400_BAD_REQUEST
,
{
'detail'
:
details
})
return
form_instance
.
cleaned_data
...
...
@@ -241,7 +249,7 @@ class Resource(object):
try
:
return
self
.
parsers
[
content_type
]
except
KeyError
:
raise
ResourceException
(
S
TATUS
_415_UNSUPPORTED_MEDIA_TYPE
,
raise
ResourceException
(
S
tatus
.
HTTP
_415_UNSUPPORTED_MEDIA_TYPE
,
{
'detail'
:
'Unsupported media type
\'
%
s
\'
'
%
content_type
})
...
...
@@ -295,14 +303,13 @@ class Resource(object):
(
accept_mimetype
==
mimetype
)):
return
(
mimetype
,
emitter
)
raise
ResourceException
(
S
TATUS
_406_NOT_ACCEPTABLE
,
raise
ResourceException
(
S
tatus
.
HTTP
_406_NOT_ACCEPTABLE
,
{
'detail'
:
'Could not statisfy the client
\'
s accepted content type'
,
'accepted_types'
:
[
item
[
0
]
for
item
in
self
.
emitters
]})
def
_handle_request
(
self
,
request
,
*
args
,
**
kwargs
):
"""
Broadly this consists of the following procedure:
0. ensure the operation is permitted
...
...
@@ -347,9 +354,14 @@ class Resource(object):
except
ResourceException
,
exc
:
# On exceptions we still serialize the response appropriately
(
self
.
resp_status
,
ret
,
self
.
resp_headers
)
=
(
exc
.
status
,
exc
.
content
,
exc
.
headers
)
# Fall back to the default emitter if we failed to perform content negotiation
if
emitter
is
None
:
mimetype
,
emitter
=
self
.
emitters
[
0
]
mimetype
,
emitter
=
self
.
emitters
[
0
]
# Provide an empty bound form if we do not have an existing form and if one is required
if
self
.
form_instance
is
None
and
emitter
.
uses_forms
:
self
.
form_instance
=
self
.
get_bound_form
()
...
...
src/rest/status.py
0 → 100644
View file @
99799032
class
Status
(
object
):
"""Descriptive HTTP status codes, for code readability."""
HTTP_200_OK
=
200
HTTP_201_CREATED
=
201
HTTP_202_ACCEPTED
=
202
HTTP_203_NON_AUTHORITATIVE_INFORMATION
=
203
HTTP_204_NO_CONTENT
=
204
HTTP_205_RESET_CONTENT
=
205
HTTP_206_PARTIAL_CONTENT
=
206
HTTP_400_BAD_REQUEST
=
400
HTTP_401_UNAUTHORIZED
=
401
HTTP_402_PAYMENT_REQUIRED
=
402
HTTP_403_FORBIDDEN
=
403
HTTP_404_NOT_FOUND
=
404
HTTP_405_METHOD_NOT_ALLOWED
=
405
HTTP_406_NOT_ACCEPTABLE
=
406
HTTP_407_PROXY_AUTHENTICATION_REQUIRED
=
407
HTTP_408_REQUEST_TIMEOUT
=
408
HTTP_409_CONFLICT
=
409
HTTP_410_GONE
=
410
HTTP_411_LENGTH_REQUIRED
=
411
HTTP_412_PRECONDITION_FAILED
=
412
HTTP_413_REQUEST_ENTITY_TOO_LARGE
=
413
HTTP_414_REQUEST_URI_TOO_LONG
=
414
HTTP_415_UNSUPPORTED_MEDIA_TYPE
=
415
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
=
416
HTTP_417_EXPECTATION_FAILED
=
417
HTTP_100_CONTINUE
=
100
HTTP_101_SWITCHING_PROTOCOLS
=
101
HTTP_300_MULTIPLE_CHOICES
=
300
HTTP_301_MOVED_PERMANENTLY
=
301
HTTP_302_FOUND
=
302
HTTP_303_SEE_OTHER
=
303
HTTP_304_NOT_MODIFIED
=
304
HTTP_305_USE_PROXY
=
305
HTTP_306_RESERVED
=
306
HTTP_307_TEMPORARY_REDIRECT
=
307
HTTP_500_INTERNAL_SERVER_ERROR
=
500
HTTP_501_NOT_IMPLEMENTED
=
501
HTTP_502_BAD_GATEWAY
=
502
HTTP_503_SERVICE_UNAVAILABLE
=
503
HTTP_504_GATEWAY_TIMEOUT
=
504
HTTP_505_HTTP_VERSION_NOT_SUPPORTED
=
505
class
ResourceException
(
Exception
):
def
__init__
(
self
,
status
,
content
=
''
,
headers
=
{}):
self
.
status
=
status
self
.
content
=
content
self
.
headers
=
headers
src/testapp/tests.py
View file @
99799032
...
...
@@ -4,7 +4,7 @@
from
django.test
import
TestCase
from
django.core.urlresolvers
import
reverse
from
testapp
import
views
import
json
#
import json
#from rest.utils import xml2dict, dict2xml
...
...
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