added lunchuman
This commit is contained in:
parent
bba6a35228
commit
4d6d190e79
|
@ -0,0 +1,30 @@
|
||||||
|
<VirtualHost *:80>
|
||||||
|
|
||||||
|
ServerName datalosjen.metanohi.name
|
||||||
|
ServerAlias www.datalosjen.metanohi.name datalosjen.lcl
|
||||||
|
ServerAdmin ns@metanohi.name
|
||||||
|
|
||||||
|
DocumentRoot /home/niels/www/meta/subsites/lunchuman
|
||||||
|
|
||||||
|
Alias /robots.txt /home/niels/www/meta/subsites/lunchuman/robots.txt
|
||||||
|
Alias /favicon.ico /home/niels/www/meta/subsites/lunchuman/favicon.ico
|
||||||
|
Alias /favicon.png /home/niels/www/meta/subsites/lunchuman/favicon.png
|
||||||
|
Alias /static /home/niels/www/meta/subsites/lunchuman/static
|
||||||
|
Alias /lunchuman.wsgi /home/niels/www/meta/subsites/films/lunchuman.wsgi
|
||||||
|
Alias /apache-config-template /home/niels/www/meta/subsites/lunchuman/apache-config-template
|
||||||
|
|
||||||
|
<Directory /home/niels/www/meta/subsites/lunchuman>
|
||||||
|
Order allow,deny
|
||||||
|
Allow from all
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
WSGIDaemonProcess lunchuman.metanohi.name processes=1 threads=1 display-name=%{GROUP}
|
||||||
|
WSGIProcessGroup lunchuman.metanohi.name
|
||||||
|
|
||||||
|
WSGIScriptAlias / /home/niels/www/meta/subsites/lunchuman/lunchuman.wsgi
|
||||||
|
|
||||||
|
LogLevel warn
|
||||||
|
ErrorLog /var/log/apache2/lunchuman.metanohi.name-error.log
|
||||||
|
CustomLog /var/log/apache2/lunchuman.metanohi.name-access.log combined
|
||||||
|
|
||||||
|
</VirtualHost>
|
|
@ -0,0 +1,3 @@
|
||||||
|
/.dynamicsettings
|
||||||
|
/.old
|
||||||
|
/lunchuman.sqlite
|
|
@ -0,0 +1,30 @@
|
||||||
|
<VirtualHost *:80>
|
||||||
|
|
||||||
|
ServerName lunchuman.example.org
|
||||||
|
ServerAlias www.lunchuman.example.org
|
||||||
|
ServerAdmin webmaster@example.org
|
||||||
|
|
||||||
|
DocumentRoot /path/to/dir/of/lunchuman
|
||||||
|
|
||||||
|
Alias /robots.txt /path/to/dir/of/lunchuman/robots.txt
|
||||||
|
Alias /favicon.ico /path/to/dir/of/lunchuman/favicon.ico
|
||||||
|
Alias /favicon.png /path/to/dir/of/lunchuman/favicon.png
|
||||||
|
Alias /static /path/to/dir/of/lunchuman/static
|
||||||
|
Alias /lunchuman.wsgi /path/to/dir/of/lunchuman/lunchuman.wsgi
|
||||||
|
Alias /apache-config-template /path/to/dir/of/lunchuman/apache-config-template
|
||||||
|
|
||||||
|
<Directory /path/to/dir/of/lunchuman>
|
||||||
|
Order allow,deny
|
||||||
|
Allow from all
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
WSGIDaemonProcess lunchuman.example.org processes=2 threads=15 display-name=%{GROUP}
|
||||||
|
WSGIProcessGroup lunchuman.example.org
|
||||||
|
|
||||||
|
WSGIScriptAlias / /path/to/dir/of/lunchuman/lunchuman.wsgi
|
||||||
|
|
||||||
|
LogLevel warn
|
||||||
|
ErrorLog /var/log/apache2/lunchuman.example.org-error.log
|
||||||
|
CustomLog /var/log/apache2/lunchuman.example.org-access.log combined
|
||||||
|
|
||||||
|
</VirtualHost>
|
|
@ -0,0 +1,37 @@
|
||||||
|
<h2>Add user</h2>
|
||||||
|
|
||||||
|
<form action='/adduser' method='post'>
|
||||||
|
<p>Name: <input type='text' name='name' /></p>
|
||||||
|
<input type='submit' value='Submit' />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<h2>Update user data</h2>
|
||||||
|
|
||||||
|
<form action='/updateuserdata' method='post'>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Moneys</th>
|
||||||
|
<th>Foods</th>
|
||||||
|
<th>Goodwill (moneys / foods)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{userdata}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<input type='submit' value='Update' />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<h2>FAQ</h2>
|
||||||
|
|
||||||
|
<p><strong>Question:</strong> The words "foods" and "moneys" are incorrect. Why
|
||||||
|
do you use them?</p>
|
||||||
|
<p><strong>Answer:</strong> In the domain of this lunch club, they are correct.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<em>Tihs is not a typo.</em>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
[lunchuman]
|
||||||
|
|
||||||
|
name = Datalosjen
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# lunchuman.wsgi: a WSGI script for managing a simple lunch club
|
||||||
|
# Copyright (C) 2012 Niels G. W. Serup
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public
|
||||||
|
# License along with this program. If not, see
|
||||||
|
# <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
## Version: 0.1.0
|
||||||
|
## Maintainer: Niels G. W. Serup <ns@metanohi.name>
|
||||||
|
## Temporary website: http://datalosjen.metanohi.name/static/source/lunchuman
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import bottle
|
||||||
|
from bottle import route, abort, error, request, redirect
|
||||||
|
import sqlite3
|
||||||
|
import threading
|
||||||
|
import itertools
|
||||||
|
import atexit
|
||||||
|
import pickle
|
||||||
|
import configparser
|
||||||
|
import locale
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
_preferred_encoding = 'utf-8' # locale.getpreferredencoding() # fail
|
||||||
|
_absfile = os.path.abspath(__file__)
|
||||||
|
_filedir = os.path.dirname(_absfile)
|
||||||
|
_configfile_name = 'lunchuman.config'
|
||||||
|
|
||||||
|
def start_bottle():
|
||||||
|
global application
|
||||||
|
application = bottle.default_app()
|
||||||
|
|
||||||
|
def block():
|
||||||
|
threading.Event().wait()
|
||||||
|
|
||||||
|
with open(os.path.join(_filedir, '.dynamicsettings'), 'rb') as f:
|
||||||
|
dynamic_settings = pickle.load(f)
|
||||||
|
|
||||||
|
if dynamic_settings['status'] == 'stop':
|
||||||
|
start_bottle()
|
||||||
|
block()
|
||||||
|
|
||||||
|
|
||||||
|
class LunchumanDatabase:
|
||||||
|
def __init__(self, fname):
|
||||||
|
self.dbfile = fname
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
self.conn = sqlite3.connect(self.dbfile)
|
||||||
|
self.cursor = self.conn.cursor()
|
||||||
|
self._create()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.conn.close()
|
||||||
|
|
||||||
|
def _create(self):
|
||||||
|
self.cursor.execute('''
|
||||||
|
create table if not exists users (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
name text,
|
||||||
|
moneys int,
|
||||||
|
foods int
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
def get_users(self):
|
||||||
|
self.cursor.execute('select * from users order by moneys / foods desc')
|
||||||
|
return self.cursor.fetchall()
|
||||||
|
|
||||||
|
def add_user(self, name):
|
||||||
|
self.cursor.execute('insert into users values (NULL, ?, 0, 0)', (name,))
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
def update_user_data(self, *users):
|
||||||
|
for id, new_name, moneys_incr, foods_incr in users:
|
||||||
|
self.cursor.execute('''
|
||||||
|
update users set name=?, moneys=moneys + (?), foods=foods + (?)
|
||||||
|
where id=?
|
||||||
|
''', (new_name, moneys_incr, foods_incr, id))
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
def add_settings_from_file(fname, settings):
|
||||||
|
cfg = configparser.SafeConfigParser(settings)
|
||||||
|
cfg.add_section('lunchuman')
|
||||||
|
cfg.read(os.path.join(_filedir, fname))
|
||||||
|
return {name: value for name, value in cfg.items('lunchuman')}
|
||||||
|
|
||||||
|
def dictpair(*ds):
|
||||||
|
return {key: val for key, val
|
||||||
|
in itertools.chain(*(d.items() for d in ds))}
|
||||||
|
|
||||||
|
def read_file(locpath):
|
||||||
|
with open(os.path.join(_filedir, locpath), 'rb') as f:
|
||||||
|
return f.read().decode(_preferred_encoding)
|
||||||
|
|
||||||
|
def get_seq_input(d, base_key):
|
||||||
|
i = 0
|
||||||
|
xs = []
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
xs.append(d['{}[{}]'.format(base_key, i)])
|
||||||
|
except KeyError:
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
return xs
|
||||||
|
|
||||||
|
settings = add_settings_from_file(
|
||||||
|
_configfile_name, {
|
||||||
|
'name': 'Lunchuman',
|
||||||
|
'dbfile': 'lunchuman.sqlite',
|
||||||
|
'template': 'template.html',
|
||||||
|
'frontpage': 'frontpage.html'
|
||||||
|
})
|
||||||
|
|
||||||
|
_template = read_file(settings['template'])
|
||||||
|
_userdata_template = '''
|
||||||
|
<input type='hidden' name='id[{i}]' value='{id}' />
|
||||||
|
<input type='hidden' name='orig_name[{i}]' value={name} />
|
||||||
|
<tr>
|
||||||
|
<td><input type='text' name='new_name[{i}]' style='width: 140px;'
|
||||||
|
value={name} /></td>
|
||||||
|
<td>{moneys} + <input type='text' name='moneys_incr[{i}]'
|
||||||
|
style='width: 40px;' value='0' class='deleteonfocus' /></td>
|
||||||
|
<td>{foods} + <input type='text' name='foods_incr[{i}]'
|
||||||
|
style='width: 40px;' value='0' class='deleteonfocus' /></td>
|
||||||
|
<td>{goodwill}</td>
|
||||||
|
</tr>
|
||||||
|
'''
|
||||||
|
_frontpage = _template.format(**dictpair(settings, {
|
||||||
|
'content': read_file(settings['frontpage']),
|
||||||
|
'headextra': "<script type='text/javascript' \
|
||||||
|
src='/static/front.js'></script>"
|
||||||
|
}))
|
||||||
|
|
||||||
|
db = LunchumanDatabase(os.path.join(_filedir, settings['dbfile']))
|
||||||
|
#atexit.register(db.close)
|
||||||
|
db.load()
|
||||||
|
|
||||||
|
|
||||||
|
@route('/')
|
||||||
|
def frontpage():
|
||||||
|
users = db.get_users()
|
||||||
|
userdata = '\n'.join(_userdata_template.format(
|
||||||
|
id=users[i][0], name=repr(users[i][1]), moneys=users[i][2],
|
||||||
|
foods=users[i][3], i=i, goodwill=('{:.2f}'.format(
|
||||||
|
users[i][2] / users[i][3]) if users[i][3] > 0
|
||||||
|
else None))
|
||||||
|
for i in range(len(users)))
|
||||||
|
return _frontpage.format(userdata=userdata)
|
||||||
|
|
||||||
|
@route('/adduser', method='post')
|
||||||
|
def add_user():
|
||||||
|
db.add_user(request.forms['name'])
|
||||||
|
redirect('/')
|
||||||
|
|
||||||
|
@route('/updateuserdata', method='post')
|
||||||
|
def update_user_data():
|
||||||
|
users = (vals[1:] for vals in
|
||||||
|
filter(lambda vals: vals[0] != vals[2] or
|
||||||
|
(vals[3] != '0' and vals[3] != '') or
|
||||||
|
(vals[4] != '0' and vals[4] != ''),
|
||||||
|
zip(*(get_seq_input(request.forms, attr)
|
||||||
|
for attr in ('orig_name', 'id', 'new_name',
|
||||||
|
'moneys_incr', 'foods_incr')))))
|
||||||
|
db.update_user_data(*users)
|
||||||
|
redirect('/')
|
||||||
|
|
||||||
|
|
||||||
|
start_bottle()
|
|
@ -0,0 +1,49 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
_absfile = os.path.abspath(__file__)
|
||||||
|
_filedir = os.path.dirname(_absfile)
|
||||||
|
dfile = os.path.join(_filedir, '.dynamicsettings')
|
||||||
|
|
||||||
|
def write(data):
|
||||||
|
with open(dfile, 'wb') as f:
|
||||||
|
pickle.dump(data, f)
|
||||||
|
|
||||||
|
def touch(fname):
|
||||||
|
if os.path.exists(fname):
|
||||||
|
return os.utime(fname, None)
|
||||||
|
else:
|
||||||
|
with open(path, 'w'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _traverse(xs):
|
||||||
|
for x in xs:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def act(d):
|
||||||
|
try:
|
||||||
|
d[sys.argv[1]]()
|
||||||
|
except (IndexError, KeyError):
|
||||||
|
print('unknown command', file=sys.stderr)
|
||||||
|
print_help()
|
||||||
|
|
||||||
|
def print_help():
|
||||||
|
print('''
|
||||||
|
Run one of:
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
restart
|
||||||
|
''')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
act({
|
||||||
|
'stop': lambda: write({'status': 'stop'}),
|
||||||
|
'start': lambda: write({'status': 'running'}),
|
||||||
|
'restart': lambda: _traverse(
|
||||||
|
f() for f in (lambda: write({'status': 'running'}),
|
||||||
|
lambda: touch(dfile))),
|
||||||
|
'help': print_help, '-h': print_help, '--help': print_help
|
||||||
|
})
|
|
@ -0,0 +1 @@
|
||||||
|
User-agent: *
|
|
@ -0,0 +1,19 @@
|
||||||
|
create_listener = function(elem) {
|
||||||
|
var listener = function() {
|
||||||
|
elem.value = '';
|
||||||
|
elem.removeEventListener('focus', listener, false);
|
||||||
|
};
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_delete_onfocus = function() {
|
||||||
|
var elems, elemI, elem, listener;
|
||||||
|
elems = document.getElementsByClassName('deleteonfocus');
|
||||||
|
for (elemI in elems) {
|
||||||
|
elem = elems[elemI];
|
||||||
|
elem.addEventListener('focus', create_listener(elem), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function(event) {prepare_delete_onfocus();}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
/home/niels/www/meta/subsites/lunchuman
|
|
@ -0,0 +1,57 @@
|
||||||
|
#header
|
||||||
|
{
|
||||||
|
background-color: pink;
|
||||||
|
color: green;
|
||||||
|
text-decoration: blink;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer
|
||||||
|
{
|
||||||
|
border-top: 1px black solid;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
table {
|
||||||
|
margin: 5px auto;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table, thead, tbody, tfoot, td, th {
|
||||||
|
border-style: inset;
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
margin: 0 5px;
|
||||||
|
padding: 1px 3px;
|
||||||
|
border-width: 0 2px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:last-child, th:last-child {
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table, tbody {
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
border-width: 0 0 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tfoot {
|
||||||
|
border-width: 2px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead, tfoot {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
.
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.1//EN'
|
||||||
|
'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'>
|
||||||
|
<html version='-//W3C//DTD XHTML 1.1//EN'
|
||||||
|
xmlns='http://www.w3.org/1999/xhtml'
|
||||||
|
xml:lang='en'
|
||||||
|
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
|
||||||
|
xsi:schemaLocation='http://www.w3.org/1999/xhtml
|
||||||
|
http://www.w3.org/MarkUp/SCHEMA/xhtml11.xsd'>
|
||||||
|
<head>
|
||||||
|
<title>{name}</title>
|
||||||
|
<link rel='stylesheet' type='text/css'
|
||||||
|
href='/static/screen.css' />
|
||||||
|
<link rel='schema.DCTERMS' href='http://purl.org/dc/terms/' />
|
||||||
|
<meta name='DCTERMS.language' content='en' />
|
||||||
|
<meta name='robots' content='NOINDEX, NOFOLLOW' />
|
||||||
|
<link rel='icon' type='image/png' href='/favicon.png' />
|
||||||
|
{headextra}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id='header'>
|
||||||
|
<h1>{name}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id='content'>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id='footer'>
|
||||||
|
Powered by <em><a href='/static/source/lunchuman/'>lunchuman</a></em>.
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue