gavel-team team mailing list archive
-
gavel-team team
-
Mailing list archive
-
Message #00024
[Branch ~gavel-team/gavel/simple_gavel] Rev 5: Got a basic REST API working.
------------------------------------------------------------
revno: 5
committer: Joshua Gardner <mellowcellofellow@xxxxxxxxx>
branch nick: simple_gavel
timestamp: Wed 2010-03-31 22:29:57 -0600
message:
Got a basic REST API working.
Usage example with curl in file gavel/rest_api_curl_example.txt.
added:
gavel/api/
gavel/api/__init__.py
gavel/api/tests.py
gavel/api/urls.py
gavel/rest_api_curl_example.txt
gavel/rules/api/
gavel/rules/api/__init__.py
gavel/rules/api/handlers.py
modified:
gavel/rules/models.py
gavel/settings.py
gavel/urls.py
--
lp:gavel
https://code.launchpad.net/~gavel-team/gavel/simple_gavel
Your team Gavel Team is subscribed to branch lp:gavel.
To unsubscribe from this branch go to https://code.launchpad.net/~gavel-team/gavel/simple_gavel/+edit-subscription
=== added directory 'gavel/api'
=== added file 'gavel/api/__init__.py'
=== added file 'gavel/api/tests.py'
--- gavel/api/tests.py 1970-01-01 00:00:00 +0000
+++ gavel/api/tests.py 2010-04-01 04:29:57 +0000
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
=== added file 'gavel/api/urls.py'
--- gavel/api/urls.py 1970-01-01 00:00:00 +0000
+++ gavel/api/urls.py 2010-04-01 04:29:57 +0000
@@ -0,0 +1,15 @@
+from django.conf.urls.defaults import *
+from piston.resource import Resource
+
+from gavel.rules.api.handlers import *
+
+motion_handler = Resource(MotionHandler)
+vote_handler = Resource(VoteHandler)
+
+urlpatterns = patterns('',
+ url(r'^motion/$', motion_handler),
+ url(r'^motion/(?P<motion_slug>[\w-]+)/$', motion_handler),
+ url(r'^motion/(?P<motion_slug>[\w-]+)/vote/$', vote_handler),
+ url(r'^motion/(?P<motion_slug>[\w-]+)/vote/(?P<vote_id>\d+)/$',
+ vote_handler),
+)
=== added file 'gavel/rest_api_curl_example.txt'
--- gavel/rest_api_curl_example.txt 1970-01-01 00:00:00 +0000
+++ gavel/rest_api_curl_example.txt 2010-04-01 04:29:57 +0000
@@ -0,0 +1,90 @@
+$ # This document demonstrates using the REST API for Gavel. It uses
+$ # the command-line HTTP tool curl.
+$
+$ # We can create an object by retreiving URL with POST.
+$ # If data is provided on the command line, curl defaults to POST. Without it it defaults to GET.
+$ curl http://127.0.0.1:8000/api/motion/ -F "title=A Motion" -F "text=Create a parliamentary procedure program."
+{
+ "text": "Create a parliamentary procedure program.",
+ "slug": "a-motion",
+ "title": "A Motion"
+}
+$
+$ # We can now view that object by retreiving its url, composed of the slug, with GET.
+$ curl http://127.0.0.1:8000/api/motion/a-motion/
+{
+ "text": "Create a parliamentary procedure program.",
+ "slug": "a-motion",
+ "title": "A Motion"
+}
+$
+$ # We can modify an object by sending data with PUT.
+$ # With curl, the method used is defined with the -X option.
+$ curl http://127.0.0.1:8000/api/motion/a-motion/ -X PUT -F "text=Create a parliamentary procedure program with Python."
+{
+ "text": "Create a parliamentary procedure program with Python.",
+ "slug": "a-motion",
+ "title": "A Motion"
+}
+$
+$ # We can now create a vote object for the motion object. Again using POST.
+$ curl http://127.0.0.1:8000/api/motion/a-motion/vote/ -F "passed=True" -F "positive=4" -F "negative=3"
+{
+ "abstain": null,
+ "positive": "4",
+ "negative": "3",
+ "datetime": "2010-03-31 23:15:40",
+ "motion": {
+ "text": "Create a parliamentary procedure program with Python.",
+ "slug": "a-motion",
+ "title": "A Motion"
+ },
+ "passed": "True",
+ "order": 0
+}
+$
+$ # We can also view the vote object with GET.
+$ # Vote objects are immutable. Not only is the save method short-circuited, but the PUT method is not allowed.
+$ curl http://127.0.0.1:8000/api/motion/a-motion/vote/0/
+{
+ "abstain": null,
+ "positive": 4,
+ "negative": 3,
+ "datetime": "2010-03-31 23:15:40",
+ "motion": {
+ "text": "Create a parliamentary procedure program with Python.",
+ "slug": "a-motion",
+ "title": "A Motion"
+ },
+ "passed": true,
+ "order": 0
+}
+$
+$ # Vote objects can, however, be deleted with the DELETE method.
+$ curl http://127.0.0.1:8000/api/motion/a-motion/vote/0/ -X DELETE
+$ # As you can see below, it's gone. Still need to implement a 404 of some kind.
+$ curl http://127.0.0.1:8000/api/motion/a-motion/vote/0/
+Piston/0.2.3rc1 (Django 1.1.1) crash report:
+
+Traceback (most recent call last):
+
+ File "/home/josh/Projects/Gavel/simple_gavel/gavel/../gavel/rules/api/handlers.py", line 43, in read
+ return motion.vote_set.get(order=vote_id)
+
+ File "/usr/lib/pymodules/python2.6/django/db/models/manager.py", line 120, in get
+ return self.get_query_set().get(*args, **kwargs)
+
+ File "/usr/lib/pymodules/python2.6/django/db/models/query.py", line 305, in get
+ % self.model._meta.object_name)
+
+DoesNotExist: Vote matching query does not exist.
+$
+$ # We can also delete motion objects.
+$ curl http://127.0.0.1:8000/api/motion/a-motion/ -X DELETE
+$
+$ # Well, that explains the RESTful API now in place. Motions and Votes
+$ # can be created, read, updated (motions only), and deleted using only
+$ # HTTP methods. Only thing I can see being needed is a way to list all objects,
+$ # or apply filters on full lists even.
+$
+
=== added directory 'gavel/rules/api'
=== added file 'gavel/rules/api/__init__.py'
=== added file 'gavel/rules/api/handlers.py'
--- gavel/rules/api/handlers.py 1970-01-01 00:00:00 +0000
+++ gavel/rules/api/handlers.py 2010-04-01 04:29:57 +0000
@@ -0,0 +1,63 @@
+from piston.handler import BaseHandler
+from piston.utils import rc
+
+from gavel.rules.models import Motion, Vote
+
+class MotionHandler(BaseHandler):
+ allowed_methods = ('GET', 'PUT', 'POST', 'DELETE')
+ model = Motion
+
+ def read(self, request, motion_slug):
+ return Motion.objects.get(slug=motion_slug)
+
+ def create(self, request):
+ motion = Motion()
+
+ motion.title = request.POST.get('title')
+ motion.text = request.POST.get('text')
+ motion.save()
+
+ return motion
+
+ def update(self, request, motion_slug):
+ motion = Motion.objects.get(slug=motion_slug)
+
+ motion.title = request.PUT.get('title') or motion.title
+ motion.text = request.PUT.get('text') or motion.text
+ motion.save()
+
+ return motion
+
+ def delete(self, request, motion_slug):
+ motion = Motion.objects.get(slug=motion_slug)
+ motion.delete()
+
+ return rc.DELETED
+
+class VoteHandler(BaseHandler):
+ allowed_methods = ('GET', 'POST', 'DELETE')
+ model = Vote
+
+ def read(self, request, motion_slug, vote_id):
+ motion = Motion.objects.get(slug=motion_slug)
+ return motion.vote_set.get(order=vote_id)
+
+ def create(self, request, motion_slug):
+ vote = Vote()
+
+ vote.motion = Motion.objects.get(slug=motion_slug)
+ vote.passed = request.POST.get('passed')
+ vote.positive = request.POST.get('positive')
+ vote.negative = request.POST.get('negative')
+ vote.abstain = request.POST.get('abstain')
+
+ vote.save()
+
+ return vote
+
+ def delete(self, request, motion_slug, vote_id):
+ motion = Motion.objects.get(slug=motion_slug)
+ vote = motion.vote_set.get(order=vote_id)
+ vote.delete()
+
+ return rc.DELETED
=== modified file 'gavel/rules/models.py'
--- gavel/rules/models.py 2010-04-01 00:12:17 +0000
+++ gavel/rules/models.py 2010-04-01 04:29:57 +0000
@@ -11,11 +11,12 @@
be voted on and subsidiary motions can be applied to it.
"""
title = models.CharField("Motion Title", max_length=50)
- slug = models.SlugField("Slug", max_length=50)
+ slug = models.SlugField("Slug", max_length=50, unique=True)
text = models.TextField("Motion Text", max_length=10240)
def save(self, *args, **kwargs):
- self.slug = re.sub(r'[^\w-]', '',
+ if self.slug == '':
+ self.slug = re.sub(r'[^\w-]', '',
re.sub(r'\s+', '-', self.title.lower()))
super(Motion, self).save(*args, **kwargs)
@@ -30,6 +31,7 @@
motion = models.ForeignKey("Motion")
passed = models.BooleanField("Vote Passed/Failed")
+ order = models.IntegerField("Per-Motion ID Number")
datetime = models.DateTimeField("Time of Vote", auto_now_add=True)
positive = models.IntegerField("Positive Votes", blank=True, null=True)
negative = models.IntegerField("Negative Votes", blank=True, null=True)
@@ -37,11 +39,13 @@
null=True)
def __unicode__(self):
- return u'%s: %s' % (self.motion.title,
+ return u'%s[%d]: %s' % (self.motion.title, self.order,
"Passed" if self.passed else "Failed")
def save(self, *args, **kwargs):
if self.id:
raise PermissionDenied("Vote cannot be modified")
+ self.order = self.motion.vote_set.count()
super(Vote, self).save(*args, **kwargs)
+
=== modified file 'gavel/settings.py'
--- gavel/settings.py 2010-04-01 00:12:17 +0000
+++ gavel/settings.py 2010-04-01 04:29:57 +0000
@@ -1,4 +1,4 @@
-# Django settings for simple_gavel project.
+# Django settings for gavel project.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
@@ -63,7 +63,8 @@
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
-ROOT_URLCONF = 'simple_gavel.urls'
+ROOT_URLCONF = 'gavel.urls'
+APPEND_SLASH = True
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
=== modified file 'gavel/urls.py'
--- gavel/urls.py 2010-03-31 19:19:02 +0000
+++ gavel/urls.py 2010-04-01 04:29:57 +0000
@@ -6,7 +6,10 @@
urlpatterns = patterns('',
# Example:
- # (r'^simple_gavel/', include('simple_gavel.foo.urls')),
+ # (r'^gavel/', include('simple_gavel.foo.urls')),
+
+ # Piston RESTful API
+ url(r'^api/', include('gavel.api.urls')),
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
# to INSTALLED_APPS to enable admin documentation: