Author: humbedooh
Date: Wed Mar 18 12:17:02 2015
New Revision: 1667520
URL: http://svn.apache.org/r1667520
Log:
First stab at a REST API for STeVe in Python (via CGI).
This just contains the backend API for setting up and viewing elections and
issues, nothing else.
Voting via API is not implemented yet.
No authentication feature yet either.
Added:
steve/trunk/pytest/
steve/trunk/pytest/REST_API_README.txt
steve/trunk/pytest/httpd.conf
steve/trunk/pytest/steve.cfg
steve/trunk/pytest/www/
steve/trunk/pytest/www/cgi-bin/
steve/trunk/pytest/www/cgi-bin/lib/
steve/trunk/pytest/www/cgi-bin/lib/__init__.py
steve/trunk/pytest/www/cgi-bin/lib/response.py
steve/trunk/pytest/www/cgi-bin/rest_admin.py
steve/trunk/pytest/www/cgi-bin/rest_voter.py
steve/trunk/pytest/www/htdocs/
Added: steve/trunk/pytest/REST_API_README.txt
URL:
http://svn.apache.org/viewvc/steve/trunk/pytest/REST_API_README.txt?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/REST_API_README.txt (added)
+++ steve/trunk/pytest/REST_API_README.txt Wed Mar 18 12:17:02 2015
@@ -0,0 +1,274 @@
+##################
+# ADMIN REST API #
+##################
+
+==========================
+Setting up a new election:
+==========================
+
+/steve/admin/setup/$electionid
+ POST
+ input:
+ title: Title of the election
+ owner: uid of election owner
+ monitors: email addresses of monitors
+ starts: UNIX timestamp of start (optional)
+ ends: UNIX timestamp when it closes (optional)
+ output:
+ HTTP 201 Created on success
+ HTTP 400 Bad request if params missing or invalid or already exists
+ HTTP 500 on error
+ response payload:
+ JSON formatted
+ {
+ "message": "created"
+ }
+
+==============================
+Editing an existing election:
+==============================
+
+/steve/admin/edit/$electionid
+ POST
+ input:
+ title: Title of the election (optional)
+ owner: uid of election owner (optional)
+ monitors: email addresses of monitors (optional)
+ starts: UNIX timestamp of start (optional)
+ ends: UNIX timestamp when it closes (optional)
+ output:
+ HTTP 200 Saved on success
+ HTTP 404 Not Found if no such election
+ HTTP 500 on error
+ response payload:
+ JSON formatted
+ {
+ "message": "Changes saved"
+ }
+
+
+
+=================================
+Creating an issue in an election:
+=================================
+
+/steve/admin/create/$electionid/$issueid
+ POST
+ input:
+ title: Title of the issue
+ description: (optional) Description of the issue
+ type: type of issue (yna, stv[1-9])
+ candidates: (if stv[1-9]) \n separated list of candidate names
+ nominatedby: (if YNA) Person to nominate this issue
+ seconds: (if YNA) \n separated list of seconds for the issue
+
+ output:
+ HTTP 201 Created on success
+ HTTP 400 Bad request if params missing or invalid or already exists
+ HTTP 500 on error
+ response payload:
+ JSON formatted
+ {
+ "message": "created"
+ }
+
+
+================================
+Editing an issue in an election:
+================================
+
+/steve/admin/edit/$electionid/$issueid
+ POST
+ input:
+ title: Title of the issue (optional)
+ description: (optional) Description of the issue (optional)
+ type: type of issue (yna, stv[1-9]) (optional)
+ candidates: (if stv[1-9]) \n separated list of candidate names
(optional)
+ nominatedby: (if YNA) Person to nominate this issue (optional)
+ seconds: (if YNA) \n separated list of seconds for the issue
(optional)
+
+ output:
+ HTTP 200 Saved on success
+ HTTP 404 Not Found if no such issue or election
+ HTTP 500 on error
+ response payload:
+ JSON formatted
+ {
+ "message": "Changes saved"
+ }
+
+
+==================================
+Adding a statement to a candidate:
+==================================
+
+/steve/admin/statement/$electionid/$issueid
+ POST
+ input:
+ candidate: Candidate to set statement for
+ statement: Statement to set/add
+
+ output:
+ HTTP 200 Saved on success
+ HTTP 400 Bad request if params missing
+ HTTP 404 Not Found if candidate is not on the ballot
+ HTTP 500 on error
+ response payload:
+ JSON formatted
+ {
+ "message": "Changes saved"
+ }
+
+
+================================
+Adding a candidate to the issue:
+================================
+
+/steve/admin/addcandidate/$electionid/$issueid
+ POST
+ input:
+ candidate: Candidate to set add
+ statement: Statement to set/add (optional)
+
+ output:
+ HTTP 200 Saved on success
+ HTTP 400 Bad request if params missing
+ HTTP 404 Not Found if no such issue/election
+ HTTP 500 on error
+ response payload:
+ JSON formatted
+ {
+ "message": "Changes saved"
+ }
+
+
+===================================
+Deleting a candidate to the issue:
+===================================
+
+/steve/admin/delcandidate/$electionid/$issueid
+ POST
+ input:
+ candidate: Candidate to delete
+
+ output:
+ HTTP 200 Saved on success
+ HTTP 400 Bad request if params missing
+ HTTP 404 Not Found if candidate is not on the ballot
+ HTTP 500 on error
+ response payload:
+ JSON formatted
+ {
+ "message": "Changes saved"
+ }
+
+
+
+
+
+
+##################
+# VOTER REST API #
+##################
+
+============================
+Viewing election and issues:
+============================
+
+/steve/voter/view/$electionid
+ GET
+ input:
+ none
+ output:
+ HTTP 200 Okay if election exists
+ HTTP 404 Not Found if no such election
+ response payload:
+ JSON formatted
+ {
+ "base_data": {
+ "owner": "humbedooh",
+ "starts": null,
+ "ends": null,
+ "monitors": [
+ "[email protected]"
+ ],
+ "title": "Foo Elections, 2015"
+ },
+ "baseurl": "https://stv.website/steve/election?foo",
+ "issues": [
+ {
+ "description": null,
+ "title": "Test Board Election",
+ "APIURL": "https://stv.website/steve/voter/view/foo/baz",
+ "prettyURL": "https://stv.website/steve/ballot?foo/baz",
+ "candidates": [
+ {
+ "name": "Just One Guy"
+ },
+ {
+ "name": "John Doe",
+ "statement": "Vote for me!"
+ },
+ {
+ "name": "Kate Smurf",
+ "statement": "Vote for me too!"
+ }
+ ],
+ "type": "stv6",
+ "id": "baz"
+ },
+ {
+ "description": "This is to nominate WALL-E for ASF
membership",
+ "title": "Membership for WALL-E",
+ "seconds": "humbedooh",
+ "APIURL":
"https://stv.website/steve/voter/view/foo/member-wall-e",
+ "prettyURL":
"https://stv.website/steve/ballot?foo/member-wall-e",
+ "candidates": [],
+ "nominatedby": "mattman",
+ "type": "yna",
+ "id": "member-wall-e"
+ }
+ ]
+ }
+
+
+=======================
+Viewing a single issue:
+=======================
+/steve/voter/view/$electionid/$issueid
+ GET
+ input:
+ none
+ output:
+ HTTP 200 Okay if issue exists
+ HTTP 404 Not Found if no such election/issue
+ response payload:
+ JSON formatted
+ {
+ "issue": {
+ "description": null,
+ "title": "Test Board Election",
+ "APIURL": "https://stv.website/steve/voter/view/foo/baz",
+ "prettyURL": "https://stv.website/steve/ballot?foo/baz",
+ "candidates": [
+ {
+ "name": "Just One Guy"
+ },
+ {
+ "name": "John Doe",
+ "statement": "Vote for me!"
+ },
+ {
+ "name": "Kate Smurf",
+ "statement": "Vote for me too!"
+ }
+ ],
+ "type": "stv6",
+ "id": "baz"
+ }
+ }
+
+
+
+
+MORE TO COME!
\ No newline at end of file
Added: steve/trunk/pytest/httpd.conf
URL:
http://svn.apache.org/viewvc/steve/trunk/pytest/httpd.conf?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/httpd.conf (added)
+++ steve/trunk/pytest/httpd.conf Wed Mar 18 12:17:02 2015
@@ -0,0 +1,22 @@
+# Sample httpd configuration for Steve's Python REST API test
+# see http://stv.website/steve/voter/view/foo for an example.
+
+<VirtualHost *80>
+DocumentRoot /home/voter/pytest/www/htdocs
+ServerName stv.website
+DirectoryIndex index.html
+<Directory /home/voter/pytest/www>
+ Require all granted
+ Options +ExecCGI
+ AddHandler cgi-script .py
+</Directory>
+
+# REST API
+ScriptAlias /steve/admin /home/voter/pytest//www/cgi-bin/rest_admin.py
+ScriptAlias /steve/voter /home/voter/pytest/www/cgi-bin/rest_voter.py
+
+# HTML generators
+ScriptAlias /steve/ballot /home/voter/pytest/www/cgi-bin/html_ballot.py
+ScriptAlias /steve/election /home/voter/pytest/www/cgi-bin/html_election.py
+
+</VirtualHost>
Added: steve/trunk/pytest/steve.cfg
URL:
http://svn.apache.org/viewvc/steve/trunk/pytest/steve.cfg?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/steve.cfg (added)
+++ steve/trunk/pytest/steve.cfg Wed Mar 18 12:17:02 2015
@@ -0,0 +1,3 @@
+[general]
+
+homedir: /home/voter
Added: steve/trunk/pytest/www/cgi-bin/lib/__init__.py
URL:
http://svn.apache.org/viewvc/steve/trunk/pytest/www/cgi-bin/lib/__init__.py?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/www/cgi-bin/lib/__init__.py (added)
+++ steve/trunk/pytest/www/cgi-bin/lib/__init__.py Wed Mar 18 12:17:02 2015
@@ -0,0 +1,3 @@
+"""
+Stuff
+"""
\ No newline at end of file
Added: steve/trunk/pytest/www/cgi-bin/lib/response.py
URL:
http://svn.apache.org/viewvc/steve/trunk/pytest/www/cgi-bin/lib/response.py?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/www/cgi-bin/lib/response.py (added)
+++ steve/trunk/pytest/www/cgi-bin/lib/response.py Wed Mar 18 12:17:02 2015
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+import json
+
+responseCodes = {
+ 200: 'Okay',
+ 201: 'Created',
+ 304: 'Not Modified',
+ 400: 'Bad Request',
+ 403: 'Access denied',
+ 404: 'Not Found',
+ 500: 'Server Error'
+}
+
+def respond(code, js):
+ c = responseCodes[code] if code in responseCodes else "Unknown Response
Code(?)"
+ out = json.dumps(js, indent=4)
+ print("Status: %u %s\r\nContent-Type: application/json\r\nContent-Length:
%u\r\n" % (code, c, len(out)))
+ print(out)
+
+
+
\ No newline at end of file
Added: steve/trunk/pytest/www/cgi-bin/rest_admin.py
URL:
http://svn.apache.org/viewvc/steve/trunk/pytest/www/cgi-bin/rest_admin.py?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/www/cgi-bin/rest_admin.py (added)
+++ steve/trunk/pytest/www/cgi-bin/rest_admin.py Wed Mar 18 12:17:02 2015
@@ -0,0 +1,301 @@
+#!/usr/bin/env python
+#####
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#####
+import os, sys, json, re, time, base64, cgi, subprocess, hashlib
+version = 2
+if sys.hexversion < 0x03000000:
+ import ConfigParser as configparser
+else:
+ import configparser
+ version = 3
+
+path = os.path.abspath(os.getcwd())
+
+sys.path.append(path)
+sys.path.append(os.path.basename(sys.argv[0]))
+if 'SCRIPT_FILENAME' in os.environ:
+ sys.path.insert(0, os.path.basename(os.environ['SCRIPT_FILENAME']))
+
+from lib import response
+
+
+# Fetch config (hack, hack, hack)
+config = configparser.RawConfigParser()
+config.read(path + '/../../steve.cfg')
+
+# Some quick paths
+homedir = config.get("general", "homedir")
+pathinfo = os.environ['PATH_INFO'] if 'PATH_INFO' in os.environ else None
+form = cgi.FieldStorage();
+
+
+
+# TODO: Authentication goes here
+karma = 5 # assume admin karma for now
+
+# Figure out what to do and where
+if pathinfo:
+ l = pathinfo.split("/")
+ if l[0] == "":
+ l.pop(0)
+ action = l[0]
+ election = l[1] if len(l) > 1 else None
+
+
+ # Set up new election?
+ if action == "setup":
+ if karma >= 5: # karma of 5 required to set up an election base
+ if election:
+ if os.path.isdir(os.path.join(homedir, "issues", election)):
+ response.respond(403, {'message': "Election already
exists!"})
+ else:
+ try:
+ required = ['title','owner','monitors']
+ xr = required
+ for i in required:
+ if not form.getvalue(i):
+ raise Exception("Required fields missing: %s"
% ", ".join(xr))
+ else:
+ xr.pop(0)
+ elpath = os.path.join(homedir, "issues", election)
+ os.mkdir(elpath)
+ with open(elpath + "/basedata.json", "w") as f:
+ f.write(json.dumps({
+ 'title': form.getvalue('title'),
+ 'owner': form.getvalue('owner'),
+ 'monitors':
form.getvalue('monitors').split(","),
+ 'starts': form.getvalue('starts'),
+ 'ends': form.getvalue('ends')
+ }))
+ f.close()
+ response.respond(201, {'message': 'Created!'})
+ except Exception as err:
+ response.respond(500, {'message': "Could not create
election: %s" % err})
+ else:
+ response.respond(400, {'message': "No election name
specified!"})
+ else:
+ response.respond(403, {'message': 'You do not have enough karma
for this'})
+
+ # Create an issue in an election
+ elif action == "create":
+ if karma >= 4: # karma of 4 required to set up an issue for the
election
+ if election:
+ issue = l[2] if len(l) > 2 else None
+ if not issue:
+ response.respond(400, {'message': 'No issue ID specified'})
+ else:
+ issuepath = os.path.join(homedir, "issues", election,
issue)
+ if os.path.isfile(issuepath + ".json"):
+ response.respond(400, {'message': 'An issue with this
ID already exists'})
+ else:
+ try:
+ required = ['title','type']
+ xr = required
+ for i in required:
+ if not form.getvalue(i):
+ raise Exception("Required fields missing:
%s" % ", ".join(xr))
+ else:
+ xr.pop(0)
+ valid_types =
['yna','stv1','stv2','stv3','stv4','stv5','stv6','stv7','stv8','stv9']
+ if not form.getvalue('type') in valid_types:
+ raise Exception('Invalid vote type: %s' %
form.getvalue('type'))
+ with open(issuepath + ".json", "w") as f:
+ candidates = []
+ if form.getvalue('candidates'):
+ for name in
form.getvalue('candidates').split("\n"):
+ candidates.append({'name': name})
+ f.write(json.dumps({
+ 'title': form.getvalue('title'),
+ 'description':
form.getvalue('description'),
+ 'type': form.getvalue('type'),
+ 'candidates': candidates,
+ 'seconds': form.getvalue('seconds'),
+ 'nominatedby': form.getvalue('nominatedby')
+ }))
+ f.close()
+ response.respond(201, {'message': 'Created!'})
+ except Exception as err:
+ response.respond(500, {'message': "Could not
create issue: %s" % err})
+ else:
+ response.respond(400, {'message': "No election specified!"})
+ else:
+ response.respond(403, {'message': 'You do not have enough karma
for this'})
+
+ # Edit an issue or election
+ elif action == "edit":
+ issue = l[2] if len(l) > 2 else None
+ if (issue and karma >= 4) or (karma >= 5 and election):
+ if election:
+ if not issue:
+ elpath = os.path.join(homedir, "issues", election)
+ if not os.path.isdir(elpath) or not
os.path.isfile(elpath+"/basedata.json"):
+ response.respond(404, {'message': 'No such issue'})
+ else:
+ try:
+ js = {}
+ with open(elpath + "/basedata.json", "r") as f:
+ js = json.loads(f.read())
+ f.close()
+ fields =
['title','owner','monitors','starts','ends']
+ for field in fields:
+ val = form.getvalue(field)
+ if val:
+ if field == "monitors":
+ val = val.split(",")
+ js[field] = val
+ with open(elpath + "/basedata.json", "w") as f:
+ f.write(json.dumps(js))
+ f.close()
+ response.respond(200, {'message': "Changed saved"})
+ except Exception as err:
+ response.respond(500, {'message': "Could not edit
election: %s" % err})
+ else:
+ issuepath = os.path.join(homedir, "issues", election,
issue)
+ if not os.path.isfile(issuepath + ".json"):
+ response.respond(404, {'message': 'No such issue'})
+ else:
+ try:
+ js = {}
+ with open(issuepath + ".json", "r") as f:
+ js = json.loads(f.read())
+ f.close()
+ fields =
['title','description','type','candidates','seconds','nominatedby']
+ for field in fields:
+ val = form.getvalue(field)
+ if val:
+ if field == "candidates" or field ==
"seconds":
+ xval = val.split("\n")
+ val = []
+ for entry in xval:
+ val.append({'name': entry})
+ js[field] = val
+ with open(issuepath + ".json", "w") as f:
+ f.write(json.dumps(js))
+ f.close()
+ response.respond(200, {'message': "Changed saved"})
+ except Exception as err:
+ response.respond(500, {'message': "Could not edit
issue: %s" % err})
+ else:
+ response.respond(400, {'message': "No election specified!"})
+ else:
+ response.respond(403, {'message': 'You do not have enough karma
for this'})
+
+ # Edit/add a statement
+ elif action == "statement":
+ issue = l[2] if len(l) > 2 else None
+ if (issue and karma >= 4):
+ issuepath = os.path.join(homedir, "issues", election, issue)
+ if not os.path.isfile(issuepath + ".json"):
+ response.respond(404, {'message': 'No such issue'})
+ else:
+ try:
+ js = {}
+ with open(issuepath + ".json", "r") as f:
+ js = json.loads(f.read())
+ f.close()
+
+ cand = form.getvalue('candidate')
+ stat = form.getvalue('statement')
+ found = False
+ for entry in js['candidates']:
+ if entry['name'] == cand:
+ found = True
+ entry['statement'] = stat
+ break
+ if not found:
+ raise Exception("No such candidate: " + cand)
+ with open(issuepath + ".json", "w") as f:
+ f.write(json.dumps(js))
+ f.close()
+ response.respond(200, {'message': "Changed saved"})
+ except Exception as err:
+ response.respond(500, {'message': "Could not edit issue:
%s" % err})
+ else:
+ response.respond(403, {'message': 'You do not have enough karma
for this'})
+
+ # Edit/add a statement
+ elif action == "addcandidate":
+ issue = l[2] if len(l) > 2 else None
+ if (issue and karma >= 4):
+ issuepath = os.path.join(homedir, "issues", election, issue)
+ if not os.path.isfile(issuepath + ".json"):
+ response.respond(404, {'message': 'No such issue'})
+ else:
+ try:
+ js = {}
+ with open(issuepath + ".json", "r") as f:
+ js = json.loads(f.read())
+ f.close()
+
+ cand = form.getvalue('candidate')
+ stat = form.getvalue('statement')
+ found = False
+ for entry in js['candidates']:
+ if entry['name'] == cand:
+ found = True
+ break
+ if found:
+ raise Exception("Candidate already exists: " + cand)
+ else:
+ js['candidates'].append( {
+ 'name': cand,
+ 'statement': stat
+ })
+ with open(issuepath + ".json", "w") as f:
+ f.write(json.dumps(js))
+ f.close()
+ response.respond(200, {'message': "Changed saved"})
+ except Exception as err:
+ response.respond(500, {'message': "Could not edit issue:
%s" % err})
+ else:
+ response.respond(403, {'message': 'You do not have enough karma
for this'})
+ elif action == "delcandidate":
+ issue = l[2] if len(l) > 2 else None
+ if (issue and karma >= 4):
+ issuepath = os.path.join(homedir, "issues", election, issue)
+ if not os.path.isfile(issuepath + ".json"):
+ response.respond(404, {'message': 'No such issue'})
+ else:
+ try:
+ js = {}
+ with open(issuepath + ".json", "r") as f:
+ js = json.loads(f.read())
+ f.close()
+
+ cand = form.getvalue('candidate')
+ found = False
+ i = 0
+ for entry in js['candidates']:
+ if entry['name'] == cand:
+ js['candidates'].pop(i)
+ found = True
+ break
+ i += 1
+ if not found:
+ raise Exception("Candidate does nost exist: " + cand)
+ with open(issuepath + ".json", "w") as f:
+ f.write(json.dumps(js))
+ f.close()
+ response.respond(200, {'message': "Changed saved"})
+ except Exception as err:
+ response.respond(500, {'message': "Could not edit issue:
%s" % err})
+ else:
+ response.respond(403, {'message': 'You do not have enough karma
for this'})
+ else:
+ response.respond(400, {'message': "No (or invalid) action supplied"})
+else:
+ response.respond(500, {'message': "No path_info supplied"})
Added: steve/trunk/pytest/www/cgi-bin/rest_voter.py
URL:
http://svn.apache.org/viewvc/steve/trunk/pytest/www/cgi-bin/rest_voter.py?rev=1667520&view=auto
==============================================================================
--- steve/trunk/pytest/www/cgi-bin/rest_voter.py (added)
+++ steve/trunk/pytest/www/cgi-bin/rest_voter.py Wed Mar 18 12:17:02 2015
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+#####
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#####
+import os, sys, json, re, time, base64, cgi, subprocess, hashlib
+from os import listdir
+version = 2
+if sys.hexversion < 0x03000000:
+ import ConfigParser as configparser
+else:
+ import configparser
+ version = 3
+
+path = os.path.abspath(os.getcwd())
+
+sys.path.append(path)
+sys.path.append(os.path.basename(sys.argv[0]))
+if 'SCRIPT_FILENAME' in os.environ:
+ sys.path.insert(0, os.path.basename(os.environ['SCRIPT_FILENAME']))
+
+from lib import response
+
+
+# Fetch config (hack, hack, hack)
+config = configparser.RawConfigParser()
+config.read(path + '/../../steve.cfg')
+
+# Some quick paths
+homedir = config.get("general", "homedir")
+pathinfo = os.environ['PATH_INFO'] if 'PATH_INFO' in os.environ else None
+form = cgi.FieldStorage();
+
+
+
+# TODO: Authentication goes here
+karma = 5 # assume admin karma for now
+
+# Figure out what to do and where
+if pathinfo:
+ l = pathinfo.split("/")
+ if l[0] == "":
+ l.pop(0)
+ action = l[0]
+ election = l[1] if len(l) > 1 else None
+ issue = l[2] if len(l) > 2 else None
+ voterid = l[3] if len(l) > 3 else None
+
+ if action == "view":
+ # View a list of issues for an election
+ if election and not issue:
+ js = []
+ elpath = os.path.join(homedir, "issues", election)
+ if os.path.isdir(elpath):
+ basedata = {}
+ try:
+ with open(elpath + "/basedata.json", "r") as f:
+ basedata = json.loads(f.read())
+ f.close()
+ issues = [ f for f in listdir(elpath) if
os.path.isfile(os.path.join(elpath,f)) and f != "basedata.json" ]
+ for issue in issues:
+ try:
+ with open(elpath + "/" + issue, "r") as f:
+ entry = json.loads(f.read())
+ f.close()
+ entry['id'] = issue.strip(".json")
+ entry['APIURL'] =
"https://%s/steve/voter/view/%s/%s" % (os.environ['SERVER_NAME'], election,
issue.strip(".json"))
+ entry['prettyURL'] =
"https://%s/steve/ballot?%s/%s" % (os.environ['SERVER_NAME'], election,
issue.strip(".json"))
+ js.append(entry)
+ except Exception as err:
+ response.respond(500, {'message': 'Could not load
issues: %s' % err})
+ except Exception as err:
+ response.respond(500, {'message': 'Could not load base
data: %s' % err})
+ response.respond(200, {'base_data': basedata, 'issues': js,
'baseurl': "https://%s/steve/election?%s" % (os.environ['SERVER_NAME'],
election)})
+ else:
+ response.respond(404, {'message': 'No such election'})
+
+ # View a speficic issue
+ elif election and issue:
+ js = []
+ issuepath = os.path.join(homedir, "issues", election, issue)
+ if os.path.isfile(issuepath + ".json"):
+ try:
+ with open(issuepath + ".json", "r") as f:
+ entry = json.loads(f.read())
+ f.close()
+ entry['id'] = issue.strip(".json")
+ entry['APIURL'] = "https://%s/steve/voter/view/%s/%s"
% (os.environ['SERVER_NAME'], election, issue)
+ entry['prettyURL'] = "https://%s/steve/ballot?%s/%s" %
(os.environ['SERVER_NAME'], election, issue)
+ response.respond(200, {'issue': entry})
+ except Exception as err:
+ response.respond(500, {'message': "Could not load issue:
%s" % err})
+ else:
+ response.respond(404, {'message': 'No such issue'})
+ else:
+ response.respond(404, {'message': 'No election ID supplied'})
+ elif action == "vote":
+ response.respond(500, {'message': 'Not implemented yet'})
+ else:
+ response.respond(400, {'message': 'Invalid action supplied'})
+else:
+ response.respond(500, {'message': 'No path info supplied, aborting'})
\ No newline at end of file