From 2f6d14ef5785b7c7bb67b57ed9253b080ebf2f72 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Felix=20D=C3=B6rre?= Date: Tue, 14 Nov 2017 14:34:54 +0100 Subject: [PATCH] initial commit for motion applicaiton --- .gitignore | 12 ++++++ README.md | 20 ++++++++++ config.py.example | 3 ++ motion.py | 74 ++++++++++++++++++++++++++++++++++++ requirements.txt | 7 ++++ schema.sql | 20 ++++++++++ templates/base.html | 42 ++++++++++++++++++++ templates/index.html | 20 ++++++++++ templates/motion.html | 18 +++++++++ templates/single_motion.html | 14 +++++++ 10 files changed, 230 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config.py.example create mode 100644 motion.py create mode 100644 requirements.txt create mode 100644 schema.sql create mode 100644 templates/base.html create mode 100644 templates/index.html create mode 100644 templates/motion.html create mode 100644 templates/single_motion.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a2dabd --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/__pycache__ +# virtualenv +/pip-selfcheck.json +/bin +/include +/lib + +/config.py + +# emacs +\#*\# +*~ diff --git a/README.md b/README.md new file mode 100644 index 0000000..5eb4ec2 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Installation +Requires 3. +To install: +``` +virtualenv -p python3 . +. bin/activate +pip install -r requirements.txt +``` +Then edit config.py.example into config.py with your database connection + +To debug-run: +``` +LANG=C.UTF-8 FLASK_DEBUG=1 FLASK_APP=motion.py flask run +``` + +To install database schema, run in an interactive python shell (`python`): +``` +import motion +motion.init_db() +``` diff --git a/config.py.example b/config.py.example new file mode 100644 index 0000000..daa9594 --- /dev/null +++ b/config.py.example @@ -0,0 +1,3 @@ +DATABASE="..." +USER="..." +PASSWORD="..." diff --git a/motion.py b/motion.py new file mode 100644 index 0000000..52547ce --- /dev/null +++ b/motion.py @@ -0,0 +1,74 @@ +from flask import g +from flask import Flask +from flask import render_template, redirect +from flask import request +import postgresql +import config + + +def get_db(): + db = getattr(g, '_database', None) + if db is None: + db = g._database = postgresql.open(config.DATABASE, user=config.USER, password=config.PASSWORD) + #db.row_factory = sqlite3.Row + return db + +app = Flask(__name__) + +@app.teardown_appcontext +def close_connection(exception): + db = getattr(g, '_database', None) + if db is not None: + db.close() + +def init_db(): + with app.app_context(): + db = get_db() + with app.open_resource('schema.sql', mode='r') as f: + db.execute(f.read()) + +@app.route("/") +def main(): + start=int(request.args.get("start", "-1")); + q = "SELECT *, motion.deadline > CURRENT_TIMESTAMP AS running FROM motion LEFT JOIN (SELECT motion_id, voter_id, "\ + + "COUNT(CASE WHEN result='yes' THEN 'yes' ELSE NULL END) as yes, "\ + + "COUNT(CASE WHEN result='no' THEN 'no' ELSE NULL END) as no, "\ + + "COUNT(CASE WHEN result='abstain' THEN 'abstain' ELSE NULL END) as abstain "\ + + "FROM vote GROUP BY motion_id, voter_id) as votes ON votes.motion_id=motion.id " + if start == -1: + p = get_db().prepare(q + "ORDER BY id DESC LIMIT 11") + rv = p() + else: + p = get_db().prepare(q + "WHERE id <= $1 ORDER BY id DESC LIMIT 11") + rv = p(start) + return render_template('index.html', motions=rv[:10], more=rv[10]["id"] if len(rv) == 11 else None) + +@app.route("/motion", methods=['POST']) +def put_motion(): + p = get_db().prepare("INSERT INTO motion(\"name\", \"content\") VALUES($1, $2)") + p(request.form.get("title", ""), request.form.get("content","")) + return redirect("/") + +voter=1 + +@app.route("/motion/") +def show_motion(id): + p = get_db().prepare("SELECT motion.*, motion.deadline > CURRENT_TIMESTAMP AS running, vote.result FROM motion LEFT JOIN vote on vote.motion_id=motion.id AND vote.voter_id=$2 WHERE id=$1") + rv = p(id,voter) + return render_template('single_motion.html', motion=rv[0]) + +@app.route("/motion//vote", methods=['POST']) +def vote(motion): + v = request.form.get("vote", "abstain") + db = get_db() + with db.xact(): + p = db.prepare("SELECT deadline > CURRENT_TIMESTAMP FROM motion WHERE id = $1") + if not p(motion)[0][0]: + return "Error, motion deadline has passed" + p = db.prepare("SELECT * FROM vote WHERE motion_id = $1 AND voter_id = $2") + rv = p(motion, voter) + if len(rv) == 0: + db.prepare("INSERT INTO vote (motion_id, voter_id, result) VALUES($1,$2,$3)")(motion,voter,v) + else: + db.prepare("UPDATE vote SET result=$3, entered=CURRENT_TIMESTAMP WHERE motion_id=$1 AND voter_id = $2")(motion,voter,v) + return redirect("/motion/" + str(motion)) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e3bed75 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +click==6.7 +Flask==0.12.2 +itsdangerous==0.24 +Jinja2==2.10 +MarkupSafe==1.0 +py-postgresql==1.2.1 +Werkzeug==0.12.2 diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..6fa7a67 --- /dev/null +++ b/schema.sql @@ -0,0 +1,20 @@ +DROP TABLE IF EXISTS voter; +CREATE TABLE voter (id serial NOT NULL, name VARCHAR(10) NOT NULL, PRIMARY KEY(id)); + + +DROP TABLE IF EXISTS motion; +CREATE TABLE motion (id serial NOT NULL, + name VARCHAR(250) NOT NULL, + content text NOT NULL, + posed timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + deadline timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP + interval '3 days'), + PRIMARY KEY(id)); + +DROP TABLE IF EXISTS vote; +DROP TYPE IF EXISTS "vote_type"; +CREATE TYPE "vote_type" AS ENUM ('yes', 'no', 'abstain'); +CREATE TABLE vote (motion_id INTEGER NOT NULL, + voter_id INTEGER NOT NULL, + result vote_type NOT NULL, + entered timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY(motion_id, voter_id)); diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..8f2b041 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,42 @@ + + + +{% block title %}Motion list{% endblock %} + + + + +{% block body %} +{% endblock %} + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..57a73f8 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} +{% block body %} +
+
+
+
+
+ + +
+
+
+{% for motion in motions %} +{% include 'motion.html' %} +{% endfor %} +{%- if more %} +Next +{%- endif %} +
+{%- endblock %} diff --git a/templates/motion.html b/templates/motion.html new file mode 100644 index 0000000..687d2a3 --- /dev/null +++ b/templates/motion.html @@ -0,0 +1,18 @@ +
+
+ # + {{motion.name}} ({{ 'Running' if motion.running else 'Finished' }}) +
Posed: {{motion.posed}}
Votes until: {{motion.deadline}}
+
+
+

{{motion.content}}

+{%- if motion.yes or motion.no or motion.abstain %} +

+{%- for vote in ['yes', 'no', 'abstain'] %} +{{vote|capitalize}} {{motion[vote]}}
+{%- endfor %} +

+{%- endif %} +
+{%- block content %}{% endblock %} +
diff --git a/templates/single_motion.html b/templates/single_motion.html new file mode 100644 index 0000000..1078343 --- /dev/null +++ b/templates/single_motion.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% block title -%} +Motion: {{motion.name}} +{%- endblock %} +{% block body %} +{%- include 'motion.html' %} +{%- if motion.running %} +
+{%- for vote in ['yes','no','abstain'] %} + +{%- endfor %} +
+{%- endif %} +{%- endblock %} -- 2.39.2