Lots of docs, trying to tidy up examples...

This commit is contained in:
tom christie tom@tomchristie.com 2011-01-30 11:00:20 +00:00
parent 40f47a9fb3
commit 250ab0f609
23 changed files with 498 additions and 105 deletions

View File

@ -47,7 +47,7 @@ source_suffix = '.rst'
master_doc = 'index' master_doc = 'index'
# General information about the project. # General information about the project.
project = u'FlyWheel' project = u'django-rest-framework'
copyright = u'2011, Tom Christie' copyright = u'2011, Tom Christie'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for

View 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:

View 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:

View 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
View 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/

View File

@ -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. * Clean, simple, class-based views for Resources.
* Support for ModelResources with nice default implementations and input validation. * Support for ModelResources with nice default implementations and input validation.
@ -16,21 +16,76 @@ Some of FlyWheel's features:
Installation & Setup 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 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 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 Library Reference
----------------- -----------------
@ -38,12 +93,12 @@ Library Reference
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
resource library/resource
modelresource library/modelresource
emitters library/emitters
parsers library/parsers
authenticators library/authenticators
response library/response
Indices and tables Indices and tables

View 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

View 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:

View File

@ -0,0 +1,9 @@
:mod:`modelresource`
====================
.. note::
TODO - document this module properly
.. automodule:: modelresource
:members:

5
docs/library/parsers.rst Normal file
View File

@ -0,0 +1,5 @@
:mod:`parsers`
==============
.. automodule:: parsers
:members:

136
docs/library/resource.rst Normal file
View 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

View File

@ -0,0 +1,5 @@
:mod:`response`
===============
.. automodule:: response
:members:

View File

@ -30,13 +30,7 @@ class BlogPost(models.Model):
@models.permalink @models.permalink
def comments_url(self): def comments_url(self):
"""Link to a resource which lists all comments for this blog post.""" """Link to a resource which lists all comments for this blog post."""
return ('blogpost.views.CommentList', (), {'blogpost_id': self.key}) return ('blogpost.views.CommentRoot', (), {'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})
def __unicode__(self): def __unicode__(self):
return self.title return self.title

View File

@ -1,11 +1,8 @@
from django.conf.urls.defaults import patterns from django.conf.urls.defaults import patterns
urlpatterns = patterns('blogpost.views', urlpatterns = patterns('blogpost.views',
(r'^$', 'RootResource'), (r'^$', 'BlogPostRoot'),
(r'^blog-posts/$', 'BlogPostList'), (r'^(?P<key>[^/]+)/$', 'BlogPostInstance'),
(r'^blog-post/$', 'BlogPostCreator'), (r'^(?P<blogpost_id>[^/]+)/comments/$', 'CommentRoot'),
(r'^blog-post/(?P<key>[^/]+)/$', 'BlogPostInstance'), (r'^(?P<blogpost>[^/]+)/comments/(?P<id>[^/]+)/$', 'CommentInstance'),
(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'),
) )

View File

@ -1,34 +1,15 @@
from flywheel.response import Response, status from flywheel.response import Response, status
from flywheel.resource import Resource from flywheel.resource import Resource
from flywheel.modelresource import ModelResource, QueryModelResource from flywheel.modelresource import ModelResource, RootModelResource
from blogpost.models import BlogPost, Comment 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') 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): class BlogPostRoot(RootModelResource):
"""A resource with which blog posts may be created.""" """A resource with which lists all existing blog posts and creates new blog posts."""
allowed_methods = ('POST',) allowed_methods = ('GET', 'POST',)
model = BlogPost model = BlogPost
fields = BLOG_POST_FIELDS fields = BLOG_POST_FIELDS
@ -38,20 +19,9 @@ class BlogPostInstance(ModelResource):
model = BlogPost model = BlogPost
fields = BLOG_POST_FIELDS fields = BLOG_POST_FIELDS
class CommentRoot(RootModelResource):
##### Comment Resources ##### """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',)
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',)
model = Comment model = Comment
fields = COMMENT_FIELDS fields = COMMENT_FIELDS

View File

@ -3,23 +3,26 @@ from django import forms
from pygments.lexers import get_all_lexers from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles 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()]) 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())) STYLE_CHOICES = sorted((item, item) for item in list(get_all_styles()))
class PygmentsForm(forms.Form): class PygmentsForm(forms.Form):
"""A simple form with some of the most important pygments settings. """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. 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.""" 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.)') help_text='(Copy and paste the code text here.)')
title = forms.CharField(required=False, help_text='(Optional)', max_length=100) title = forms.CharField(required=False,
linenos = forms.BooleanField(label='Show Line Numbers', required=False) help_text='(Optional)',
lexer = forms.ChoiceField(choices=LEXER_CHOICES, initial='python') max_length=100)
style = forms.ChoiceField(choices=STYLE_CHOICES, initial='friendly') 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')

View File

@ -2,5 +2,5 @@ from django.conf.urls.defaults import patterns
urlpatterns = patterns('pygments_api.views', urlpatterns = patterns('pygments_api.views',
(r'^$', 'PygmentsRoot'), (r'^$', 'PygmentsRoot'),
(r'^([a-zA-Z0-9]+)/$', 'PygmentsInstance'), (r'^([a-zA-Z0-9-]+)/$', 'PygmentsInstance'),
) )

View File

@ -11,10 +11,19 @@ from pygments import highlight
from forms import PygmentsForm from forms import PygmentsForm
import os import os
import hashlib import uuid
import operator
# We need somewhere to store the code that we highlight # We need somewhere to store the code that we highlight
HIGHLIGHTED_CODE_DIR = os.path.join(settings.MEDIA_ROOT, 'pygments') 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): class HTMLEmitter(BaseEmitter):
@ -24,38 +33,42 @@ class HTMLEmitter(BaseEmitter):
class PygmentsRoot(Resource): class PygmentsRoot(Resource):
"""This example demonstrates a simple RESTful Web API aound the awesome pygments library. """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 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): def post(self, request, auth, content):
# Generate a unique id by hashing the input """Create a new highlighed snippet and return it's location.
input_str = ''.join(['%s%s' % (key, content[key]) for key in sorted(content.keys())]) For the purposes of the sandbox example, also ensure we delete the oldest snippets if we have > MAX_FILES."""
hash = hashlib.md5() unique_id = str(uuid.uuid1())
hash.update(input_str)
unique_id = hash.hexdigest()
pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id) pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
if not os.path.exists(pathname): lexer = get_lexer_by_name(content['lexer'])
# We only need to generate the file if it doesn't already exist. linenos = 'table' if content['linenos'] else False
options = {'title': content['title']} if content['title'] else {} options = {'title': content['title']} if content['title'] else {}
linenos = 'table' if content['linenos'] else False formatter = HtmlFormatter(style=content['style'], linenos=linenos, full=True, **options)
lexer = get_lexer_by_name(content['lexer'])
formatter = HtmlFormatter(style=content['style'], linenos=linenos, full=True, **options)
with open(pathname, 'w') as outfile: with open(pathname, 'w') as outfile:
highlight(content['code'], lexer, formatter, 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)}) return Response(status.HTTP_201_CREATED, headers={'Location': self.reverse(PygmentsInstance, unique_id)})
class PygmentsInstance(Resource): class PygmentsInstance(Resource):
"""Simply return the stored highlighted HTML file with the correct mime type. """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',) allowed_methods = anon_allowed_methods = ('GET',)
emitters = (HTMLEmitter,) emitters = (HTMLEmitter,)
def get(self, request, auth, unique_id): def get(self, request, auth, unique_id):
"""Return the highlighted snippet."""
pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id) pathname = os.path.join(HIGHLIGHTED_CODE_DIR, unique_id)
if not os.path.exists(pathname): if not os.path.exists(pathname):
return Resource(status.HTTP_404_NOT_FOUND) return Resource(status.HTTP_404_NOT_FOUND)

View File

@ -7,7 +7,6 @@ urlpatterns = patterns('',
(r'^pygments-example/', include('pygments_api.urls')), (r'^pygments-example/', include('pygments_api.urls')),
(r'^blog-post-example/', include('blogpost.urls')), (r'^blog-post-example/', include('blogpost.urls')),
(r'^object-store-example/', include('objectstore.urls')), (r'^object-store-example/', include('objectstore.urls')),
(r'^testarchive-example/', include('testarchive.urls')),
(r'^accounts/login/$', 'django.contrib.auth.views.login'), (r'^accounts/login/$', 'django.contrib.auth.views.login'),
(r'^accounts/logout/$', 'django.contrib.auth.views.logout'), (r'^accounts/logout/$', 'django.contrib.auth.views.logout'),
(r'^admin/doc/', include('django.contrib.admindocs.urls')), (r'^admin/doc/', include('django.contrib.admindocs.urls')),

View File

@ -83,16 +83,15 @@ class DocumentingTemplateEmitter(BaseEmitter):
provide a form that can be used to submit arbitrary content.""" provide a form that can be used to submit arbitrary content."""
# Get the form instance if we have one bound to the input # Get the form instance if we have one bound to the input
form_instance = resource.form_instance form_instance = resource.form_instance
print form_instance
# Otherwise if this isn't an error response # Otherwise if this isn't an error response
# then attempt to get a form bound to the response object # then attempt to get a form bound to the response object
if not form_instance and resource.response.has_content_body: if not form_instance and resource.response.has_content_body:
try: try:
form_instance = resource.get_form(resource.response.raw_content) form_instance = resource.get_form(resource.response.raw_content)
if form_instance:
form_instance.is_valid()
except: except:
pass
if form_instance and not form_instance.is_valid():
form_instance = None form_instance = None
# If we still don't have a form instance then try to get an unbound form # If we still don't have a form instance then try to get an unbound form

View File

@ -1,5 +1,3 @@
"""TODO: docs
"""
from django.forms import ModelForm from django.forms import ModelForm
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.db.models import Model from django.db.models import Model
@ -379,9 +377,20 @@ class ModelResource(Resource):
return 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): 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 queryset = None
def get_form(self, data=None): def get_form(self, data=None):

View File

@ -23,7 +23,7 @@
</head> </head>
<body> <body>
<div class='header'> <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> <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>
<div class='content'> <div class='content'>

View File

@ -125,7 +125,6 @@ class XML2Dict(object):
""" """
result = re.compile("\{(.*)\}(.*)").search(tag) result = re.compile("\{(.*)\}(.*)").search(tag)
if result: if result:
print tag
value.namespace, tag = result.groups() value.namespace, tag = result.groups()
return (tag, value) return (tag, value)