mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-22 09:36:49 +03:00
Lots of docs, trying to tidy up examples...
This commit is contained in:
parent
40f47a9fb3
commit
250ab0f609
|
@ -47,7 +47,7 @@ source_suffix = '.rst'
|
|||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'FlyWheel'
|
||||
project = u'django-rest-framework'
|
||||
copyright = u'2011, Tom Christie'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
|
|
29
docs/examples/blogpost.rst
Normal file
29
docs/examples/blogpost.rst
Normal file
|
@ -0,0 +1,29 @@
|
|||
ModelResource example - Blog posts
|
||||
==================================
|
||||
|
||||
The models
|
||||
----------
|
||||
|
||||
``models.py``
|
||||
|
||||
.. include:: ../../examples/blogpost/models.py
|
||||
:literal:
|
||||
|
||||
URL configuration
|
||||
-----------------
|
||||
|
||||
``urls.py``
|
||||
|
||||
.. include:: ../../examples/blogpost/urls.py
|
||||
:literal:
|
||||
|
||||
Creating the resources
|
||||
----------------------
|
||||
|
||||
Once we have some existing models there's very little we need to do to create the corresponding resources. We simply create a base resource and an instance resource for each model we're working with.
|
||||
django-rest-framework will provide the default operations on the resources all the usual input validation that Django's models can give us for free.
|
||||
|
||||
``views.py``
|
||||
|
||||
.. include:: ../../examples/blogpost/views.py
|
||||
:literal:
|
13
docs/examples/objectstore.rst
Normal file
13
docs/examples/objectstore.rst
Normal file
|
@ -0,0 +1,13 @@
|
|||
Resource example - An object store
|
||||
==================================
|
||||
|
||||
``urls.py``
|
||||
|
||||
.. include:: ../../examples/objectstore/urls.py
|
||||
:literal:
|
||||
|
||||
``views.py``
|
||||
|
||||
.. include:: ../../examples/objectstore/views.py
|
||||
:literal:
|
||||
|
91
docs/examples/pygments.rst
Normal file
91
docs/examples/pygments.rst
Normal file
|
@ -0,0 +1,91 @@
|
|||
Resource with form validation - A pygments pastebin
|
||||
===================================================
|
||||
|
||||
This example demonstrates creating a REST API using a :class:`.Resource` with some form validation on the input.
|
||||
We're going to provide a simple wrapper around the awesome `pygments <http://pygments.org/>`_ library, to create the Web API for a simple pastebin.
|
||||
|
||||
.. note::
|
||||
|
||||
A live sandbox instance of this API is available at http://api.django-rest-framework.org/pygments/
|
||||
|
||||
You can browse the API using a web browser(1), or from the command line(2)::
|
||||
|
||||
curl -X GET http://api.django-rest-framework.org/pygments/ -H 'Accept: text/plain'
|
||||
|
||||
#. Except for Internet Explorer. Because it's `dumb <http://www.gethifi.com/blog/browser-rest-http-accept-headers>`_.
|
||||
#. See `using CURL with django-rest-framework <http://django-rest-framework.org/howto/usingcurl.html>`_ for more details.
|
||||
|
||||
URL configuration
|
||||
-----------------
|
||||
|
||||
We'll need two resources:
|
||||
|
||||
* A resource which represents the root of the API.
|
||||
* A resource which represents an instance of a highlighted snippet.
|
||||
|
||||
``urls.py``
|
||||
|
||||
.. include:: ../../examples/pygments_api/urls.py
|
||||
:literal:
|
||||
|
||||
Form validation
|
||||
---------------
|
||||
|
||||
We'll now add a form to specify what input fields are required when creating a new highlighed code snippet. This will include:
|
||||
|
||||
* The code text itself.
|
||||
* An optional title for the code.
|
||||
* A flag to determine if line numbers should be included.
|
||||
* Which programming langauge to interpret the code snippet as.
|
||||
* Which output style to use for the highlighting.
|
||||
|
||||
``forms.py``
|
||||
|
||||
.. include:: ../../examples/pygments_api/forms.py
|
||||
:literal:
|
||||
|
||||
Creating the resources
|
||||
----------------------
|
||||
|
||||
We'll need to define 3 resource handling methods on our resources.
|
||||
|
||||
* ``PygmentsRoot.get()`` method, which lists all the existing snippets.
|
||||
* ``PygmentsRoot.post()`` method, which creates new snippets.
|
||||
* ``PygmentsInstance.get()`` method, which returns existing snippets.
|
||||
|
||||
And set a number of attributes on our resources.
|
||||
|
||||
* Set the ``allowed_methods`` and ``anon_allowed_methods`` attributes on both resources allowing for full unauthenticated access.
|
||||
* Set the ``form`` attribute on the ``PygmentsRoot`` resource, to give us input validation when we create snippets.
|
||||
* Set the ``emitters`` attribute on the ``PygmentsInstance`` resource, so that
|
||||
|
||||
``views.py``
|
||||
|
||||
.. include:: ../../examples/pygments_api/views.py
|
||||
:literal:
|
||||
|
||||
Completed
|
||||
---------
|
||||
|
||||
And we're done. We now have an API that is:
|
||||
|
||||
* **Browseable.** The API supports media types for both programmatic and human access, and can be accessed either via a browser or from the command line.
|
||||
* **Self describing.** The API serves as it's own documentation.
|
||||
* **Well connected.** The API can be accessed fully by traversal from the initial URL. Clients never need to construct URLs themselves.
|
||||
|
||||
Our API also supports multiple media types for both input and output, and applies sensible input validation in all cases.
|
||||
|
||||
For example if we make a POST request using form input:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
bash: curl -X POST --data 'code=print "hello, world!"' --data 'style=foobar' -H 'X-Requested-With: XMLHttpRequest' http://api.django-rest-framework.org/pygments/
|
||||
{"detail": {"style": ["Select a valid choice. foobar is not one of the available choices."], "lexer": ["This field is required."]}}
|
||||
|
||||
Or if we make the same request using JSON:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
bash: curl -X POST --data-binary '{"code":"print \"hello, world!\"", "style":"foobar"}' -H 'Content-Type: application/json' -H 'X-Requested-With: XMLHttpRequest' http://api.django-rest-framework.org/pygments/
|
||||
{"detail": {"style": ["Select a valid choice. foobar is not one of the available choices."], "lexer": ["This field is required."]}}
|
||||
|
30
docs/howto/usingcurl.rst
Normal file
30
docs/howto/usingcurl.rst
Normal file
|
@ -0,0 +1,30 @@
|
|||
Using CURL with django-rest-framework
|
||||
=====================================
|
||||
|
||||
`curl <http://curl.haxx.se/>`_ is a great command line tool for making requests to URLs.
|
||||
|
||||
There are a few things that can be helpful to remember when using CURL with django-rest-framework APIs.
|
||||
|
||||
#. Curl sends an ``Accept: */*`` header by default::
|
||||
|
||||
curl -X GET http://example.com/my-api/
|
||||
|
||||
#. Setting the ``Accept:`` header on a curl request can be useful::
|
||||
|
||||
curl -X GET -H 'Accept: application/json' http://example.com/my-api/
|
||||
|
||||
#. The text/plain representation is useful for browsing the API::
|
||||
|
||||
curl -X GET -H 'Accept: text/plain' http://example.com/my-api/
|
||||
|
||||
#. All POST requests should include an ```X-Requested-With: XMLHttpRequest`` header <http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#how-it-works>`_::
|
||||
|
||||
curl -X POST -H 'X-Requested-With: XMLHttpRequest' --data 'foo=bar' http://example.com/my-api/
|
||||
|
||||
#. ``POST`` and ``PUT`` requests can contain form data (ie ``Content-Type:: application/x-www-form-urlencoded``)::
|
||||
|
||||
curl -X PUT --data 'foo=bar' http://example.com/my-api/some-resource/
|
||||
|
||||
#. Or any other content type::
|
||||
|
||||
curl -X PUT -H 'Content-Type: application/json' --data-binary '{"foo":"bar"}' http://example.com/my-api/some-resource/
|
|
@ -1,9 +1,9 @@
|
|||
FlyWheel Documentation
|
||||
======================
|
||||
Django REST framework
|
||||
=====================
|
||||
|
||||
This is the online documentation for FlyWheel - A REST framework for Django.
|
||||
A lightweight REST framework for Django.
|
||||
|
||||
Some of FlyWheel's features:
|
||||
Features:
|
||||
|
||||
* Clean, simple, class-based views for Resources.
|
||||
* Support for ModelResources with nice default implementations and input validation.
|
||||
|
@ -16,21 +16,76 @@ Some of FlyWheel's features:
|
|||
Installation & Setup
|
||||
--------------------
|
||||
|
||||
blah di blah
|
||||
The django-rest-framework project is hosted as a `mercurial repository on bitbucket <https://bitbucket.org/tomchristie/flywheel>`_.
|
||||
To get a local copy of the repository use mercurial::
|
||||
|
||||
hg clone https://tomchristie@bitbucket.org/tomchristie/flywheel
|
||||
|
||||
To add django-rest-framework to a django project:
|
||||
|
||||
* Copy or symlink the ``djangorestframework`` directory into python's ``site-packages`` directory, or otherwise ensure that the ``djangorestframework`` directory is on your ``PYTHONPATH``.
|
||||
* Add ``djangorestframework`` to your ``INSTALLED_APPS``.
|
||||
* Ensure the ``TEMPLATE_LOADERS`` setting contains the item ``'django.template.loaders.app_directories.Loader'``. (It will do by default, so you shouldn't normally need to do anything here.)
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
Blah di blah
|
||||
Often you'll want parts of your API to directly map to existing Models.
|
||||
At it's simplest this looks something like this...
|
||||
|
||||
``views.py``::
|
||||
|
||||
from djangorestframework.modelresource import ModelResource, ModelRootResource
|
||||
from models import MyModel
|
||||
|
||||
class MyModelRootResource(ModelRootResource):
|
||||
"""A create/list resource for MyModel."""
|
||||
allowed_methods = ('GET', 'POST')
|
||||
model = MyModel
|
||||
|
||||
class MyModelResource(ModelResource):
|
||||
"""A read/update/delete resource for MyModel."""
|
||||
allowed_methods = ('GET', 'PUT', 'DELETE')
|
||||
model = MyModel
|
||||
|
||||
``urls.py``::
|
||||
|
||||
urlpatterns += patterns('myapp.views',
|
||||
url(r'^mymodel/$', 'MyModelRootResource'),
|
||||
url(r'^mymodel/([^/]+)/$', 'MyModelResource'),
|
||||
)
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Blah blah blah, examples here.
|
||||
There's a few real world examples included with django-rest-framework.
|
||||
These demonstrate the following use cases:
|
||||
|
||||
Add a toctree for examples
|
||||
#. Using :class:`.Resource` for resources that do not map to models.
|
||||
#. Using :class:`.Resource` with forms for input validation.
|
||||
#. Using :class:`.ModelResource` for resources that map directly to models.
|
||||
|
||||
All the examples are freely available for testing in the sandbox here: http://api.django-rest-framework.org
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
examples/objectstore
|
||||
examples/pygments
|
||||
examples/blogpost
|
||||
|
||||
How Tos
|
||||
-------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
howto/usingcurl
|
||||
|
||||
.. note::
|
||||
|
||||
TODO
|
||||
|
||||
Library Reference
|
||||
-----------------
|
||||
|
@ -38,12 +93,12 @@ Library Reference
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
resource
|
||||
modelresource
|
||||
emitters
|
||||
parsers
|
||||
authenticators
|
||||
response
|
||||
library/resource
|
||||
library/modelresource
|
||||
library/emitters
|
||||
library/parsers
|
||||
library/authenticators
|
||||
library/response
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
30
docs/library/authenticators.rst
Normal file
30
docs/library/authenticators.rst
Normal file
|
@ -0,0 +1,30 @@
|
|||
:mod:`authenticators`
|
||||
=====================
|
||||
|
||||
.. module:: authenticators
|
||||
|
||||
The authenticators module provides a standard set of authentication methods that can be plugged in to a :class:`.Resource`, as well as providing a template by which to write custom authentication methods.
|
||||
|
||||
The base class
|
||||
--------------
|
||||
|
||||
All authenticators must subclass the :class:`BaseAuthenticator` class and override it's :func:`authenticate` method.
|
||||
|
||||
.. class:: BaseAuthenticator
|
||||
|
||||
.. method:: authenticate(request)
|
||||
|
||||
Authenticate the request and return the authentication context or None.
|
||||
|
||||
The default permission checking on :class:`.Resource` will use the allowed_methods attribute for permissions if the authentication context is not None, and use anon_allowed_methods otherwise.
|
||||
|
||||
The authentication context is passed to the handler calls (eg :meth:`.Resource.get`, :meth:`.Resource.post` etc...) in order to allow them to apply any more fine grained permission checking at the point the response is being generated.
|
||||
|
||||
This function must be overridden to be implemented.
|
||||
|
||||
Provided authenticators
|
||||
-----------------------
|
||||
|
||||
.. note::
|
||||
|
||||
TODO - document this module properly
|
7
docs/library/emitters.rst
Normal file
7
docs/library/emitters.rst
Normal file
|
@ -0,0 +1,7 @@
|
|||
:mod:`emitters`
|
||||
===============
|
||||
|
||||
The emitters module provides a set of emitters that can be plugged in to a :class:`.Resource`. An emitter is responsible for taking the output of a and serializing it to a given media type. A :class:`.Resource` can have a number of emitters, allow the same content to be serialized in a number of different formats depending on the requesting client's preferences, as specified in the HTTP Request's Accept header.
|
||||
|
||||
.. automodule:: emitters
|
||||
:members:
|
9
docs/library/modelresource.rst
Normal file
9
docs/library/modelresource.rst
Normal file
|
@ -0,0 +1,9 @@
|
|||
:mod:`modelresource`
|
||||
====================
|
||||
|
||||
.. note::
|
||||
|
||||
TODO - document this module properly
|
||||
|
||||
.. automodule:: modelresource
|
||||
:members:
|
5
docs/library/parsers.rst
Normal file
5
docs/library/parsers.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
:mod:`parsers`
|
||||
==============
|
||||
|
||||
.. automodule:: parsers
|
||||
:members:
|
136
docs/library/resource.rst
Normal file
136
docs/library/resource.rst
Normal file
|
@ -0,0 +1,136 @@
|
|||
:mod:`resource`
|
||||
===============
|
||||
|
||||
.. module:: resource
|
||||
|
||||
The :mod:`resource` module is the core of FlyWheel. It provides the :class:`Resource` base class which handles incoming HTTP requests and maps them to method calls, performing authentication, input deserialization, input validation and output serialization.
|
||||
|
||||
Resources are created by sublassing :class:`Resource`, setting a number of class attributes, and overriding one or more methods.
|
||||
|
||||
.. class:: Resource
|
||||
|
||||
:class:`Resource` class attributes
|
||||
----------------------------------
|
||||
|
||||
The following class attributes determine the behavior of the Resource and are intended to be overridden.
|
||||
|
||||
.. attribute:: Resource.allowed_methods
|
||||
|
||||
A list of the HTTP methods that the Resource supports.
|
||||
HTTP requests to the resource that do not map to an allowed operation will result in a 405 Method Not Allowed response.
|
||||
|
||||
Default: ``('GET',)``
|
||||
|
||||
.. attribute:: Resource.anon_allowed_methods
|
||||
|
||||
A list of the HTTP methods that the Resource supports for unauthenticated users.
|
||||
Unauthenticated HTTP requests to the resource that do not map to an allowed operation will result in a 405 Method Not Allowed response.
|
||||
|
||||
Default: ``()``
|
||||
|
||||
.. attribute:: Resource.emitters
|
||||
|
||||
The list of emitters that the Resource supports. This determines which media types the resource can serialize it's output to. Clients can specify which media types they accept using standard HTTP content negotiation via the Accept header. (See `RFC 2616 - Sec 14.1 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html>`_) Clients can also override this standard content negotiation by specifying a `_format` ...
|
||||
|
||||
The :mod:`emitters` module provides the :class:`BaseEmitter` class and a set of default emitters, including emitters for JSON and XML, as well as emitters for HTML and Plain Text which provide for a self documenting API.
|
||||
|
||||
The ordering of the Emitters is important as it determines an order of preference.
|
||||
|
||||
Default: ``(emitters.JSONEmitter, emitters.DocumentingHTMLEmitter, emitters.DocumentingXHTMLEmitter, emitters.DocumentingPlainTextEmitter, emitters.XMLEmitter)``
|
||||
|
||||
.. attribute:: Resource.parsers
|
||||
|
||||
The list of parsers that the Resource supports. This determines which media types the resource can accept as input for incoming HTTP requests. (Typically PUT and POST requests).
|
||||
|
||||
The ordering of the Parsers may be considered informative of preference but is not used ...
|
||||
|
||||
Default: ``(parsers.JSONParser, parsers.XMLParser, parsers.FormParser)``
|
||||
|
||||
.. attribute:: Resource.authenticators
|
||||
|
||||
The list of authenticators that the Resource supports. This determines which authentication methods (eg Basic, Digest, OAuth) are used to authenticate requests.
|
||||
|
||||
Default: ``(authenticators.UserLoggedInAuthenticator, authenticators.BasicAuthenticator)``
|
||||
|
||||
.. attribute:: Resource.form
|
||||
|
||||
If not None, this attribute should be a Django form which will be used to validate any request data.
|
||||
This attribute is typically only used for POST or PUT requests to the resource.
|
||||
|
||||
Deafult: ``None``
|
||||
|
||||
.. attribute:: Resource.callmap
|
||||
|
||||
Maps HTTP methods to function calls on the :class:`Resource`. It may be overridden in order to add support for other HTTP methods such as HEAD, OPTIONS and PATCH, or in order to map methods to different function names, for example to use a more `CRUD <http://en.wikipedia.org/wiki/Create,_read,_update_and_delete>`_ like style.
|
||||
|
||||
Default: ``{ 'GET': 'get', 'POST': 'post', 'PUT': 'put', 'DELETE': 'delete' }``
|
||||
|
||||
|
||||
:class:`Resource` methods
|
||||
-------------------------
|
||||
|
||||
.. method:: Resource.get
|
||||
.. method:: Resource.post
|
||||
.. method:: Resource.put
|
||||
.. method:: Resource.delete
|
||||
.. method:: Resource.authenticate
|
||||
.. method:: Resource.reverse
|
||||
|
||||
:class:`Resource` properties
|
||||
----------------------------
|
||||
|
||||
.. method:: Resource.name
|
||||
.. method:: Resource.description
|
||||
.. method:: Resource.default_emitter
|
||||
.. method:: Resource.default_parser
|
||||
.. method:: Resource.emitted_media_types
|
||||
.. method:: Resource.parsed_media_types
|
||||
|
||||
:class:`Resource` reserved form and query parameters
|
||||
----------------------------------------------------
|
||||
|
||||
.. attribute:: Resource.ACCEPT_QUERY_PARAM
|
||||
|
||||
If set, allows the default `Accept:` header content negotiation to be bypassed by setting the requested media type in a query parameter on the URL. This can be useful if it is necessary to be able to hyperlink to a given format on the Resource using standard HTML.
|
||||
|
||||
Set to None to disable, or to another string value to use another name for the reserved URL query parameter.
|
||||
|
||||
Default: ``"_accept"``
|
||||
|
||||
.. attribute:: Resource.METHOD_PARAM
|
||||
|
||||
If set, allows for PUT and DELETE requests to be tunneled on form POST operations, by setting a (typically hidden) form field with the method name. This allows standard HTML forms to perform method requests which would otherwise `not be supported <http://dev.w3.org/html5/spec/Overview.html#attr-fs-method>`_
|
||||
|
||||
Set to None to disable, or to another string value to use another name for the reserved form field.
|
||||
|
||||
Default: ``"_method"``
|
||||
|
||||
.. attribute:: Resource.CONTENTTYPE_PARAM
|
||||
|
||||
Used together with :attr:`CONTENT_PARAM`.
|
||||
|
||||
If set, allows for arbitrary content types to be tunneled on form POST operations, by setting a form field with the content type. This allows standard HTML forms to perform requests with content types other those `supported by default <http://dev.w3.org/html5/spec/Overview.html#attr-fs-enctype>`_ (ie. `application/x-www-form-urlencoded`, `multipart/form-data`, and `text-plain`)
|
||||
|
||||
Set to None to disable, or to another string value to use another name for the reserved form field.
|
||||
|
||||
Default: ``"_contenttype"``
|
||||
|
||||
.. attribute:: Resource.CONTENT_PARAM
|
||||
|
||||
Used together with :attr:`CONTENTTYPE_PARAM`.
|
||||
|
||||
Set to None to disable, or to another string value to use another name for the reserved form field.
|
||||
|
||||
Default: ``"_content"``
|
||||
|
||||
.. attribute:: Resource.CSRF_PARAM
|
||||
|
||||
The name used in Django's (typically hidden) form field for `CSRF Protection <http://docs.djangoproject.com/en/dev/ref/contrib/csrf/>`_.
|
||||
|
||||
Setting to None does not disable Django's CSRF middleware, but it does mean that the field name will not be treated as reserved by FlyWheel, so for example the default :class:`FormParser` will return fields with this as part of the request content, rather than ignoring them.
|
||||
|
||||
Default:: ``"csrfmiddlewaretoken"``
|
||||
|
||||
reserved params
|
||||
internal methods
|
||||
|
5
docs/library/response.rst
Normal file
5
docs/library/response.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
:mod:`response`
|
||||
===============
|
||||
|
||||
.. automodule:: response
|
||||
:members:
|
|
@ -30,13 +30,7 @@ class BlogPost(models.Model):
|
|||
@models.permalink
|
||||
def comments_url(self):
|
||||
"""Link to a resource which lists all comments for this blog post."""
|
||||
return ('blogpost.views.CommentList', (), {'blogpost_id': self.key})
|
||||
|
||||
@property
|
||||
@models.permalink
|
||||
def comment_url(self):
|
||||
"""Link to a resource which can create a comment for this blog post."""
|
||||
return ('blogpost.views.CommentCreator', (), {'blogpost_id': self.key})
|
||||
return ('blogpost.views.CommentRoot', (), {'blogpost_id': self.key})
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
from django.conf.urls.defaults import patterns
|
||||
|
||||
urlpatterns = patterns('blogpost.views',
|
||||
(r'^$', 'RootResource'),
|
||||
(r'^blog-posts/$', 'BlogPostList'),
|
||||
(r'^blog-post/$', 'BlogPostCreator'),
|
||||
(r'^blog-post/(?P<key>[^/]+)/$', 'BlogPostInstance'),
|
||||
(r'^blog-post/(?P<blogpost_id>[^/]+)/comments/$', 'CommentList'),
|
||||
(r'^blog-post/(?P<blogpost_id>[^/]+)/comment/$', 'CommentCreator'),
|
||||
(r'^blog-post/(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', 'CommentInstance'),
|
||||
(r'^$', 'BlogPostRoot'),
|
||||
(r'^(?P<key>[^/]+)/$', 'BlogPostInstance'),
|
||||
(r'^(?P<blogpost_id>[^/]+)/comments/$', 'CommentRoot'),
|
||||
(r'^(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', 'CommentInstance'),
|
||||
)
|
||||
|
|
|
@ -1,34 +1,15 @@
|
|||
from flywheel.response import Response, status
|
||||
from flywheel.resource import Resource
|
||||
from flywheel.modelresource import ModelResource, QueryModelResource
|
||||
from flywheel.modelresource import ModelResource, RootModelResource
|
||||
from blogpost.models import BlogPost, Comment
|
||||
|
||||
##### Root Resource #####
|
||||
|
||||
class RootResource(Resource):
|
||||
"""This is the top level resource for the API.
|
||||
All the sub-resources are discoverable from here."""
|
||||
allowed_methods = ('GET',)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return Response(status.HTTP_200_OK,
|
||||
{'blog-posts': self.reverse(BlogPostList),
|
||||
'blog-post': self.reverse(BlogPostCreator)})
|
||||
|
||||
|
||||
##### Blog Post Resources #####
|
||||
|
||||
BLOG_POST_FIELDS = ('created', 'title', 'slug', 'content', 'absolute_url', 'comment_url', 'comments_url')
|
||||
COMMENT_FIELDS = ('username', 'comment', 'created', 'rating', 'absolute_url', 'blogpost_url')
|
||||
|
||||
class BlogPostList(QueryModelResource):
|
||||
"""A resource which lists all existing blog posts."""
|
||||
allowed_methods = ('GET', )
|
||||
model = BlogPost
|
||||
fields = BLOG_POST_FIELDS
|
||||
|
||||
class BlogPostCreator(ModelResource):
|
||||
"""A resource with which blog posts may be created."""
|
||||
allowed_methods = ('POST',)
|
||||
class BlogPostRoot(RootModelResource):
|
||||
"""A resource with which lists all existing blog posts and creates new blog posts."""
|
||||
allowed_methods = ('GET', 'POST',)
|
||||
model = BlogPost
|
||||
fields = BLOG_POST_FIELDS
|
||||
|
||||
|
@ -38,20 +19,9 @@ class BlogPostInstance(ModelResource):
|
|||
model = BlogPost
|
||||
fields = BLOG_POST_FIELDS
|
||||
|
||||
|
||||
##### Comment Resources #####
|
||||
|
||||
COMMENT_FIELDS = ('username', 'comment', 'created', 'rating', 'absolute_url', 'blogpost_url')
|
||||
|
||||
class CommentList(QueryModelResource):
|
||||
"""A resource which lists all existing comments for a given blog post."""
|
||||
allowed_methods = ('GET', )
|
||||
model = Comment
|
||||
fields = COMMENT_FIELDS
|
||||
|
||||
class CommentCreator(ModelResource):
|
||||
"""A resource with which blog comments may be created for a given blog post."""
|
||||
allowed_methods = ('POST',)
|
||||
class CommentRoot(RootModelResource):
|
||||
"""A resource which lists all existing comments for a given blog post, and creates new blog comments for a given blog post."""
|
||||
allowed_methods = ('GET', 'POST',)
|
||||
model = Comment
|
||||
fields = COMMENT_FIELDS
|
||||
|
||||
|
|
|
@ -3,23 +3,26 @@ from django import forms
|
|||
from pygments.lexers import get_all_lexers
|
||||
from pygments.styles import get_all_styles
|
||||
|
||||
import httplib2 as httplib
|
||||
|
||||
|
||||
LEXER_CHOICES = sorted([(item[1][0], item[0]) for item in get_all_lexers()])
|
||||
STYLE_CHOICES = sorted((item, item) for item in list(get_all_styles()))
|
||||
|
||||
|
||||
class PygmentsForm(forms.Form):
|
||||
"""A simple form with some of the most important pygments settings.
|
||||
The code to be highlighted can be specified either in a text field, or by URL.
|
||||
We do some additional form validation to ensure clients see helpful error responses."""
|
||||
|
||||
code = forms.CharField(widget=forms.Textarea, label='Code Text', max_length=1000000,
|
||||
code = forms.CharField(widget=forms.Textarea,
|
||||
label='Code Text',
|
||||
max_length=1000000,
|
||||
help_text='(Copy and paste the code text here.)')
|
||||
title = forms.CharField(required=False, help_text='(Optional)', max_length=100)
|
||||
linenos = forms.BooleanField(label='Show Line Numbers', required=False)
|
||||
lexer = forms.ChoiceField(choices=LEXER_CHOICES, initial='python')
|
||||
style = forms.ChoiceField(choices=STYLE_CHOICES, initial='friendly')
|
||||
title = forms.CharField(required=False,
|
||||
help_text='(Optional)',
|
||||
max_length=100)
|
||||
linenos = forms.BooleanField(label='Show Line Numbers',
|
||||
required=False)
|
||||
lexer = forms.ChoiceField(choices=LEXER_CHOICES,
|
||||
initial='python')
|
||||
style = forms.ChoiceField(choices=STYLE_CHOICES,
|
||||
initial='friendly')
|
||||
|
||||
|
||||
|
|
|
@ -2,5 +2,5 @@ from django.conf.urls.defaults import patterns
|
|||
|
||||
urlpatterns = patterns('pygments_api.views',
|
||||
(r'^$', 'PygmentsRoot'),
|
||||
(r'^([a-zA-Z0-9]+)/$', 'PygmentsInstance'),
|
||||
(r'^([a-zA-Z0-9-]+)/$', 'PygmentsInstance'),
|
||||
)
|
||||
|
|
|
@ -11,10 +11,19 @@ from pygments import highlight
|
|||
from forms import PygmentsForm
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
import uuid
|
||||
import operator
|
||||
|
||||
# We need somewhere to store the code that we highlight
|
||||
HIGHLIGHTED_CODE_DIR = os.path.join(settings.MEDIA_ROOT, 'pygments')
|
||||
MAX_FILES = 5
|
||||
|
||||
def remove_oldest_files(dir, max_files):
|
||||
"""Remove the oldest files in a directory 'dir', leaving at most 'max_files' remaining"""
|
||||
filepaths = [os.path.join(dir, file) for file in os.listdir(dir)]
|
||||
ctime_sorted_paths = [item[0] for item in sorted([(path, os.path.getctime(path)) for path in filepaths],
|
||||
key=operator.itemgetter(1), reverse=True)]
|
||||
[os.remove(path) for path in ctime_sorted_paths[max_files:]]
|
||||
|
||||
|
||||
class HTMLEmitter(BaseEmitter):
|
||||
|
@ -24,38 +33,42 @@ class HTMLEmitter(BaseEmitter):
|
|||
|
||||
class PygmentsRoot(Resource):
|
||||
"""This example demonstrates a simple RESTful Web API aound the awesome pygments library.
|
||||
This top level resource is used to create highlighted code snippets."""
|
||||
This top level resource is used to create highlighted code snippets, and to list all the existing code snippets."""
|
||||
form = PygmentsForm
|
||||
allowed_methods = anon_allowed_methods = ('POST',)
|
||||
allowed_methods = anon_allowed_methods = ('GET', 'POST',)
|
||||
|
||||
def get(self, request, auth):
|
||||
"""Return a list of all currently existing snippets."""
|
||||
unique_ids = sorted(os.listdir(HIGHLIGHTED_CODE_DIR))
|
||||
return [self.reverse(PygmentsInstance, unique_id) for unique_id in unique_ids]
|
||||
|
||||
def post(self, request, auth, content):
|
||||
# Generate a unique id by hashing the input
|
||||
input_str = ''.join(['%s%s' % (key, content[key]) for key in sorted(content.keys())])
|
||||
hash = hashlib.md5()
|
||||
hash.update(input_str)
|
||||
unique_id = hash.hexdigest()
|
||||
"""Create a new highlighed snippet and return it's location.
|
||||
For the purposes of the sandbox example, also ensure we delete the oldest snippets if we have > MAX_FILES."""
|
||||
unique_id = str(uuid.uuid1())
|
||||
pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
|
||||
|
||||
if not os.path.exists(pathname):
|
||||
# We only need to generate the file if it doesn't already exist.
|
||||
options = {'title': content['title']} if content['title'] else {}
|
||||
linenos = 'table' if content['linenos'] else False
|
||||
lexer = get_lexer_by_name(content['lexer'])
|
||||
formatter = HtmlFormatter(style=content['style'], linenos=linenos, full=True, **options)
|
||||
|
||||
with open(pathname, 'w') as outfile:
|
||||
highlight(content['code'], lexer, formatter, outfile)
|
||||
|
||||
lexer = get_lexer_by_name(content['lexer'])
|
||||
linenos = 'table' if content['linenos'] else False
|
||||
options = {'title': content['title']} if content['title'] else {}
|
||||
formatter = HtmlFormatter(style=content['style'], linenos=linenos, full=True, **options)
|
||||
|
||||
with open(pathname, 'w') as outfile:
|
||||
highlight(content['code'], lexer, formatter, outfile)
|
||||
|
||||
remove_oldest_files(HIGHLIGHTED_CODE_DIR, MAX_FILES)
|
||||
|
||||
return Response(status.HTTP_201_CREATED, headers={'Location': self.reverse(PygmentsInstance, unique_id)})
|
||||
|
||||
|
||||
class PygmentsInstance(Resource):
|
||||
"""Simply return the stored highlighted HTML file with the correct mime type.
|
||||
This Resource only emits HTML and uses a standard HTML emitter rather than FlyWheel's DocumentingHTMLEmitter class."""
|
||||
This Resource only emits HTML and uses a standard HTML emitter rather than the emitters.DocumentingHTMLEmitter class."""
|
||||
allowed_methods = anon_allowed_methods = ('GET',)
|
||||
emitters = (HTMLEmitter,)
|
||||
|
||||
def get(self, request, auth, unique_id):
|
||||
"""Return the highlighted snippet."""
|
||||
pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
|
||||
if not os.path.exists(pathname):
|
||||
return Resource(status.HTTP_404_NOT_FOUND)
|
||||
|
|
|
@ -7,7 +7,6 @@ urlpatterns = patterns('',
|
|||
(r'^pygments-example/', include('pygments_api.urls')),
|
||||
(r'^blog-post-example/', include('blogpost.urls')),
|
||||
(r'^object-store-example/', include('objectstore.urls')),
|
||||
(r'^testarchive-example/', include('testarchive.urls')),
|
||||
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
|
||||
(r'^accounts/logout/$', 'django.contrib.auth.views.logout'),
|
||||
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
|
|
|
@ -83,16 +83,15 @@ class DocumentingTemplateEmitter(BaseEmitter):
|
|||
provide a form that can be used to submit arbitrary content."""
|
||||
# Get the form instance if we have one bound to the input
|
||||
form_instance = resource.form_instance
|
||||
print form_instance
|
||||
|
||||
# Otherwise if this isn't an error response
|
||||
# then attempt to get a form bound to the response object
|
||||
if not form_instance and resource.response.has_content_body:
|
||||
try:
|
||||
form_instance = resource.get_form(resource.response.raw_content)
|
||||
if form_instance:
|
||||
form_instance.is_valid()
|
||||
except:
|
||||
pass
|
||||
if form_instance and not form_instance.is_valid():
|
||||
form_instance = None
|
||||
|
||||
# If we still don't have a form instance then try to get an unbound form
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
"""TODO: docs
|
||||
"""
|
||||
from django.forms import ModelForm
|
||||
from django.db.models.query import QuerySet
|
||||
from django.db.models import Model
|
||||
|
@ -379,9 +377,20 @@ class ModelResource(Resource):
|
|||
return
|
||||
|
||||
|
||||
class RootModelResource(ModelResource):
|
||||
"""A Resource which provides default operations for list and create."""
|
||||
allowed_methods = ('GET', 'POST')
|
||||
queryset = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
queryset = self.queryset if self.queryset else self.model.objects.all()
|
||||
return queryset
|
||||
|
||||
|
||||
class QueryModelResource(ModelResource):
|
||||
allowed_methods = ('read',)
|
||||
"""Resource with default operations for list.
|
||||
TODO: provide filter/order/num_results/paging, and a create operation to create queries."""
|
||||
allowed_methods = ('GET',)
|
||||
queryset = None
|
||||
|
||||
def get_form(self, data=None):
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class='header'>
|
||||
<span class='api'><a href='http://www.thewebhaswon.com/flywheel/'>FlyWheel API</a></span>
|
||||
<span class='api'><a href='http://django-rest-framework.org'>Django REST framework</a></span>
|
||||
<span class='auth'>{% if user.is_active %}Welcome, {{ user }}.{% if logout_url %} <a href='{{ logout_url }}'>Log out</a>{% endif %}{% else %}Not logged in {% if login_url %}<a href='{{ login_url }}'>Log in</a>{% endif %}{% endif %}</span>
|
||||
</div>
|
||||
<div class='content'>
|
||||
|
|
|
@ -125,7 +125,6 @@ class XML2Dict(object):
|
|||
"""
|
||||
result = re.compile("\{(.*)\}(.*)").search(tag)
|
||||
if result:
|
||||
print tag
|
||||
value.namespace, tag = result.groups()
|
||||
return (tag, value)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user