added lunchuman

This commit is contained in:
Niels Serup 2012-02-28 11:37:03 +01:00
parent bba6a35228
commit 4d6d190e79
13 changed files with 449 additions and 0 deletions

30
subsites/lunchuman-apache Normal file
View File

@ -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>

3
subsites/lunchuman/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/.dynamicsettings
/.old
/lunchuman.sqlite

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,4 @@
[lunchuman]
name = Datalosjen

View File

@ -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()

49
subsites/lunchuman/manage Executable file
View File

@ -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
})

View File

@ -0,0 +1 @@
User-agent: *

View File

@ -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();}

View File

@ -0,0 +1 @@
/home/niels/www/meta/subsites/lunchuman

View File

@ -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;
}

View File

@ -0,0 +1 @@
.

View File

@ -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>