From 42f2f9b40d1295e18a5b720b0d1f6ad85e928d8a Mon Sep 17 00:00:00 2001 From: "tom christie tom@tomchristie.com" Date: Sun, 30 Jan 2011 18:30:39 +0000 Subject: [PATCH] Rename to django-rest-framework, get simpleexample working --- README.txt | 20 +++++---- {flywheel => djangorestframework}/__init__.py | 0 .../authenticators.py | 0 {flywheel => djangorestframework}/emitters.py | 8 ++-- .../modelresource.py | 42 +++++++++++++------ {flywheel => djangorestframework}/parsers.py | 2 +- {flywheel => djangorestframework}/resource.py | 4 +- {flywheel => djangorestframework}/response.py | 0 .../templates/emitter.html | 0 .../templates/emitter.txt | 0 .../templatetags/__init__.py | 0 .../templatetags/add_query_param.py | 0 .../templatetags/urlize_quoted_links.py | 2 +- {flywheel => djangorestframework}/utils.py | 0 docs/conf.py | 2 +- docs/index.rst | 28 ++++--------- docs/requirements.txt | 8 ++++ examples/blogpost/views.py | 6 +-- examples/objectstore/views.py | 4 +- examples/pygments_api/views.py | 6 +-- examples/requirements.txt | 6 +++ examples/settings.py | 9 ++-- examples/simpleexample/__init__.py | 0 examples/simpleexample/models.py | 23 ++++++++++ examples/simpleexample/urls.py | 6 +++ examples/simpleexample/views.py | 18 ++++++++ examples/urls.py | 27 ++++++++---- requirements.txt | 2 + 28 files changed, 155 insertions(+), 68 deletions(-) rename {flywheel => djangorestframework}/__init__.py (100%) rename {flywheel => djangorestframework}/authenticators.py (100%) rename {flywheel => djangorestframework}/emitters.py (96%) rename {flywheel => djangorestframework}/modelresource.py (90%) rename {flywheel => djangorestframework}/parsers.py (98%) rename {flywheel => djangorestframework}/resource.py (99%) rename {flywheel => djangorestframework}/response.py (100%) rename {flywheel => djangorestframework}/templates/emitter.html (100%) rename {flywheel => djangorestframework}/templates/emitter.txt (100%) rename {flywheel => djangorestframework}/templatetags/__init__.py (100%) rename {flywheel => djangorestframework}/templatetags/add_query_param.py (100%) rename {flywheel => djangorestframework}/templatetags/urlize_quoted_links.py (99%) rename {flywheel => djangorestframework}/utils.py (100%) create mode 100644 docs/requirements.txt create mode 100644 examples/requirements.txt create mode 100644 examples/simpleexample/__init__.py create mode 100644 examples/simpleexample/models.py create mode 100644 examples/simpleexample/urls.py create mode 100644 examples/simpleexample/views.py diff --git a/README.txt b/README.txt index 44e4f96de..784713b6d 100644 --- a/README.txt +++ b/README.txt @@ -1,17 +1,21 @@ -# To install django-rest-framework... -# -# Requirements: -# python2.6 -# virtualenv +# To install django-rest-framework in a virtualenv environment... hg clone https://tomchristie@bitbucket.org/tomchristie/django-rest-framework cd django-rest-framework/ virtualenv --no-site-packages --distribute --python=python2.6 env source ./env/bin/activate -pip install -r ./requirements.txt -python ./src/manage.py test +pip install -r requirements.txt # To build the documentation... -sphinx-build -c docs -b html -d cache docs html +pip install -r docs/requirements.txt +sphinx-build -c docs -b html -d docs-build docs html + +# To run the examples... + +pip install -r examples/requirements.txt +cd examples +export PYTHONPATH=.. +python manage.py syncdb +python manage.py runserver diff --git a/flywheel/__init__.py b/djangorestframework/__init__.py similarity index 100% rename from flywheel/__init__.py rename to djangorestframework/__init__.py diff --git a/flywheel/authenticators.py b/djangorestframework/authenticators.py similarity index 100% rename from flywheel/authenticators.py rename to djangorestframework/authenticators.py diff --git a/flywheel/emitters.py b/djangorestframework/emitters.py similarity index 96% rename from flywheel/emitters.py rename to djangorestframework/emitters.py index f548e1d93..a69407f17 100644 --- a/flywheel/emitters.py +++ b/djangorestframework/emitters.py @@ -1,5 +1,5 @@ """Emitters are used to serialize a Resource's output into specific media types. -FlyWheel also provides HTML and PlainText emitters that help self-document the API, +django-rest-framework also provides HTML and PlainText emitters that help self-document the API, by serializing the output along with documentation regarding the Resource, output status and headers, and providing forms and links depending on the allowed methods, emitters and parsers on the Resource. """ @@ -7,8 +7,8 @@ from django.conf import settings from django.template import RequestContext, loader from django import forms -from flywheel.response import NoContent -from flywheel.utils import dict2xml, url_resolves +from djangorestframework.response import NoContent +from djangorestframework.utils import dict2xml, url_resolves from urllib import quote_plus import string @@ -193,7 +193,7 @@ class XMLEmitter(BaseEmitter): class DocumentingHTMLEmitter(DocumentingTemplateEmitter): """Emitter which provides a browsable HTML interface for an API. - See the examples listed in the FlyWheel documentation to see this in actions.""" + See the examples listed in the django-rest-framework documentation to see this in actions.""" media_type = 'text/html' template = 'emitter.html' diff --git a/flywheel/modelresource.py b/djangorestframework/modelresource.py similarity index 90% rename from flywheel/modelresource.py rename to djangorestframework/modelresource.py index d68ec79e8..a9605d4ac 100644 --- a/flywheel/modelresource.py +++ b/djangorestframework/modelresource.py @@ -2,8 +2,8 @@ from django.forms import ModelForm from django.db.models.query import QuerySet from django.db.models import Model -from flywheel.response import status, Response, ResponseException -from flywheel.resource import Resource +from djangorestframework.response import status, Response, ResponseException +from djangorestframework.resource import Resource import decimal import inspect @@ -336,28 +336,41 @@ class ModelResource(Resource): return _any(data, self.fields) - def post(self, request, content, *args, **kwargs): + def post(self, request, auth, content, *args, **kwargs): # TODO: test creation on a non-existing resource url all_kw_args = dict(content.items() + kwargs.items()) - instance = self.model(**all_kw_args) + if args: + instance = self.model(pk=args[-1], **all_kw_args) + else: + instance = self.model(**all_kw_args) instance.save() headers = {} if hasattr(instance, 'get_absolute_url'): headers['Location'] = self.add_domain(instance.get_absolute_url()) return Response(status.HTTP_201_CREATED, instance, headers) - def get(self, request, *args, **kwargs): + def get(self, request, auth, *args, **kwargs): try: - instance = self.model.objects.get(**kwargs) + if args: + # If we have any none kwargs then assume the last represents the primrary key + instance = self.model.objects.get(pk=args[-1], **kwargs) + else: + # Otherwise assume the kwargs uniquely identify the model + instance = self.model.objects.get(**kwargs) except self.model.DoesNotExist: raise ResponseException(status.HTTP_404_NOT_FOUND) return instance - def put(self, request, content, *args, **kwargs): + def put(self, request, auth, content, *args, **kwargs): # TODO: update on the url of a non-existing resource url doesn't work correctly at the moment - will end up with a new url try: - instance = self.model.objects.get(**kwargs) + if args: + # If we have any none kwargs then assume the last represents the primrary key + instance = self.model.objects.get(pk=args[-1], **kwargs) + else: + # Otherwise assume the kwargs uniquely identify the model + instance = self.model.objects.get(**kwargs) for (key, val) in content.items(): setattr(instance, key, val) except self.model.DoesNotExist: @@ -367,9 +380,14 @@ class ModelResource(Resource): instance.save() return instance - def delete(self, request, *args, **kwargs): + def delete(self, request, auth, *args, **kwargs): try: - instance = self.model.objects.get(**kwargs) + if args: + # If we have any none kwargs then assume the last represents the primrary key + instance = self.model.objects.get(pk=args[-1], **kwargs) + else: + # Otherwise assume the kwargs uniquely identify the model + instance = self.model.objects.get(**kwargs) except self.model.DoesNotExist: raise ResponseException(status.HTTP_404_NOT_FOUND, None, {}) @@ -382,7 +400,7 @@ class RootModelResource(ModelResource): allowed_methods = ('GET', 'POST') queryset = None - def get(self, request, *args, **kwargs): + def get(self, request, auth, *args, **kwargs): queryset = self.queryset if self.queryset else self.model.objects.all() return queryset @@ -396,7 +414,7 @@ class QueryModelResource(ModelResource): def get_form(self, data=None): return None - def get(self, request, *args, **kwargs): + def get(self, request, auth, *args, **kwargs): queryset = self.queryset if self.queryset else self.model.objects.all() return queryset diff --git a/flywheel/parsers.py b/djangorestframework/parsers.py similarity index 98% rename from flywheel/parsers.py rename to djangorestframework/parsers.py index 98232a96f..a656e2ebb 100644 --- a/flywheel/parsers.py +++ b/djangorestframework/parsers.py @@ -1,4 +1,4 @@ -from flywheel.response import status, ResponseException +from djangorestframework.response import status, ResponseException try: import json diff --git a/flywheel/resource.py b/djangorestframework/resource.py similarity index 99% rename from flywheel/resource.py rename to djangorestframework/resource.py index 2a8554f3f..d06d51b01 100644 --- a/flywheel/resource.py +++ b/djangorestframework/resource.py @@ -2,8 +2,8 @@ from django.contrib.sites.models import Site from django.core.urlresolvers import reverse from django.http import HttpResponse -from flywheel import emitters, parsers, authenticators -from flywheel.response import status, Response, ResponseException +from djangorestframework import emitters, parsers, authenticators +from djangorestframework.response import status, Response, ResponseException from decimal import Decimal import re diff --git a/flywheel/response.py b/djangorestframework/response.py similarity index 100% rename from flywheel/response.py rename to djangorestframework/response.py diff --git a/flywheel/templates/emitter.html b/djangorestframework/templates/emitter.html similarity index 100% rename from flywheel/templates/emitter.html rename to djangorestframework/templates/emitter.html diff --git a/flywheel/templates/emitter.txt b/djangorestframework/templates/emitter.txt similarity index 100% rename from flywheel/templates/emitter.txt rename to djangorestframework/templates/emitter.txt diff --git a/flywheel/templatetags/__init__.py b/djangorestframework/templatetags/__init__.py similarity index 100% rename from flywheel/templatetags/__init__.py rename to djangorestframework/templatetags/__init__.py diff --git a/flywheel/templatetags/add_query_param.py b/djangorestframework/templatetags/add_query_param.py similarity index 100% rename from flywheel/templatetags/add_query_param.py rename to djangorestframework/templatetags/add_query_param.py diff --git a/flywheel/templatetags/urlize_quoted_links.py b/djangorestframework/templatetags/urlize_quoted_links.py similarity index 99% rename from flywheel/templatetags/urlize_quoted_links.py rename to djangorestframework/templatetags/urlize_quoted_links.py index 60088cf69..eea424a41 100644 --- a/flywheel/templatetags/urlize_quoted_links.py +++ b/djangorestframework/templatetags/urlize_quoted_links.py @@ -97,4 +97,4 @@ urlize_quoted_links.is_safe = True # Register urlize_quoted_links as a custom filter # http://docs.djangoproject.com/en/dev/howto/custom-template-tags/ register = template.Library() -register.filter(urlize_quoted_links) \ No newline at end of file +register.filter(urlize_quoted_links) diff --git a/flywheel/utils.py b/djangorestframework/utils.py similarity index 100% rename from flywheel/utils.py rename to djangorestframework/utils.py diff --git a/docs/conf.py b/docs/conf.py index 71cacb3d2..3689a6362 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,7 +14,7 @@ import sys, os sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) -sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'flywheel')) +sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'djangorestframework')) sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'examples')) import settings from django.core.management import setup_environ diff --git a/docs/index.rst b/docs/index.rst index 7a871a5c6..7da3f0174 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,10 +26,10 @@ Requirements Installation & Setup -------------------- -The django-rest-framework project is hosted as a `mercurial repository on bitbucket `_. +The django-rest-framework project is hosted as a `mercurial repository on bitbucket `_. To get a local copy of the repository use mercurial:: - hg clone https://tomchristie@bitbucket.org/tomchristie/flywheel + hg clone https://tomchristie@bitbucket.org/tomchristie/django-rest-framework To add django-rest-framework to a django project: @@ -43,27 +43,15 @@ Getting Started 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``:: +``urls.py`` - from djangorestframework.modelresource import ModelResource, ModelRootResource - from models import MyModel +.. include:: ../examples/simpleexample/urls.py + :literal: - class MyModelRootResource(ModelRootResource): - """A create/list resource for MyModel.""" - allowed_methods = ('GET', 'POST') - model = MyModel +``views.py`` - 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'), - ) +.. include:: ../examples/simpleexample/views.py + :literal: Examples diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..77cdf485a --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,8 @@ +# Documentation requires Django & Sphinx, and their dependencies... + +Django==1.2.4 +Jinja2==2.5.5 +Pygments==1.4 +Sphinx==1.0.7 +docutils==0.7 +wsgiref==0.1.2 diff --git a/examples/blogpost/views.py b/examples/blogpost/views.py index c5be25441..bfb53b5d5 100644 --- a/examples/blogpost/views.py +++ b/examples/blogpost/views.py @@ -1,6 +1,6 @@ -from flywheel.response import Response, status -from flywheel.resource import Resource -from flywheel.modelresource import ModelResource, RootModelResource +from djangorestframework.response import Response, status +from djangorestframework.resource import Resource +from djangorestframework.modelresource import ModelResource, RootModelResource from blogpost.models import BlogPost, Comment BLOG_POST_FIELDS = ('created', 'title', 'slug', 'content', 'absolute_url', 'comment_url', 'comments_url') diff --git a/examples/objectstore/views.py b/examples/objectstore/views.py index 4681fe704..90bdb9e36 100644 --- a/examples/objectstore/views.py +++ b/examples/objectstore/views.py @@ -1,7 +1,7 @@ from django.conf import settings -from flywheel.resource import Resource -from flywheel.response import Response, status +from djangorestframework.resource import Resource +from djangorestframework.response import Response, status import pickle import os diff --git a/examples/pygments_api/views.py b/examples/pygments_api/views.py index d9082adac..e22705d96 100644 --- a/examples/pygments_api/views.py +++ b/examples/pygments_api/views.py @@ -1,8 +1,8 @@ from django.conf import settings -from flywheel.resource import Resource -from flywheel.response import Response, status -from flywheel.emitters import BaseEmitter +from djangorestframework.resource import Resource +from djangorestframework.response import Response, status +from djangorestframework.emitters import BaseEmitter from pygments.formatters import HtmlFormatter from pygments.lexers import get_lexer_by_name diff --git a/examples/requirements.txt b/examples/requirements.txt new file mode 100644 index 000000000..4ae9e3c7b --- /dev/null +++ b/examples/requirements.txt @@ -0,0 +1,6 @@ +# For the examples we need Django, pygments and httplib2... + +Django==1.2.4 +wsgiref==0.1.2 +Pygments==1.4 +httplib2==0.6.0 diff --git a/examples/settings.py b/examples/settings.py index 0ae3bf562..4aa5dd006 100644 --- a/examples/settings.py +++ b/examples/settings.py @@ -93,9 +93,10 @@ INSTALLED_APPS = ( 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', - 'django.contrib.admin', - 'flywheel', - 'blogpost', + #'django.contrib.admin', + 'djangorestframework', + 'simpleexample', 'objectstore', - 'pygments_api' + 'pygments_api', + 'blogpost', ) diff --git a/examples/simpleexample/__init__.py b/examples/simpleexample/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/simpleexample/models.py b/examples/simpleexample/models.py new file mode 100644 index 000000000..13867f611 --- /dev/null +++ b/examples/simpleexample/models.py @@ -0,0 +1,23 @@ +from django.db import models + +MAX_INSTANCES = 20 + +class MyModel(models.Model): + foo = models.BooleanField() + bar = models.IntegerField(help_text='Must be an integer.') + baz = models.CharField(max_length=32, help_text='Free text. Max length 32 chars.') + created = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ('created',) + + def save(self, *args, **kwargs): + """For the purposes of the sandbox, limit the maximum number of stored models.""" + while MyModel.objects.all().count() > MAX_INSTANCES: + MyModel.objects.all()[0].delete() + super(MyModel, self).save(*args, **kwargs) + + @models.permalink + def get_absolute_url(self): + return ('simpleexample.views.MyModelResource', (self.pk,)) + diff --git a/examples/simpleexample/urls.py b/examples/simpleexample/urls.py new file mode 100644 index 000000000..d853ba5ad --- /dev/null +++ b/examples/simpleexample/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls.defaults import patterns, url + +urlpatterns = patterns('simpleexample.views', + url(r'^$', 'MyModelRootResource'), + url(r'^([0-9]+)/$', 'MyModelResource'), +) diff --git a/examples/simpleexample/views.py b/examples/simpleexample/views.py new file mode 100644 index 000000000..1f113ac27 --- /dev/null +++ b/examples/simpleexample/views.py @@ -0,0 +1,18 @@ +from djangorestframework.modelresource import ModelResource, RootModelResource +from simpleexample.models import MyModel + +FIELDS = ('foo', 'bar', 'baz', 'absolute_url') + +class MyModelRootResource(RootModelResource): + """A create/list resource for MyModel. + Available for both authenticated and anonymous access for the purposes of the sandbox.""" + model = MyModel + allowed_methods = anon_allowed_methods = ('GET', 'POST') + fields = FIELDS + +class MyModelResource(ModelResource): + """A read/update/delete resource for MyModel. + Available for both authenticated and anonymous access for the purposes of the sandbox.""" + model = MyModel + allowed_methods = anon_allowed_methods = ('GET', 'PUT', 'DELETE') + fields = FIELDS diff --git a/examples/urls.py b/examples/urls.py index b1dec13d5..2b8e6fcd0 100644 --- a/examples/urls.py +++ b/examples/urls.py @@ -1,14 +1,27 @@ from django.conf.urls.defaults import patterns, include -from django.contrib import admin +#from django.contrib import admin +from djangorestframework.resource import Resource + +#admin.autodiscover() + +class RootResource(Resource): + allowed_methods = anon_allowed_methods = ('GET',) + + def get(self, request, auth): + return {'simple example': self.reverse('simpleexample.views.MyModelRootResource'), + 'pygments example': self.reverse('pygments_api.views.PygmentsRoot'), + 'object store example': self.reverse('objectstore.views.ObjectStoreRoot'), + 'blog post example': self.reverse('blogpost.views.BlogPostRoot'),} -admin.autodiscover() urlpatterns = patterns('', - (r'^pygments-example/', include('pygments_api.urls')), - (r'^blog-post-example/', include('blogpost.urls')), - (r'^object-store-example/', include('objectstore.urls')), + (r'^$', RootResource), + (r'^simple-example/', include('simpleexample.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'^admin/doc/', include('django.contrib.admindocs.urls')), + #(r'^admin/', include(admin.site.urls)), ) diff --git a/requirements.txt b/requirements.txt index 8144d8289..84f0c4eb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ +# Django and pip are required if installing into a virtualenv environment... + Django==1.2.4 distribute==0.6.14 wsgiref==0.1.2