From 805aa03ec1871f6a766d9052b348ddce9e9843c3 Mon Sep 17 00:00:00 2001 From: tom christie tom@tomchristie.com Date: Sat, 19 Feb 2011 10:26:27 +0000 Subject: Yowzers. Final big bunch of refactoring for 0.1 release. Now support Django 1.3's views, admin style api is all polished off, loads of tests, new test project for running the test. All sorts of goodness. Getting ready to push this out now. --- examples/blogpost/models.py | 8 +++--- examples/blogpost/urls.py | 13 +++++----- examples/blogpost/views.py | 22 ++++++++-------- examples/mixin/__init__.py | 0 examples/mixin/urls.py | 23 +++++++++++++++++ examples/modelresourceexample/models.py | 2 +- examples/modelresourceexample/urls.py | 5 ++-- examples/objectstore/urls.py | 9 ++++--- examples/objectstore/views.py | 9 ++++--- examples/pygments_api/urls.py | 9 ++++--- examples/pygments_api/views.py | 6 +++-- examples/requirements.txt | 2 ++ examples/resourceexample/urls.py | 7 +++--- examples/resourceexample/views.py | 5 ++-- examples/sandbox/__init__.py | 0 examples/sandbox/views.py | 35 ++++++++++++++++++++++++++ examples/settings.py | 17 +++++++------ examples/templates/base.html | 7 ------ examples/templates/registration/login.html | 26 ------------------- examples/urls.py | 40 ++++++++---------------------- 20 files changed, 133 insertions(+), 112 deletions(-) create mode 100644 examples/mixin/__init__.py create mode 100644 examples/mixin/urls.py create mode 100644 examples/sandbox/__init__.py create mode 100644 examples/sandbox/views.py delete mode 100644 examples/templates/base.html delete mode 100644 examples/templates/registration/login.html (limited to 'examples') diff --git a/examples/blogpost/models.py b/examples/blogpost/models.py index e1968415..c85ca788 100644 --- a/examples/blogpost/models.py +++ b/examples/blogpost/models.py @@ -24,13 +24,13 @@ class BlogPost(models.Model): @models.permalink def get_absolute_url(self): - return ('blogpost.views.BlogPostInstance', (), {'key': self.key}) + return ('blog-post', (), {'key': self.key}) @property @models.permalink def comments_url(self): """Link to a resource which lists all comments for this blog post.""" - return ('blogpost.views.CommentRoot', (), {'blogpost_id': self.key}) + return ('comments', (), {'blogpost_id': self.key}) def __unicode__(self): return self.title @@ -52,11 +52,11 @@ class Comment(models.Model): @models.permalink def get_absolute_url(self): - return ('blogpost.views.CommentInstance', (), {'blogpost': self.blogpost.key, 'id': self.id}) + return ('comment', (), {'blogpost': self.blogpost.key, 'id': self.id}) @property @models.permalink def blogpost_url(self): """Link to the blog post resource which this comment corresponds to.""" - return ('blogpost.views.BlogPostInstance', (), {'key': self.blogpost.key}) + return ('blog-post', (), {'key': self.blogpost.key}) diff --git a/examples/blogpost/urls.py b/examples/blogpost/urls.py index ee209b3e..b2df96a1 100644 --- a/examples/blogpost/urls.py +++ b/examples/blogpost/urls.py @@ -1,8 +1,9 @@ -from django.conf.urls.defaults import patterns +from django.conf.urls.defaults import patterns, url +from blogpost.views import BlogPosts, BlogPostInstance, Comments, CommentInstance -urlpatterns = patterns('blogpost.views', - (r'^$', 'BlogPostRoot'), - (r'^(?P[^/]+)/$', 'BlogPostInstance'), - (r'^(?P[^/]+)/comments/$', 'CommentRoot'), - (r'^(?P[^/]+)/comments/(?P[^/]+)/$', 'CommentInstance'), +urlpatterns = patterns('', + url(r'^$', BlogPosts.as_view(), name='blog-posts'), + url(r'^(?P[^/]+)/$', BlogPostInstance.as_view(), name='blog-post'), + url(r'^(?P[^/]+)/comments/$', Comments.as_view(), name='comments'), + url(r'^(?P[^/]+)/comments/(?P[^/]+)/$', CommentInstance.as_view(), name='comment'), ) diff --git a/examples/blogpost/views.py b/examples/blogpost/views.py index bfb53b5d..0377b447 100644 --- a/examples/blogpost/views.py +++ b/examples/blogpost/views.py @@ -1,33 +1,33 @@ from djangorestframework.response import Response, status from djangorestframework.resource import Resource from djangorestframework.modelresource import ModelResource, RootModelResource -from blogpost.models import BlogPost, Comment +from blogpost import models BLOG_POST_FIELDS = ('created', 'title', 'slug', 'content', 'absolute_url', 'comment_url', 'comments_url') COMMENT_FIELDS = ('username', 'comment', 'created', 'rating', 'absolute_url', 'blogpost_url') -class BlogPostRoot(RootModelResource): +class BlogPosts(RootModelResource): """A resource with which lists all existing blog posts and creates new blog posts.""" - allowed_methods = ('GET', 'POST',) - model = BlogPost + anon_allowed_methods = allowed_methods = ('GET', 'POST',) + model = models.BlogPost fields = BLOG_POST_FIELDS class BlogPostInstance(ModelResource): """A resource which represents a single blog post.""" - allowed_methods = ('GET', 'PUT', 'DELETE') - model = BlogPost + anon_allowed_methods = allowed_methods = ('GET', 'PUT', 'DELETE') + model = models.BlogPost fields = BLOG_POST_FIELDS -class CommentRoot(RootModelResource): +class Comments(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 + anon_allowed_methods = allowed_methods = ('GET', 'POST',) + model = models.Comment fields = COMMENT_FIELDS class CommentInstance(ModelResource): """A resource which represents a single comment.""" - allowed_methods = ('GET', 'PUT', 'DELETE') - model = Comment + anon_allowed_methods = allowed_methods = ('GET', 'PUT', 'DELETE') + model = models.Comment fields = COMMENT_FIELDS diff --git a/examples/mixin/__init__.py b/examples/mixin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/mixin/urls.py b/examples/mixin/urls.py new file mode 100644 index 00000000..05009284 --- /dev/null +++ b/examples/mixin/urls.py @@ -0,0 +1,23 @@ +from djangorestframework.compat import View # Use Django 1.3's django.views.generic.View, or fall back to a clone of that if Django < 1.3 +from djangorestframework.emitters import EmitterMixin, DEFAULT_EMITTERS +from djangorestframework.response import Response + +from django.conf.urls.defaults import patterns, url +from django.core.urlresolvers import reverse + + +class ExampleView(EmitterMixin, View): + """An example view using Django 1.3's class based views. + Uses djangorestframework's EmitterMixin to provide support for multiple output formats.""" + emitters = DEFAULT_EMITTERS + + def get(self, request): + response = Response(200, {'description': 'Some example content', + 'url': reverse('mixin-view')}) + return self.emit(response) + + +urlpatterns = patterns('', + url(r'^$', ExampleView.as_view(), name='mixin-view'), +) + diff --git a/examples/modelresourceexample/models.py b/examples/modelresourceexample/models.py index 036501d0..16047524 100644 --- a/examples/modelresourceexample/models.py +++ b/examples/modelresourceexample/models.py @@ -19,5 +19,5 @@ class MyModel(models.Model): @models.permalink def get_absolute_url(self): - return ('modelresourceexample.views.MyModelResource', (self.pk,)) + return ('my-model-resource', (self.pk,)) diff --git a/examples/modelresourceexample/urls.py b/examples/modelresourceexample/urls.py index c43cf56a..53d950cd 100644 --- a/examples/modelresourceexample/urls.py +++ b/examples/modelresourceexample/urls.py @@ -1,6 +1,7 @@ from django.conf.urls.defaults import patterns, url +from modelresourceexample.views import MyModelRootResource, MyModelResource urlpatterns = patterns('modelresourceexample.views', - url(r'^$', 'MyModelRootResource'), - url(r'^([0-9]+)/$', 'MyModelResource'), + url(r'^$', MyModelRootResource.as_view(), name='my-model-root-resource'), + url(r'^([0-9]+)/$', MyModelResource.as_view(), name='my-model-resource'), ) diff --git a/examples/objectstore/urls.py b/examples/objectstore/urls.py index c04e731e..2c685f59 100644 --- a/examples/objectstore/urls.py +++ b/examples/objectstore/urls.py @@ -1,6 +1,7 @@ -from django.conf.urls.defaults import patterns - +from django.conf.urls.defaults import patterns, url +from objectstore.views import ObjectStoreRoot, StoredObject + urlpatterns = patterns('objectstore.views', - (r'^$', 'ObjectStoreRoot'), - (r'^(?P[A-Za-z0-9_-]{1,64})/$', 'StoredObject'), + url(r'^$', ObjectStoreRoot.as_view(), name='object-store-root'), + url(r'^(?P[A-Za-z0-9_-]{1,64})/$', StoredObject.as_view(), name='stored-object'), ) diff --git a/examples/objectstore/views.py b/examples/objectstore/views.py index e1b239dc..b3ed5533 100644 --- a/examples/objectstore/views.py +++ b/examples/objectstore/views.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.core.urlresolvers import reverse from djangorestframework.resource import Resource from djangorestframework.response import Response, status @@ -29,7 +30,7 @@ class ObjectStoreRoot(Resource): def get(self, request, auth): """Return a list of all the stored object URLs.""" keys = sorted(os.listdir(OBJECT_STORE_DIR)) - return [self.reverse(StoredObject, key=key) for key in keys] + return [reverse('stored-object', kwargs={'key':key}) for key in keys] def post(self, request, auth, content): """Create a new stored object, with a unique key.""" @@ -37,9 +38,9 @@ class ObjectStoreRoot(Resource): pathname = os.path.join(OBJECT_STORE_DIR, key) pickle.dump(content, open(pathname, 'wb')) remove_oldest_files(OBJECT_STORE_DIR, MAX_FILES) - return Response(status.HTTP_201_CREATED, content, {'Location': self.reverse(StoredObject, key=key)}) - - + return Response(status.HTTP_201_CREATED, content, {'Location': reverse('stored-object', kwargs={'key':key})}) + + class StoredObject(Resource): """Represents a stored object. The object may be any picklable content.""" diff --git a/examples/pygments_api/urls.py b/examples/pygments_api/urls.py index f96f4518..905e31c5 100644 --- a/examples/pygments_api/urls.py +++ b/examples/pygments_api/urls.py @@ -1,6 +1,7 @@ -from django.conf.urls.defaults import patterns +from django.conf.urls.defaults import patterns, url +from pygments_api.views import PygmentsRoot, PygmentsInstance -urlpatterns = patterns('pygments_api.views', - (r'^$', 'PygmentsRoot'), - (r'^([a-zA-Z0-9-]+)/$', 'PygmentsInstance'), +urlpatterns = patterns('', + url(r'^$', PygmentsRoot.as_view(), name='pygments-root'), + url(r'^([a-zA-Z0-9-]+)/$', PygmentsInstance.as_view(), name='pygments-instance'), ) diff --git a/examples/pygments_api/views.py b/examples/pygments_api/views.py index e22705d9..84e5e703 100644 --- a/examples/pygments_api/views.py +++ b/examples/pygments_api/views.py @@ -1,4 +1,6 @@ +from __future__ import with_statement # for python 2.5 from django.conf import settings +from django.core.urlresolvers import reverse from djangorestframework.resource import Resource from djangorestframework.response import Response, status @@ -41,7 +43,7 @@ class PygmentsRoot(Resource): 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] + return [reverse('pygments-instance', args=[unique_id]) for unique_id in unique_ids] def post(self, request, auth, content): """Create a new highlighed snippet and return it's location. @@ -59,7 +61,7 @@ class PygmentsRoot(Resource): 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': reverse('pygments-instance', args=[unique_id])}) class PygmentsInstance(Resource): diff --git a/examples/requirements.txt b/examples/requirements.txt index 4ae9e3c7..09cda945 100644 --- a/examples/requirements.txt +++ b/examples/requirements.txt @@ -4,3 +4,5 @@ Django==1.2.4 wsgiref==0.1.2 Pygments==1.4 httplib2==0.6.0 +Markdown==2.0.3 + diff --git a/examples/resourceexample/urls.py b/examples/resourceexample/urls.py index 828caef2..cb6435bb 100644 --- a/examples/resourceexample/urls.py +++ b/examples/resourceexample/urls.py @@ -1,6 +1,7 @@ from django.conf.urls.defaults import patterns, url +from resourceexample.views import ExampleResource, AnotherExampleResource -urlpatterns = patterns('resourceexample.views', - url(r'^$', 'ExampleResource'), - url(r'^(?P[0-9]+)/$', 'AnotherExampleResource'), +urlpatterns = patterns('', + url(r'^$', ExampleResource.as_view(), name='example-resource'), + url(r'^(?P[0-9]+)/$', AnotherExampleResource.as_view(), name='another-example-resource'), ) diff --git a/examples/resourceexample/views.py b/examples/resourceexample/views.py index e5bb5efa..c4462ee2 100644 --- a/examples/resourceexample/views.py +++ b/examples/resourceexample/views.py @@ -1,13 +1,14 @@ +from django.core.urlresolvers import reverse from djangorestframework.resource import Resource from djangorestframework.response import Response, status from resourceexample.forms import MyForm class ExampleResource(Resource): - """A basic read only resource that points to 3 other resources.""" + """A basic read-only resource that points to 3 other resources.""" allowed_methods = anon_allowed_methods = ('GET',) def get(self, request, auth): - return {"Some other resources": [self.reverse(AnotherExampleResource, num=num) for num in range(3)]} + return {"Some other resources": [reverse('another-example-resource', kwargs={'num':num}) for num in range(3)]} class AnotherExampleResource(Resource): """A basic GET-able/POST-able resource.""" diff --git a/examples/sandbox/__init__.py b/examples/sandbox/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/sandbox/views.py b/examples/sandbox/views.py new file mode 100644 index 00000000..561bdb1d --- /dev/null +++ b/examples/sandbox/views.py @@ -0,0 +1,35 @@ +"""The root view for the examples provided with Django REST framework""" + +from django.core.urlresolvers import reverse +from djangorestframework.resource import Resource + + +class Sandbox(Resource): + """This is the sandbox for the examples provided with [Django REST framework](http://django-rest-framework.org). + + These examples are provided to help you get a better idea of the some of the features of RESTful APIs created using the framework. + + All the example APIs allow anonymous access, and can be navigated either through the browser or from the command line... + + bash: curl -X GET http://api.django-rest-framework.org/ # (Use default emitter) + bash: curl -X GET http://api.django-rest-framework.org/ -H 'Accept: text/plain' # (Use plaintext documentation emitter) + + The examples provided: + + 1. A basic example using the [Resource](http://django-rest-framework.org/library/resource.html) class. + 2. A basic example using the [ModelResource](http://django-rest-framework.org/library/modelresource.html) class. + 3. An basic example using Django 1.3's [class based views](http://docs.djangoproject.com/en/dev/topics/class-based-views/) and djangorestframework's [EmitterMixin](http://django-rest-framework.org/library/emitters.html). + 4. A generic object store API. + 5. A code highlighting API. + 6. A blog posts and comments API. + + Please feel free to browse, create, edit and delete the resources in these examples.""" + allowed_methods = anon_allowed_methods = ('GET',) + + def get(self, request, auth): + return [{'name': 'Simple Resource example', 'url': reverse('example-resource')}, + {'name': 'Simple ModelResource example', 'url': reverse('my-model-root-resource')}, + {'name': 'Simple Mixin-only example', 'url': reverse('mixin-view')}, + {'name': 'Object store API', 'url': reverse('object-store-root')}, + {'name': 'Code highlighting API', 'url': reverse('pygments-root')}, + {'name': 'Blog posts API', 'url': reverse('blog-posts')}] diff --git a/examples/settings.py b/examples/settings.py index 865dc394..c4cdd992 100644 --- a/examples/settings.py +++ b/examples/settings.py @@ -1,7 +1,4 @@ -# Django settings for src project. -import os - -BASE_DIR = os.path.dirname(__file__) +# Settings for djangorestframework examples project DEBUG = True TEMPLATE_DEBUG = DEBUG @@ -48,17 +45,23 @@ USE_L10N = True # Absolute filesystem path to the directory that will hold user-uploaded files. # Example: "/home/media/media.lawrence.com/" +# NOTE: Some of the djangorestframework examples use MEDIA_ROOT to store content. MEDIA_ROOT = 'media/' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # 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 = '' # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a # trailing slash. # Examples: "http://foo.com/media/", "/media/". -ADMIN_MEDIA_PREFIX = '/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 = '/admin-media/' # Make this unique, and don't share it with anybody. SECRET_KEY = 't&9mru2_k$t8e2-9uq-wu2a1)9v*us&j3i#lsqkt(lbx*vh1cu' @@ -84,16 +87,16 @@ TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. - os.path.join(BASE_DIR, 'templates') ) + INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', - #'django.contrib.admin', + 'djangorestframework', 'resourceexample', diff --git a/examples/templates/base.html b/examples/templates/base.html deleted file mode 100644 index 1ff37dab..00000000 --- a/examples/templates/base.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - -{% block content %}{% endblock %} - - \ No newline at end of file diff --git a/examples/templates/registration/login.html b/examples/templates/registration/login.html deleted file mode 100644 index 9d0b481b..00000000 --- a/examples/templates/registration/login.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - -{% if form.errors %} -

Your username and password didn't match. Please try again.

-{% endif %} - -
-{% csrf_token %} - - - - - - - - - -
{{ form.username.label_tag }}{{ form.username }}
{{ form.password.label_tag }}{{ form.password }}
- - - -
- -{% endblock %} \ No newline at end of file diff --git a/examples/urls.py b/examples/urls.py index 41b80d58..03894e4e 100644 --- a/examples/urls.py +++ b/examples/urls.py @@ -1,37 +1,19 @@ -from django.conf.urls.defaults import patterns, include -#from django.contrib import admin -from djangorestframework.resource import Resource +from django.conf.urls.defaults import patterns, include, url +from sandbox.views import Sandbox -#admin.autodiscover() +urlpatterns = patterns('djangorestframework.views', + (r'robots.txt', 'deny_robots'), + (r'favicon.ico', 'favicon'), -class RootResource(Resource): - """This is the sandbox for the examples provided with django-rest-framework. + (r'^$', Sandbox.as_view()), - These examples are here to help you get a better idea of the some of the - features of django-rest-framework API, such as automatic form and model validation, - support for multiple input and output media types, etc... - - Please feel free to browse, create, edit and delete the resources here, either - in the browser, from the command line, or programmatically.""" - allowed_methods = anon_allowed_methods = ('GET',) - - def get(self, request, auth): - return {'Simple Resource example': self.reverse('resourceexample.views.ExampleResource'), - 'Simple ModelResource example': self.reverse('modelresourceexample.views.MyModelRootResource'), - 'Object store API (Resource)': self.reverse('objectstore.views.ObjectStoreRoot'), - 'A pygments pastebin API (Resource + forms)': self.reverse('pygments_api.views.PygmentsRoot'), - 'Blog posts API (ModelResource)': self.reverse('blogpost.views.BlogPostRoot'),} - - -urlpatterns = patterns('', - (r'^$', RootResource), - (r'^model-resource-example/', include('modelresourceexample.urls')), (r'^resource-example/', include('resourceexample.urls')), + (r'^model-resource-example/', include('modelresourceexample.urls')), + (r'^mixin/', include('mixin.urls')), (r'^object-store/', include('objectstore.urls')), (r'^pygments/', include('pygments_api.urls')), (r'^blog-post/', include('blogpost.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')), - #(r'^admin/', include(admin.site.urls)), + + (r'^accounts/login/$', 'api_login'), + (r'^accounts/logout/$', 'api_logout'), ) -- cgit v1.2.3