gavel-team team mailing list archive
-
gavel-team team
-
Mailing list archive
-
Message #00026
[Branch ~gavel-team/gavel/simple_gavel] Rev 6: Lots of changes, biggest change is Vote objects are now related OneToOne to Motion objects.
------------------------------------------------------------
revno: 6
committer: Joshua Gardner <mellowcellofellow@xxxxxxxxx>
branch nick: simple_gavel
timestamp: Fri 2010-04-02 14:14:05 -0600
message:
Lots of changes, biggest change is Vote objects are now related OneToOne to Motion objects.
modified:
gavel/api/urls.py
gavel/rest_api_curl_example.txt
gavel/rules/api/handlers.py
gavel/rules/models.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
=== modified file 'gavel/api/urls.py'
--- gavel/api/urls.py 2010-04-01 04:29:57 +0000
+++ gavel/api/urls.py 2010-04-02 20:14:05 +0000
@@ -10,6 +10,4 @@
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),
)
=== modified file 'gavel/rest_api_curl_example.txt'
--- gavel/rest_api_curl_example.txt 2010-04-01 04:29:57 +0000
+++ gavel/rest_api_curl_example.txt 2010-04-02 20:14:05 +0000
@@ -1,90 +1,123 @@
-$ # 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"
-{
+# Create a motion with auto slug.
+# If no slug is supplied, the slug is made the primary key.
+$ curl 127.0.0.1:8000/api/motion/ -F "text=Motion 1"
+{
+ "text": "Motion 1",
+ "slug": "1"
+}$
+
+# Get the motion.
+# Motion URLs are /api/motion/slug/
+$ curl 127.0.0.1:8000/api/motion/1/
+{
+ "text": "Motion 1",
+ "slug": "1"
+}$
+
+# Create a motion with custom slug.
+# A slug can be provided in the POST data.
+$ curl 127.0.0.1:8000/api/motion/ -F "text=Motion with custom slug." -F "slug=foo"
+{
+ "text": "Motion with custom slug.",
+ "slug": "foo"
+}$
+$ curl 127.0.0.1:8000/api/motion/foo/
+{
+ "text": "Motion with custom slug.",
+ "slug": "foo"
+}$
+
+# Create a motion with a custom slug from the URL.
+# A motion can be created with the slug already in place in the URL.
+$ curl 127.0.0.1:8000/api/motion/bar/ -F "text=Motion with custom slug from url."
+{
+ "text": "Motion with custom slug from url.",
+ "slug": "bar"
+}$
+
+$ curl 127.0.0.1:8000/api/motion/bar/
+{
+ "text": "Motion with custom slug from url.",
+ "slug": "bar"
+}$
+
+# Record a vote.
+# A vote is made by POSTing to /api/motion/slug/vote/
+$ curl 127.0.0.1:8000/api/motion/1/vote/ -F "passed=False"
+{
+ "positive": null,
"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"
+ "negative": null,
+ "passed": "False",
+ "datetime": "2010-04-02 14:52:52"
+}$
+
+# Get A motion with a vote.
+# A motion with a vote now has a vote field containing all the vote
+# data.
+$ curl 127.0.0.1:8000/api/motion/1/
+{
+ "vote": {
+ "positive": null,
+ "abstain": null,
+ "negative": null,
+ "passed": true,
+ "datetime": "2010-04-02 14:52:52"
},
+ "text": "Motion 1",
+ "slug": "1"
+}$
+
+# Record a vote with more details.
+# A vote can have more detailed data, like how many positive, negative,
+# or abstaining votes.
+$ curl 127.0.0.1:8000/api/motion/foo/vote/ -F "passed=True" -F "positive=5" -F "negative=4"
+{
+ "positive": "5",
+ "abstain": null,
+ "negative": "4",
"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/
-{
+ "datetime": "2010-04-02 14:54:04"
+}$
+$ curl 127.0.0.1:8000/api/motion/foo/
+{
+ "vote": {
+ "positive": 5,
+ "abstain": null,
+ "negative": 4,
+ "passed": true,
+ "datetime": "2010-04-02 14:54:04"
+ },
+ "text": "Motion with custom slug.",
+ "slug": "foo"
+}$
+
+# Record a vote without the passed field.
+# The passed field is the only required field, but it can be left out if
+# both positive and negative fields are supplied. The passed will be filled
+# in computationally.
+$ curl 127.0.0.1:8000/api/motion/bar/vote/ -F "positive=10" -F "negative=8"
+{
+ "positive": 10,
"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"
- },
+ "negative": 8,
"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.
+ "datetime": "2010-04-02 14:56:16"
+}$
+
+# Modifying a motion.
+# A motion can be changed with the PUT method.
+$ curl 127.0.0.1:8000/api/motion/1/ -X PUT -F "text=This is motion 1."
+{
+ "text": "This is motion 1.",
+ "slug": "1"
+}$
+
+# Deleting a motion.
+# A motion can be deleted with the DELETE method.
+# The associated Vote is also deleted. Votes are immutable and undeletable.
+$ curl 127.0.0.1:8000/api/motion/1/ -X DELETE
+$ curl 127.0.0.1:8000/api/motion/foo/ -X DELETE
+$ curl 127.0.0.1:8000/api/motion/bar/ -X DELETE
$
=== modified file 'gavel/rules/api/handlers.py'
--- gavel/rules/api/handlers.py 2010-04-01 04:29:57 +0000
+++ gavel/rules/api/handlers.py 2010-04-02 20:14:05 +0000
@@ -2,62 +2,72 @@
from piston.utils import rc
from gavel.rules.models import Motion, Vote
+Motions = Motion.objects
+Votes = Vote.objects
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):
+ self.fields = ['slug', 'text']
+ try:
+ motion = Motions.get(slug=motion_slug)
+ except Motion.DoesNotExist:
+ return rc.NOT_HERE
+
+ try:
+ motion.vote
+ self.fields.append('vote')
+ except Vote.DoesNotExist:
+ pass
+
+ return motion
+
+ def create(self, request, motion_slug=None):
motion = Motion()
- motion.title = request.POST.get('title')
motion.text = request.POST.get('text')
+ motion.slug = motion_slug or request.POST.get('slug') or ''
motion.save()
- return motion
+ return self.read(request, motion.slug)
def update(self, request, motion_slug):
- motion = Motion.objects.get(slug=motion_slug)
+ motion = Motions.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 = Motions.get(slug=motion_slug)
motion.delete()
return rc.DELETED
class VoteHandler(BaseHandler):
- allowed_methods = ('GET', 'POST', 'DELETE')
+ allowed_methods = ('GET', 'POST')
model = Vote
+ fields = ('passed', 'positive', 'negative', 'abstain', 'datetime')
- def read(self, request, motion_slug, vote_id):
- motion = Motion.objects.get(slug=motion_slug)
- return motion.vote_set.get(order=vote_id)
+ def read(self, request, motion_slug):
+ motion = Motions.get(slug=motion_slug)
+ return motion.vote
def create(self, request, motion_slug):
+
vote = Vote()
-
- vote.motion = Motion.objects.get(slug=motion_slug)
+
+ vote.motion = Motions.get(slug=motion_slug)
vote.passed = request.POST.get('passed')
- vote.positive = request.POST.get('positive')
- vote.negative = request.POST.get('negative')
+ vote.positive = int(request.POST.get('positive'))
+ vote.negative = int(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 04:29:57 +0000
+++ gavel/rules/models.py 2010-04-02 20:14:05 +0000
@@ -1,5 +1,3 @@
-import re
-
from django.db import models
from django.core.exceptions import PermissionDenied
@@ -10,28 +8,30 @@
A motion is a proposition that the assembled body do something. It can
be voted on and subsidiary motions can be applied to it.
"""
- title = models.CharField("Motion Title", max_length=50)
+
+ text = models.TextField("Motion Text", max_length=512)
slug = models.SlugField("Slug", max_length=50, unique=True)
- text = models.TextField("Motion Text", max_length=10240)
def save(self, *args, **kwargs):
if self.slug == '':
- self.slug = re.sub(r'[^\w-]', '',
- re.sub(r'\s+', '-', self.title.lower()))
+ self.slug = 'NULL'
+ super(Motion, self).save(*args, **kwargs)
+
+ if self.slug == 'NULL':
+ self.slug = str(self.pk)
super(Motion, self).save(*args, **kwargs)
def __unicode__(self):
- return u'%s' % self.title
+ return u'%s' % self.text
class Vote(models.Model):
"""
A vote is a record of the votes made on a motion.
"""
- motion = models.ForeignKey("Motion")
+ motion = models.OneToOneField("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)
@@ -39,13 +39,16 @@
null=True)
def __unicode__(self):
- return u'%s[%d]: %s' % (self.motion.title, self.order,
+ return u'%s: %s' % (self.motion.text,
"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()
+
+ if self.passed == None and \
+ self.positive != None and self.negative != None:
+ self.passed = True if self.positive > self.negative else False
+
super(Vote, self).save(*args, **kwargs)