Skip to content

Commit

Permalink
Merge 7c641dd into 42ebe55
Browse files Browse the repository at this point in the history
  • Loading branch information
makyo committed Nov 26, 2016
2 parents 42ebe55 + 7c641dd commit 388bfdc
Show file tree
Hide file tree
Showing 21 changed files with 313 additions and 46 deletions.
3 changes: 3 additions & 0 deletions .coveragerc
@@ -0,0 +1,3 @@
[report]
show_missing = True
skip_covered = True
2 changes: 2 additions & 0 deletions .flake8
@@ -0,0 +1,2 @@
[flake8]
exclude = bin,lib
9 changes: 9 additions & 0 deletions .travis.yml
@@ -0,0 +1,9 @@
language: python
python:
- "2.7"
- "3.5"
install: "pip install -r requirements.txt"
script:
- make test
after_success:
- coveralls
7 changes: 6 additions & 1 deletion Makefile
Expand Up @@ -7,4 +7,9 @@ deps: bin/python

.PHONY: run
run:
bin/python polycules.py
python polycules.py

.PHONY: test
test:
flake8 --config=.flake8
nosetests --with-coverage --cover-erase --verbosity=2 --cover-package=polycules,model
4 changes: 4 additions & 0 deletions README.md
@@ -1,2 +1,6 @@
# polycul.es

