← Back to team overview

gavel-team team mailing list archive

[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)