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