[![Build Status](https://travis-ci.org/makyo/polycul.es.svg?branch=master)](https://travis-ci.org/makyo/polycul.es)
[![Coverage Status](https://coveralls.io/repos/github/makyo/polycul.es/badge.svg?branch=master)](https://coveralls.io/github/makyo/polycul.es?branch=master)

Graphing polyamorous relationships with force directed layouts.
31 changes: 31 additions & 0 deletions delete_legacy.py
@@ -0,0 +1,31 @@
from six.moves import input
import sys

from model import Polycule
import polycules


db = polycules.connect_db()

id = input('Enter the ID of the polycule to delete > ')
polycule = Polycule.get(db, int(id), None, force=True)
if polycule is None:
print('\nNo polycule with that ID found.')
db.close()
sys.exit(1)

if polycule.delete_pass is not None:
confirm = input(
'\nPolycule has a delete password, are you sure? [y/n] > ')
if confirm[0].lower() != 'y':
print('\nOkay, exiting without deleting.')
db.close()
sys.exit(1)

confirm = input(
'\nAre you sure you want to delete {}? [y/n] > '.format(id))
if confirm[0].lower() == 'y':
print('\nDeleting {}...'.format(id))
polycule.delete(None, force=True)
print('Polycule deleted.')
db.close()
3 changes: 1 addition & 2 deletions schema.sql → migrations/000-initial.sql
@@ -1,5 +1,4 @@
drop table if exists polycules;
create table polycules (
create table if not exists polycules (
id integer primary key autoincrement,
graph text not null
);
2 changes: 2 additions & 0 deletions migrations/001-add-view-pass.sql
@@ -0,0 +1,2 @@
alter table polycules
add column view_pass char(60);
2 changes: 2 additions & 0 deletions migrations/002-add-delete-pass.sql
@@ -0,0 +1,2 @@
alter table polycules
add column delete_pass char(60);
71 changes: 71 additions & 0 deletions model.py
@@ -0,0 +1,71 @@
import bcrypt


class Polycule(object):
def __init__(self, db=None, id=None, graph=None, view_pass=None,
delete_pass=None):
self._db = db
self.id = id
self.graph = graph
self.view_pass = view_pass
self.delete_pass = delete_pass

@classmethod
def get(cls, db, id, password, force=False):
result = db.execute('select * from polycules where id = ?', [id])
graph = result.fetchone()
if graph is None:
return None
polycule = Polycule(
db=db,
id=graph[0],
graph=graph[1],
view_pass=graph[2],
delete_pass=graph[3])
if not force and (
polycule.view_pass is not None and
not bcrypt.checkpw(password.encode('utf-8'),
polycule.view_pass.encode('utf-8'))):
raise Polycule.PermissionDenied
return polycule

@classmethod
def create(cls, db, graph, raw_view_pass, raw_delete_pass):
if raw_view_pass is not None:
view_pass = bcrypt.hashpw(
raw_view_pass.encode(), bcrypt.gensalt()).decode()
else:
view_pass = None
if raw_delete_pass is not None:
delete_pass = bcrypt.hashpw(
raw_delete_pass.encode(), bcrypt.gensalt()).decode()
else:
delete_pass = None
result = db.execute('select count(*) from polycules where graph = ?',
[graph])
existing = result.fetchone()[0]
if existing != 0:
raise Polycule.IdenticalGraph
cur = db.cursor()
result = cur.execute('''insert into polycules
(graph, view_pass, delete_pass) values (?, ?, ?)''', [
graph,
view_pass,
delete_pass,
])
db.commit()
return Polycule.get(db, result.lastrowid, None, force=True)

def delete(self, password, force=False):
if not force and not bcrypt.checkpw(password.encode('utf-8'),
self.delete_pass.encode('utf-8')):
raise Polycule.PermissionDenied
cur = self._db.cursor()
cur.execute('delete from polycules where id = ?', [self.id])
self._db.commit()

class PermissionDenied(Exception):
pass

class IdenticalGraph(Exception):
pass
88 changes: 56 additions & 32 deletions polycules.py
@@ -1,10 +1,9 @@
import base64
import os
import sqlite3

from contextlib import closing

from flask import (
abort,
Flask,
g,
redirect,
Expand All @@ -13,6 +12,8 @@
session,
)

from model import Polycule

# Config
DATABASE = 'dev.db'
DEBUG = True
Expand All @@ -28,18 +29,22 @@ def connect_db():
return sqlite3.connect(app.config['DATABASE'])


def init_db():
def migrate():
migrations_dir = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'migrations')
with closing(connect_db()) as db:
with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
for filename in os.listdir(migrations_dir):
with open(os.path.join(migrations_dir, filename), 'rb') as f:
db.cursor().execute(f.read())


def generate_csrf_token():
if '_csrf_token' not in session:
session['_csrf_token'] = base64.b64encode(os.urandom(12))
return session['_csrf_token']


app.jinja_env.globals['csrf_token'] = generate_csrf_token


Expand All @@ -48,7 +53,7 @@ def before_request():
if request.method == 'POST':
token = session.pop('_csrf_token', None)
if not token or token != request.form.get('_csrf_token'):
abort(403)
return render_template('error.jinja2', error='Token expired :(')
g.db = connect_db()


Expand All @@ -66,40 +71,42 @@ def front():
return render_template('front.jinja2')


@app.route('/<int:polycule_id>')
@app.route('/<int:polycule_id>', methods=['GET', 'POST'])
def view_polycule(polycule_id):
""" View a polycule. """
cur = g.db.execute('select graph from polycules where id = ?',
[polycule_id])
graph = cur.fetchone()
if graph is None:
abort(404)
return render_template('view_polycule.jinja2', graph=graph[0], id=polycule_id)
try:
polycule = Polycule.get(g.db, polycule_id,
request.form.get('view_pass', b''))
except Polycule.PermissionDenied:
return render_template('view_auth.jinja2')
if polycule is None:
return render_template('error.jinja2', error='Polycule not found :(')
return render_template('view_polycule.jinja2', polycule=polycule)


@app.route('/embed/<int:polycule_id>')
def embed_polycule(polycule_id):
""" View just a polycule for embedding in an iframe. """
cur = g.db.execute('select graph from polycules where id = ?',
[polycule_id])
graph = cur.fetchone()
if graph is None:
abort(404)
return render_template('embed_polycule.jinja2', graph=graph[0])
polycule = Polycule.get(g.db, polycule_id, request.form.get('view_pass'))
if polycule is None:
return render_template('error.jinja2', error='Polycule not found :(')
return render_template('embed_polycule.jinja2', graph=polycule.graph)


@app.route('/inherit/<int:polycule_id>')
@app.route('/inherit/<int:polycule_id>', methods=['GET', 'POST'])
def inherit_polycule(polycule_id):
"""
Take a given polycule and enter create mode, with that polycule's contents
already in place
"""
cur = g.db.execute('select graph from polycules where id = ?',
[polycule_id])
graph = cur.fetchone()
if graph is None:
abort(404)
return render_template('create_polycule.jinja2', inherited=graph[0])
try:
polycule = Polycule.get(g.db, polycule_id,
request.form.get('view_pass', b''))
except Polycule.PermissionDenied:
return render_template('view_auth.jinja2')
if polycule is None:
return render_template('error.jinja2', error='Polycule not found :(')
return render_template('create_polycule.jinja2', inherited=polycule.graph)


@app.route('/create')
Expand All @@ -117,11 +124,28 @@ def create_polycule():
def save_polycule():
""" Save a created polycule. """
# TODO check json encoding, check size
g.db.execute('insert into polycules (graph) values (?)',
[request.form['graph']])
g.db.commit()
cur = g.db.execute('select id from polycules order by id desc limit 1')
return redirect('/{}'.format(cur.fetchone()[0]))
try:
polycule = Polycule.create(
g.db,
request.form['graph'],
request.form.get('view_pass', b''),
request.form.get('delete_pass', b''))
except Polycule.IdenticalGraph:
return render_template('error.jinja2', error='An identical polycule '
'to the one you submitted already exists!')
return redirect('/{}'.format(polycule.id))


@app.route('/delete/<int:polycule_id>', methods=['POST'])
def delete_polycule(polycule_id):
polycule = Polycule.get(g.db, polycule_id, None, force=True)
if polycule is None:
return render_template('error.jinja2', error='Polycule not found :(')
try:
polycule.delete(request.form.get('delete_pass', b''))
except Polycule.PermissionDenied:
return render_template('view_auth.jinja2', polycule_id=polycule_id)
return redirect('/')


if __name__ == '__main__':
Expand Down
16 changes: 15 additions & 1 deletion requirements.txt
@@ -1,6 +1,20 @@
bcrypt==3.1.1
cffi==1.9.1
configparser==3.5.0
coverage==4.0.3
enum34==1.1.6
flake8==3.2.1
Flask==0.10.1
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
mccabe==0.5.2
nose==1.3.7
pycodestyle==2.2.0
pycparser==2.17
pyflakes==1.3.0
python-coveralls==2.9.0
PyYAML==3.12
requests==2.12.1
six==1.10.0
Werkzeug==0.11.4
wheel==0.24.0
5 changes: 5 additions & 0 deletions static/build.js
Expand Up @@ -329,3 +329,8 @@ svg.on('mousedown', mousedown)
d3.select(window)
.on('keydown', keydown)
.on('keyup', keyup);
d3.select('.expand-help').on('click', function(e) {
d3.event.preventDefault();
var body = d3.select('.instructions .body');
body.classed('hidden', !body.classed('hidden'));
});
32 changes: 32 additions & 0 deletions static/style.css
Expand Up @@ -2,6 +2,7 @@ body,
input,
button {
font-family: "Ubuntu Mono", monospace;
color: #333;
}

a,
Expand All @@ -21,6 +22,26 @@ a:hover {
margin: 0 auto;
}

form {
padding-top: 1em;
text-align: center;
}

.form-group {
display: inline-block;
width: 45%;
margin-right: 5%;
text-align: left;
}

.form-group:last-of-type {
margin-right: 0;
}

.help-block {
color: #999;
}

#graph {
position: relative;
border: 2px solid #eee;
Expand All @@ -47,6 +68,17 @@ a:hover {
left: 1em;
}

#graph .instructions .expand-help .caret {
display: inline-block;
width: 0;
height: 0;
vertical-align: middle;
border-top: 5px dashed;
border-top: 5px solid ~"\9";
border-right: 5px solid transparent;
border-left: 5px solid transparent;
}

#graph .link line {
stroke: rgba(0,0,0,0.25);
cursor: pointer;
Expand Down

0 comments on commit 388bfdc

Please sign in to comment.