"Firt release"

This commit is contained in:
Doug Le Tough 2018-03-05 22:12:02 +01:00
parent f1ec84ef16
commit ec8b53c3e5
22 changed files with 7205 additions and 128 deletions

View File

@ -7,8 +7,18 @@ import inspect
import random
import binascii
import bcrypt
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash
import datetime
import ast
from reportlab.lib import colors
from reportlab.lib.enums import TA_LEFT, TA_CENTER
from reportlab.lib.pagesizes import A4, cm, portrait
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, PageBreak, Indenter
from reportlab.lib.colors import black, white, orange, bisque, lightsalmon
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash, send_file
from functools import wraps
import cStringIO
from flask_mail import Mail, Message
# Optionnal modules
import psycopg2
@ -18,6 +28,9 @@ from flask_sqlalchemy import SQLAlchemy
# App settings
########################################################################
app = Flask(__name__)
mail = Mail(app)
# Jinja2 loopcontrols extension
app.jinja_env.add_extension('jinja2.ext.loopcontrols')
# Path to static files
app.static_url_path='/static'
# Set debug mode to False for production
@ -30,6 +43,7 @@ app.secret_key = '9ac80548e3a8d8dfd1aefcd9a3a73473'
db = SQLAlchemy(app)
########################################################################
# Sample user database
########################################################################
@ -42,6 +56,7 @@ class Tetawebapp_users(db.Model):
phone = db.Column(db.Text, nullable=True)
diet = db.Column(db.Text, nullable=True)
is_admin = db.Column(db.Integer, nullable=False, default=0)
link_id = db.Column(db.Text, nullable=True)
class Tetawebapp_roles(db.Model):
__tablename__ = 'participer_thsf_roles'
@ -53,10 +68,16 @@ class Tetawebapp_turns(db.Model):
__tablename__ = 'participer_thsf_turns'
id = db.Column(db.Integer, primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('participer_thsf_roles.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('participer_thsf_users.id'), nullable=True)
wday = db.Column(db.Enum('Jeudi', 'Vendredi', 'Samedi', 'Dimanche'), nullable=False)
start_time = db.Column(db.Time, nullable=False)
end_time = db.Column(db.Time, nullable=False)
start_time = db.Column(db.DateTime, nullable=False)
end_time = db.Column(db.DateTime, nullable=False)
num_slot = db.Column(db.Integer, nullable=False, default=2)
class Tetawebapp_staffs(db.Model):
__tablename__ = 'participer_thsf_staffs'
id = db.Column(db.Integer, primary_key=True)
turn_id = db.Column(db.Integer, db.ForeignKey('participer_thsf_turns.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('participer_thsf_users.id'), default=0, nullable=False)
slot_num = db.Column(db.Integer, nullable=False)
########################################################################
# Menu and navigation management
@ -73,14 +94,13 @@ def get_menu(page):
The value MUST be 0."""
menu = [[u'Accueil', {u'/': [u'/']}, 0],
[u'Mon compte', {u'/account': [u'/account', u'/account/update']}, 0],
[u'Mes tours de staff', {u'/turns': [u'/turns']}, 0],
[u'Feuilles de staff', {u'/staff_sheets': [u'/staff_sheet']}, 0],
[u'Feuille de staff', {u'/staffsheet': [u'/staffsheet', u'/staffsheet/clear/<TURN_ID>/<SLOT_ID>', u'/staffsheet/update/<TURN_ID>/<SLOT_ID>']}, 0],
[u'Déconnexion', {u'/logout': [u'/logout']}, 0],
]
if session['is_admin']:
menu = [[u'Accueil', {u'/': [u'/']}, 0],
[u'Tours de staff', {u'/turns': [u'/turns', u'/turn/<ID>', u'/turn/new', u'/turn/add', u'/turn/delete/<ID>', u'/turn/update/<ID>']}, 0],
[u'Feuilles de staff', {u'/staff_sheets': [u'/staff_sheet']}, 0],
[u'Feuille de staff', {u'/staffsheet': [u'/staffsheet', u'/staffsheet/clear/<TURN_ID>/<SLOT_ID>', u'/staffsheet/update/<TURN_ID>/<SLOT_ID>', u'/staffsheet/pdf']}, 0],
[u'Liste des staffers', {u'/users': [u'/users', u'/account/<ID>', u'/account/delete/<ID>']}, 0],
[u'Déconnexion', {u'/logout': [u'/logout']}, 0],
]
@ -154,11 +174,27 @@ def check_session(func):
def check_login(login, password):
""" Puts the login verification code here """
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
stored_hash = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.password).first()
is_admin = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.is_admin).first()
stored_hash = Tetawebapp_users.query.filter(Tetawebapp_users.mail==login, Tetawebapp_users.link_id==None).with_entities(Tetawebapp_users.password).first()
if stored_hash:
if bcrypt.checkpw(password, stored_hash[0].encode('utf-8')):
is_admin = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.is_admin).first()
user_id = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.id).first()
session['is_admin'] = is_admin[0]
session['user_id'] = user_id[0]
return True
print "[+] Login failed"
return False
def check_confirm(login, password, link_id):
""" Check identity when confirming email address """
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
stored_hash = Tetawebapp_users.query.filter(Tetawebapp_users.mail==login, Tetawebapp_users.link_id==link_id).with_entities(Tetawebapp_users.password).first()
if stored_hash:
if bcrypt.checkpw(password, stored_hash[0].encode('utf-8')):
is_admin = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.is_admin).first()
user_id = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.id).first()
session['is_admin'] = is_admin[0]
session['user_id'] = user_id[0]
return True
return False
@ -173,8 +209,9 @@ def register_user(login, password, confirm):
# User already exists
print "[+] User already exists"
return False
link_id = gen_token(20)
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
user = Tetawebapp_users(mail=login.encode('utf8'), password=hashed_password)
user = Tetawebapp_users(mail=login.encode('utf8'), password=hashed_password, link_id=link_id)
try:
db.session.add(user)
commit = db.session.commit()
@ -186,9 +223,31 @@ def register_user(login, password, confirm):
print "------------------------------"
return False
if commit != None:
print "[+] Error at register_user: commit was not None"
return False
send_mail(login, link_id)
return True
def confirm_user(login, password, link_id):
""" Confirm user """
if check_confirm(login, password, link_id):
user = Tetawebapp_users.query.filter_by(mail=login).first()
setattr(user, 'link_id', None)
try:
db.session.add(user)
commit = db.session.commit()
except Exception as e:
db.session.rollback()
print "[+] Error at confirm_user:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
if commit != None:
return False
return True
return False
def update_user(login, password, confirm, name, phone, diet):
""" Update user infos with provided data """
if password != confirm:
@ -250,7 +309,7 @@ def update_user_by_id(user_id, login, password, confirm, name, phone, diet):
commit = db.session.commit()
except Exception as e:
db.session.rollback()
print "[+] Error at update_user:"
print "[+] Error at update_user_by_id:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
@ -275,12 +334,22 @@ def delete_user(user_id):
print "------------------------------"
return False
def save_turn(role_id, day, start, end):
def save_turn(role_id, day, start, end, num_slot):
""" Save a new turn """
days = ['2018-05-08', '2018-05-09', '2018-05-10', '2018-05-11', '2018-05-12', '2018-05-13', '2018-05-14']
day_index = days.index(day)
sday = day
eday = day
if str(start[0]) == '0':
sday = days[day_index+1]
if str(end[0]) == '0':
eday = days[day_index+1]
start = '%s %s' % (sday, start)
end = '%s %s' % (eday, end)
turn = Tetawebapp_turns(role_id=role_id.encode('utf-8'),
wday=day.encode('utf-8'),
start_time=start.encode('utf-8'),
end_time=end.encode('utf-8'),
num_slot=num_slot.encode('utf-8'),
)
try:
db.session.add(turn)
@ -296,18 +365,45 @@ def save_turn(role_id, day, start, end):
return False
return True
def update_turn_by_id(turn_id, role_id, wday, start, end):
def get_turn_by_id(turn_id):
""" Get specified stafff turn """
days = {'2018-05-08': 'Mardi',
'2018-05-09': 'Mercredi',
'2018-05-10': 'Jeudi',
'2018-05-11': 'Vendredi',
'2018-05-12': 'Samedi',
'2018-05-13': 'Dimanche',
'2018-05-14': 'Lundi'}
turn = Tetawebapp_turns.query.filter_by(id=turn_id).first()
s_day, s_time = str(turn.start_time).split(' ')
e_day, e_time = str(turn.end_time).split(' ')
if s_time[0] == '0':
s_day = (datetime.datetime.strptime(s_day, '%Y-%m-%d')-datetime.timedelta(1)).strftime("%Y-%m-%d")
day = days[s_day]
return {'id': turn_id, 'role_id': turn.role_id, 'day': day, 'start_time': s_time, 'end_time': e_time, 'num_slot': turn.num_slot}
def update_turn_by_id(turn_id, role_id, day, start, end, num_slot):
""" Update turn with provided data """
check_turn = Tetawebapp_turns.query.filter_by(id=turn_id).count()
if check_turn == 0:
# User does not exist
print "[+] User does not exist"
print "[+] Turn does not exist"
return False
days = ['2018-05-08', '2018-05-09', '2018-05-10', '2018-05-11', '2018-05-12', '2018-05-13', '2018-05-14']
day_index = days.index(day)
sday = day
eday = day
if str(start[0]) == '0':
sday = days[day_index +1]
if str(end[0]) == '0':
eday = days[day_index +1]
start = '%s %s' % (sday, start)
end = '%s %s' % (eday, end)
turn = Tetawebapp_turns.query.filter_by(id=turn_id).first()
setattr(turn, 'role_id', role_id)
setattr(turn, 'wday', wday)
setattr(turn, 'start_time', start)
setattr(turn, 'end_time', end)
setattr(turn, 'num_slot', num_slot)
try:
db.session.add(turn)
commit = db.session.commit()
@ -339,6 +435,104 @@ def drop_turn(turn_id):
print "------------------------------"
return False
def turns_list():
""" List staff turns """
turns = []
tuesday = '2018-05-08 06:00:00'
wenesday = '2018-05-09 06:00:00'
thirsday = '2018-05-10 06:00:00'
friday = '2018-05-11 06:00:00'
saturday = '2018-05-12 06:00:00'
sunday = '2018-05-13 06:00:00'
monday = '2018-05-14 06:00:00'
tuesday_turns = Tetawebapp_turns.query.filter(Tetawebapp_turns.start_time > tuesday, Tetawebapp_turns.start_time < wenesday)
tuesday_turns = tuesday_turns.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id)
tuesday_turns = tuesday_turns.add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id, Tetawebapp_turns.start_time).all()
wenesday_turns = Tetawebapp_turns.query.filter(Tetawebapp_turns.start_time > wenesday, Tetawebapp_turns.start_time < thirsday)
wenesday_turns = wenesday_turns.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id)
wenesday_turns = wenesday_turns.add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id, Tetawebapp_turns.start_time).all()
thirsday_turns = Tetawebapp_turns.query.filter(Tetawebapp_turns.start_time > thirsday, Tetawebapp_turns.start_time < friday)
thirsday_turns = thirsday_turns.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id)
thirsday_turns = thirsday_turns.add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id, Tetawebapp_turns.start_time).all()
friday_turns = Tetawebapp_turns.query.filter(Tetawebapp_turns.start_time > friday, Tetawebapp_turns.start_time < saturday)
friday_turns = friday_turns.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id)
friday_turns = friday_turns.add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id, Tetawebapp_turns.start_time).all()
saturday_turns = Tetawebapp_turns.query.filter(Tetawebapp_turns.start_time > saturday, Tetawebapp_turns.start_time < sunday)
saturday_turns = saturday_turns.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id)
saturday_turns = saturday_turns.add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id, Tetawebapp_turns.start_time).all()
sunday_turns = Tetawebapp_turns.query.filter(Tetawebapp_turns.start_time > sunday, Tetawebapp_turns.start_time < monday)
sunday_turns = sunday_turns.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id)
sunday_turns = sunday_turns.add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id, Tetawebapp_turns.start_time).all()
turns.append(('Mardi', tuesday_turns))
turns.append(('Mercredi', wenesday_turns))
turns.append(('Jeudi', thirsday_turns))
turns.append(('Vendredi', friday_turns))
turns.append(('Samedi', saturday_turns))
turns.append(('Dimanche', sunday_turns))
return turns
def get_staffs():
""" """
try:
staffs = Tetawebapp_staffs.query.join(Tetawebapp_users, Tetawebapp_staffs.user_id==Tetawebapp_users.id)
staffs = staffs.add_columns(Tetawebapp_users.name).all()
return staffs
except Exception as e:
print "[+] Error at get_staffs:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
def drop_staff_slot(turn_id, slot_num, user_id):
""" Drop staff given slot """
slot = Tetawebapp_staffs.query.filter_by(turn_id=turn_id, slot_num=slot_num).first()
if slot.user_id == user_id:
try:
Tetawebapp_staffs.query.filter_by(turn_id=turn_id, slot_num=slot_num).delete()
db.session.commit()
return True
except Exception as e:
db.session.rollback()
print "[+] Error at drop_staff_slot:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
return False
def save_staff_slot(turn_id, slot_id, user_id):
""" Save staff given slot """
slot = Tetawebapp_staffs(user_id=user_id,
turn_id=turn_id,
slot_num=slot_id)
try:
db.session.add(slot)
commit = db.session.commit()
except Exception as e:
db.session.rollback()
print "[+] Error at save_staff_slot:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
if commit != None:
return False
return True
def get_roles():
""" Get full roles list """
try:
roles = Tetawebapp_roles.query.filter(Tetawebapp_roles.id > 3).all()
return roles
except Exception as e:
print "[+] Error at get_staffs:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
def check_user_info():
""" Check user info and send appropriate message if info are not complete"""
message = ''
@ -351,11 +545,28 @@ def check_user_info():
message = "Vos informations personnelles ne sont pas complètement renseignées. N'oubliez pas de remplir votre fiche située dans la section 'Mon compte'"
return message.decode('utf-8')
def gen_token():
def gen_token(size=42):
""" Generate a random token to be stored in session and cookie """
token = binascii.hexlify(os.urandom(42))
token = binascii.hexlify(os.urandom(size))
return token
def send_mail(email, link_id):
msg = Message("Confirmation d'inscription au staff THSF",
sender="dave.null@tetalab.org",
recipients=[email])
msg.body = "Bonjour,\nVous recevez ce courriel car vous avez souhaité faire partie de l'équipe du staff du THSF.\n\n"
msg.body += "Pour confirmer votre inscription, rendez vous à la page suivante:\n"
msg.body += "https://wemake.thsf.net/confirm/%s\n\n" % str(link_id)
msg.body += "Si vous n'êtes pas à l'origine de cette inscription, ignorez simplement ce courriel.\n"
msg.body += "Votre compte sera automatiquement supprimé dans les 24 heures.\n\n"
msg.body += "Si vous désirez vous désinscrire, faites simplement une demande sur https://bofh.tetalab.org\n\n"
msg.body += "Dans tous les cas la base de données sera totalement réinitialisée à la fin du THSF.\n\n"
msg.body += "Votre aide nous est précieuse.\nMerci et bravo à vous.\n"
msg.body += "-- \n"
msg.body += "L'équipe d'organisation du THSF\n"
msg.body += "(Ce mail vous est envoyé par un robot qui n' a pas réussi le test de Turing, inutile de lui répondre)"
mail.send(msg)
########################################################################
# Routes:
# -------
@ -388,11 +599,50 @@ def login():
# Credentials are not valid
response = app.make_response(render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide"))
session['token'] = ''
session['login'] = ''
session['is_admin'] = 0
sync_cookies(response, session)
return response
except AttributeError:
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@app.route("/confirm/<LINK_ID>", methods=['GET', 'POST'])
def confirm(LINK_ID):
""" Index page """
link_id = LINK_ID.encode('utf-8')
return render_template('confirm.html', message="Merci de confirmer votre enregistrement", link_id=link_id)
@app.route("/confirm/link/<LINK_ID>", methods=['GET', 'POST'])
def confirm_link(LINK_ID):
""" Index page """
try:
link_id = LINK_ID.encode('utf-8')
login = request.form.get('login').encode('utf-8')
password = request.form.get('password').encode('utf-8')
if check_confirm(login, password, link_id) and confirm_user(login, password, link_id):
# Generate and store a token in session
session['token'] = gen_token()
session['login'] = login
# Return user to index page
page = '/'
menu = get_menu(page)
message = check_user_info()
response = app.make_response(render_template('index.html', menu=menu, message=message, login=login))
# Push token to cookie
sync_cookies(response, session)
return response
# Credentials are not valid
response = app.make_response(render_template('confirm.html', message="Utilisateur ou mot de passe invalide", link_id=link_id))
session['token'] = ''
session['login'] = ''
session['is_admin'] = 0
sync_cookies(response, session)
return response
except AttributeError:
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
link_id = LINK_ID.encode('utf-8')
return render_template('confirm.html', message="Merci de confirmer votre enregistrement", link_id=link_id)
@app.route("/register", methods=['GET', 'POST'])
def register():
""" Allow self registration """
@ -400,24 +650,11 @@ def register():
login = request.form.get('login').encode('utf-8')
password = request.form.get('password').encode('utf-8')
confirm = request.form.get('confirm').encode('utf-8')
if register_user(login, password, confirm):
# Generate and store a token in session
session['token'] = gen_token()
session['login'] = login
# Return user to index page
page = '/'
menu = get_menu(page)
message = check_user_info()
response = app.make_response(render_template('index.html', menu=menu, login=login, message=message))
# Push token to cookie
sync_cookies(response, session)
return response
# Error while registering user
message = "Erreur lors de l'enregsitrement: L'utilisateur existe t-il déjà ?".decode('utf-8')
response = app.make_response(render_template('login_or_register.html', message=message))
session['token'] = ''
sync_cookies(response, session)
return response
if register_user(login, password, confirm):
message = "Merci de votre engagement. Un courriel contenant les instructions de confirmation votre inscription vient de vous être envoyé."
# Error while registering user
return render_template('login_or_register.html', message=message.decode('utf-8'))
except AttributeError:
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@ -578,15 +815,20 @@ def delete_account(ID):
@app.route("/turns", methods=['GET', 'POST'])
@check_session
def list_turn():
def list_turns():
""" List staff turns """
try:
page = str(request.url_rule)
menu = get_menu(page)
message = ''
if session['is_admin']:
page = str(request.url_rule)
menu = get_menu(page)
turns = Tetawebapp_turns.query.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id).add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id).all()
turns = turns_list()
message = ''
return render_template('list_turns.html', menu=menu, page=page, turns=turns, message=message)
# TODO:
# Here comes the list_turns_by_user_id code
except AttributeError:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@ -595,12 +837,19 @@ def list_turn():
@check_session
def new_turn():
""" New turn form """
tuesday = '2018-05-08'
wenesday = '2018-05-09'
thirsday = '2018-05-10'
friday = '2018-05-11'
saturday = '2018-05-12'
sunday = '2018-05-13'
monday = '2018-05-14'
try:
if session['is_admin']:
page = str(request.url_rule)
menu = get_menu(page)
roles = Tetawebapp_roles.query.order_by(Tetawebapp_roles.id).all()
days = ['Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
days = [('Mardi', tuesday), ('Mercredi', wenesday), ('Jeudi', thirsday), ('Vendredi', friday), ('Samedi', saturday), ('Dimanche', sunday)]
return render_template('new_turn.html', menu=menu, page=page, roles=roles, days=days)
except AttributeError:
# User is not logged in
@ -616,18 +865,15 @@ def add_turn():
day = request.form.get('day').encode('utf-8')
start = request.form.get('start').encode('utf-8')
end = request.form.get('end').encode('utf-8')
num_slot = request.form.get('num_slot').encode('utf-8')
page = str(request.url_rule)
menu = get_menu(page)
turns = Tetawebapp_turns.query.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id).add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id).all()
turns = turns_list()
message = "Erreur lors de l'enregistrement.".decode('utf-8')
if save_turn(role_id, day, start, end):
turns = Tetawebapp_turns.query.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id).add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id).all()
if save_turn(role_id, day, start, end, num_slot):
turns = turns_list()
message=''
return render_template('list_turns.html', menu=menu, page=page, turns=turns, message=message)
# Error while saving turn
roles = Tetawebapp_roles.query.order_by(Tetawebapp_roles.id).all()
days = ['Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
return render_template('new_turn.html', menu=menu, page=page, roles=roles, days=days, message=message)
# User is not admin
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except AttributeError as e:
@ -639,14 +885,21 @@ def add_turn():
def turn_by_id(ID):
try:
if session['is_admin']:
tuesday = '2018-05-08'
wenesday = '2018-05-09'
thirsday = '2018-05-10'
friday = '2018-05-11'
saturday = '2018-05-12'
sunday = '2018-05-13'
monday = '2018-05-14'
days = [('Mardi', tuesday), ('Mercredi', wenesday), ('Jeudi', thirsday), ('Vendredi', friday), ('Samedi', saturday), ('Dimanche', sunday)]
page = str(request.url_rule)
menu = get_menu(page)
roles = Tetawebapp_roles.query.order_by(Tetawebapp_roles.id).all()
days = ['Jeudi', 'Vendredi', 'Samedi', 'Dimanche']
message = 'ID du tour de staff non conforme'
turns = Tetawebapp_turns.query.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id).add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id).all()
turns = turns_list()
turn_id = int(ID.encode('utf-8'))
turn = Tetawebapp_turns.query.filter_by(id=ID).first()
turn = get_turn_by_id(turn_id)
return render_template('turn_by_id.html', menu=menu, page=page, turn=turn, roles=roles, days=days)
except AttributeError:
# User is not logged in
@ -661,17 +914,18 @@ def update_turn(ID):
""" Update given staff turn """
try:
role_id = request.form.get('role_id').encode('utf-8')
day = request.form.get('day').encode('utf-8')
start = request.form.get('start').encode('utf-8')
end = request.form.get('end').encode('utf-8')
num_slot = request.form.get('num_slot').encode('utf-8')
day = request.form.get('day').encode('utf-8')
if session['is_admin']:
page = str(request.url_rule)
menu = get_menu(page)
turns = Tetawebapp_turns.query.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id).add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id).all()
turns = turns_list()
message = "Erreur lors de l'enregistrement.".decode('utf-8')
turn_id = int(ID.encode('utf-8'))
if update_turn_by_id(turn_id, role_id, day, start, end):
turns = Tetawebapp_turns.query.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id).add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id).all()
if update_turn_by_id(turn_id, role_id, day, start, end, num_slot):
turns = turns_list()
message = ''
return render_template('list_turns.html', menu=menu, page=page, turns=turns, message=message)
# User is not admin
@ -692,11 +946,11 @@ def delete_turn(ID):
message = 'Erreur lors de la suppression.'
page = str(request.url_rule)
menu = get_menu(page)
turns = Tetawebapp_turns.query.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id).add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id).all()
turns = turns_list()
turn_id = int(ID.encode('utf-8'))
if drop_turn(turn_id):
message = ''
turns = Tetawebapp_turns.query.join(Tetawebapp_roles, Tetawebapp_turns.role_id==Tetawebapp_roles.id).add_columns(Tetawebapp_roles.role).order_by(Tetawebapp_turns.role_id).all()
turns = turns_list()
return render_template('list_turns.html', menu=menu, turns=turns, message=message)
return render_template('list_turns.html', menu=menu, turns=turns, message=message)
# User is not admin
@ -708,6 +962,201 @@ def delete_turn(ID):
# ID is not an integer
return render_template('list_turns.html', menu=menu, page=page, turns=turns, message=message)
@app.route("/staffsheet", methods=['GET', 'POST'])
@check_session
def staffsheet():
try:
user_id = session['user_id']
if len(check_user_info()) == 0:
page = str(request.url_rule)
menu = get_menu(page)
turns = turns_list()
staffs = get_staffs()
roles = get_roles()
return render_template('staffsheet.html', menu=menu, turns=turns, staffs=staffs, user_id=user_id, roles=roles, message='')
else:
return account()
except AttributeError as e:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@app.route("/staffsheet/pdf", methods=['GET', 'POST'])
def staffsheet_pdf():
turns = turns_list()
staffs = get_staffs()
roles = get_roles()
data = render_template('staffsheet_txt.html', turns=turns, staffs=staffs, roles=roles)
data = [line for line in data.split('\n') if len(line) > 0]
data = '\n'.join(data)
data = [line for line in data.split('",\n') if len(line) > 0]
data = '", '.join(data)
ddata = [ast.literal_eval(line) for line in data.split('\n')]
pdf = cStringIO.StringIO()
doc = SimpleDocTemplate(pdf, pagesize=A4, rightMargin=3,leftMargin=3, topMargin=3,bottomMargin=3)
elements = []
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name='master_title',
alignment=TA_LEFT,
fontSize=20,
leading=25,
spaceBefore=10,
spaceAfter=10,
))
styles.add(ParagraphStyle(name='day_title',
alignment=TA_LEFT,
backColor=orange,
borderWidth=1,
borderColor=black,
borderPadding=5,
spaceBefore=10,
spaceAfter=10
))
styles.add(ParagraphStyle(name='role_title',
alignment=TA_LEFT,
backColor=lightsalmon,
borderWidth=1,
borderColor=black,
borderPadding=5,
spaceBefore=10,
spaceAfter=10
))
styles.add(ParagraphStyle(name='turn',
alignment=TA_LEFT,
backColor=white,
borderWidth=1,
borderColor=black,
borderPadding=5,
spaceBefore=10,
spaceAfter=0
))
styles.add(ParagraphStyle(name='basic_text',
alignment=TA_LEFT,
spaceBefore=5,
spaceAfter=5
))
styles.add(ParagraphStyle(name='row1',
alignment=TA_LEFT,
backColor=white,
spaceBefore=10,
spaceAfter=10
))
styles.add(ParagraphStyle(name='row2',
alignment=TA_CENTER,
backColor=white,
spaceBefore=10,
spaceAfter=10
))
elements.append(Paragraph("<b>Fiches de poste</b>", styles['master_title']))
elements.append(Paragraph("Les postes de référents (référent staff, référent bar, référent run) sont réservés à des personnes ayant une bonne connaissance du lieu et de l'évènement.",
styles['basic_text']))
for role in roles:
elements.append(Paragraph("<b>%s</b>" % role.role, styles['day_title']))
desc = role.description.split('|')
for point in desc:
elements.append(Paragraph(point, styles['basic_text'], bulletText='-'))
elements.append(PageBreak())
elements.append(Paragraph("<b>Feuilles de staff</b>", styles['master_title']))
elements.append(Paragraph("<b>Ménage le soir même pour tous les derniers créneaux du jour</b>", styles['basic_text'], bulletText='-'))
elements.append(Paragraph("Tâches dévolues <b>à tous et à tous moments</b>:", styles['basic_text'], bulletText='-'))
elements.append(Indenter(left=20, right=0) )
elements.append(Paragraph("Veiller à la sécurité générale du lieu", styles['basic_text'], bulletText='-'))
elements.append(Paragraph("Ramassage bouteilles ou objets en verre", styles['basic_text'], bulletText='-'))
elements.append(Paragraph("Séparation des bagarres (rarissime)", styles['basic_text'], bulletText='-'))
elements.append(Paragraph("Sécurisation des personnes en difficulté (ou trop alcoolisées), etc...", styles['basic_text'], bulletText='-'))
elements.append(Paragraph("<b>Sourire et bonne humeur</b> quel que soit le niveau de fatigue ;)", styles['basic_text'], bulletText='-'))
elements.append(Indenter(left=-20, right=0) )
for day in turns:
wday = day[0]
day_turns = day[1]
cur_role = ''
if wday not in ['Mardi', 'Mercredi']:
elements.append(PageBreak())
elements.append(Paragraph("<b>%s</b>" % wday, styles['day_title']))
for turn in day_turns:
rows = []
role = turn[1]
start_time = turn[0].start_time
end_time = turn[0].end_time
num_slot = turn[0].num_slot
role_id = turn[0].role_id
turn_id = turn[0].id
if role != cur_role:
cur_role = role
elements.append(Paragraph("<b>%s</b>" % role, styles['role_title']))
row = (Paragraph("<b>%s / %s</b>" % (start_time.strftime('%HH%M'), end_time.strftime('%HH%M')), styles['row1']),)
for slot in range(0, num_slot):
cols_width = [100] + [485/num_slot] * num_slot
allocated_slot = []
for sslot in staffs:
if sslot[0].turn_id == turn_id and sslot[0].slot_num == slot:
allocated_slot.append(sslot[0].slot_num)
row += (Paragraph(sslot[1], styles['row2']),)
if slot not in allocated_slot:
row += (Paragraph("&nbsp;", styles['row2']),)
rows.append(row)
table = Table(rows, colWidths=cols_width, rowHeights=23)
table.setStyle(TableStyle( [('GRID', (0,0), (-1,-1), 0.25, colors.black),
('ALIGN', (1,1), (-1,-1), 'RIGHT')]))
elements.append(table)
styles.wordWrap = 'CJK'
doc.build(elements)
pdf_out = pdf.getvalue()
pdf.close()
response = app.make_response(pdf_out)
response.headers['Content-Disposition'] = "attachment; filename=feuille_staff_thsf.pdf"
response.mimetype = 'application/pdf'
return response
@app.route("/staffsheet/clear/<TURN_ID>/<SLOT_ID>", methods=['GET', 'POST'])
@check_session
def clear_staff_slot(TURN_ID, SLOT_ID):
try:
turn_id = int(TURN_ID.encode('utf-8'))
slot_id = int(SLOT_ID.encode('utf-8'))
user_id = session['user_id']
message = "Erreur lors de l'enregistrement des données"
if (drop_staff_slot(turn_id, slot_id, user_id)):
message = ''
page = str(request.url_rule)
menu = get_menu(page)
turns = turns_list()
staffs = get_staffs()
roles = get_roles()
user_id = session['user_id']
return render_template('staffsheet.html', menu=menu, turns=turns, staffs=staffs, user_id=user_id, roles=roles, message=message.decode('utf-8'))
except AttributeError:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except ValueError:
# At least one ID is not integer
return render_template('login_or_register.html', message="Identifiants non conformes")
@app.route("/staffsheet/update/<TURN_ID>/<SLOT_ID>", methods=['GET', 'POST'])
@check_session
def update_staff_slot(TURN_ID, SLOT_ID):
try:
turn_id = int(TURN_ID.encode('utf-8'))
slot_id = int(SLOT_ID.encode('utf-8'))
user_id = session['user_id']
message = "Erreur lors de l'enregistrement des données"
if (save_staff_slot(turn_id, slot_id, user_id)):
message = ''
page = str(request.url_rule)
menu = get_menu(page)
turns = turns_list()
staffs = get_staffs()
roles = get_roles()
user_id = session['user_id']
return render_template('staffsheet.html', menu=menu, turns=turns, staffs=staffs, user_id=user_id, roles=roles, message=message.decode('utf-8'))
except AttributeError as e:
# User is not logged in
print e
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except ValueError:
# At least one ID is not integer
return render_template('login_or_register.html', message="Identifiants non conformes")
########################################################################
# Main
########################################################################

View File

@ -37,7 +37,8 @@ CREATE TABLE participer_thsf_users (
name text,
phone text,
diet text,
is_admin integer not NULL
is_admin integer not NULL,
link_id text
);
@ -48,24 +49,37 @@ CREATE TABLE participer_thsf_users (
CREATE TABLE participer_thsf_roles (
id serial primary key,
role text not NULL,
description text not NULL
description text not NULL,
num_slot integer not null default 2
);
\echo *********************************
\echo * Creating participer_thsf_turns table
\echo *********************************
CREATE TYPE dow AS ENUM ('Jeudi', 'Vendredi', 'Samedi', 'Dimanche');
CREATE TABLE participer_thsf_turns (
id serial primary key,
role_id integer not NULL,
user_id integer,
wday dow not NULL,
start_time time not NULL,
end_time time not NULL,
start_time timestamp not NULL,
end_time timestamp not NULL,
num_slot integer not NULL default 2,
constraint fk_turns_role
foreign key (role_id)
REFERENCES participer_thsf_roles (id),
constraint fk_turns_user
REFERENCES participer_thsf_roles (id)
);
\echo *********************************
\echo * Creating participer_thsf_staffs table
\echo *********************************
CREATE TABLE participer_thsf_staffs (
id serial primary key,
user_id integer not NULL,
turn_id integer not NULL,
slot_num integer not NULL,
constraint fk_turns_turn_id
foreign key (turn_id)
REFERENCES participer_thsf_turns(id),
constraint fk_turns_staff_user_id
foreign key (user_id)
REFERENCES participer_thsf_users (id)
);
@ -85,6 +99,11 @@ alter table participer_thsf_roles owner to participer_thsf;
\echo *************************************************
alter table participer_thsf_turns owner to participer_thsf;
\echo *************************************************
\echo * Giving participer_thsf_staffs ownership to participer_thsf
\echo *************************************************
alter table participer_thsf_staffs owner to participer_thsf;
\echo *********************************************************************
\echo * Inserting user demo identified by password demo to participer_thsf_users
\echo *********************************************************************
@ -98,10 +117,108 @@ insert into participer_thsf_users (mail, password, name, phone, diet, is_admin)
\echo *********************************************************************
\echo * Inserting roles to participer_thsf_roles
\echo *********************************************************************
insert into participer_thsf_roles (role, description) values ('Référent staff', '');
insert into participer_thsf_roles (role, description) values ('Référent run', '');
insert into participer_thsf_roles (role, description) values ('Référent bar', '');
insert into participer_thsf_roles (role, description) values ('Préparation/Installation/signalétique', 'Fermeture des zones inaccessibles au public|Prépa buvette : allumage tireuses et frigos 4h avant ouverture + approvisionnement des bars en produits destinés à la vente|Préparation des différents postes / stands selon besoins : PLN, billetterie, T-Shirts...|Prépa toilettes public : aspiration/pompage, nettoyage à grandes eaux, remplissage du produit, changement poubelles, approvisionnement PQ|Nettoyage lieu, réapprovisionnement poubelles aux endroits stratégiques');
insert into participer_thsf_roles (role, description) values ('Volante', 'veille à la sécurité du lieu : rondes, surveillance accès, gestion de crises, messager, remplaçant, etc... (collaboration avec référent staff)|Ramassage verre, check PQ, ménage ponctuel|Sur le dernier créneau horaire, nettoyage de deux blocs WC|Au moins une personne connaissant bien Myrys sur chaque créneau horaire');
insert into participer_thsf_roles (role, description) values ('Volante', 'Veiller à la sécurité du lieu : rondes, surveillance accès, gestion de crises, messager, remplaçant, etc... (collaboration avec référent staff)|Ramassage verre, check PQ, ménage ponctuel|Sur le dernier créneau horaire, nettoyage de deux blocs WC|Au moins une personne connaissant bien Myrys sur chaque créneau horaire');
insert into participer_thsf_roles (role, description) values ('P.L.N', 'Accueil public, explication du concept de PLN, tampons (si besoin)|Informer et guider le public (prog, espaces, toilettes,...)|Comptage du public');
insert into participer_thsf_roles (role, description) values ('Buvette','Service au bar et encaissement (caisse ou tickets boisson selon besoins)|Nettoyage régulier : bar, tireuses, cendriers, verres abandonnés...');
insert into participer_thsf_roles (role, description) values ('Buvette ''bulle''','Service au bar et encaissement (caisse ou tickets boisson selon besoins)|Nettoyage régulier : bar, tireuses, cendriers, verres abandonnés...');
insert into participer_thsf_roles (role, description) values ('Bar ''concert''','Service au bar et encaissement (caisse ou tickets boisson selon besoins)|Nettoyage régulier : bar, tireuses, cendriers, verres abandonnés...');
insert into participer_thsf_roles (role, description) values ('Billeterie','Vente de tickets boisson / 1 ticket = 2,50 €|Poinçonnage des cartes VIP. 1 poinçon = 1 ticket = 1 euros|Tickets valables sur les 4 jours mais NON REMBOURSABLES');
insert into participer_thsf_roles (role, description) values ('Catering', 'Vérification de l''accès au catering (badge)|Service à l''assiette si besoin, gestion des quantités, réapprovisionnement du buffet|Maintenir la propreté des espaces catering (cuisine, buffet, tables, salle d''AG, tables extérieures, jardins)|Informer / rappeler le principe d''autogestion pour la vaisselle');
insert into participer_thsf_roles (role, description) values ('Ménage', 'Nettoyage général à la fermeture du lieu : ramasser verres / déchets / cendriers, sortir poubelles, cleaner surfaces (bar, stand,...) + balai');
\echo *********************************************************************
\echo * Inserting turns to participer_thsf_turns
\echo *********************************************************************
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (1, '2018-05-10 20:00:00', '2018-05-11 02:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (1, '2018-05-10 12:00:00', '2018-05-10 20:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (1, '2018-05-11 12:00:00', '2018-05-11 21:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (1, '2018-05-12 12:00:00', '2018-05-12 21:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (1, '2018-05-11 21:00:00', '2018-05-12 04:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (1, '2018-05-12 21:00:00', '2018-05-13 05:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (1, '2018-05-13 12:00:00', '2018-05-13 18:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (1, '2018-05-13 18:00:00', '2018-05-13 23:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (2, '2018-05-10 12:00:00', '2018-05-10 21:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (2, '2018-05-11 12:00:00', '2018-05-11 21:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (2, '2018-05-12 12:00:00', '2018-05-12 21:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (2, '2018-05-13 12:00:00', '2018-05-13 21:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (3, '2018-05-10 12:00:00', '2018-05-10 21:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (3, '2018-05-11 12:00:00', '2018-05-11 21:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (3, '2018-05-12 12:00:00', '2018-05-12 21:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (3, '2018-05-13 12:00:00', '2018-05-13 21:00:00', 1);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (4, '2018-05-08 14:00:00', '2018-05-08 18:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (4, '2018-05-09 14:00:00', '2018-05-09 18:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (4, '2018-05-10 13:00:00', '2018-05-10 15:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-10 14:00:00', '2018-05-10 17:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-10 17:00:00', '2018-05-10 20:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-10 20:00:00', '2018-05-10 23:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-10 23:00:00', '2018-05-11 02:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-10 14:00:00', '2018-05-10 17:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-10 17:00:00', '2018-05-10 20:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-10 20:00:00', '2018-05-10 23:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-10 23:00:00', '2018-05-11 02:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (9, '2018-05-10 20:00:00', '2018-05-10 23:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (9, '2018-05-10 23:00:00', '2018-05-11 02:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (7, '2018-05-10 14:00:00', '2018-05-10 17:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (7, '2018-05-10 17:00:00', '2018-05-10 21:00:00', 3);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (10, '2018-05-10 12:00:00', '2018-05-10 14:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (10, '2018-05-10 19:30:00', '2018-05-10 22:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (4, '2018-05-11 13:00:00', '2018-05-11 14:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-11 14:00:00', '2018-05-11 17:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-11 17:00:00', '2018-05-11 20:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-11 20:00:00', '2018-05-11 23:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-11 23:00:00', '2018-05-12 01:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-12 01:30:00', '2018-05-12 03:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-11 14:00:00', '2018-05-11 17:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-11 17:00:00', '2018-05-11 20:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-11 20:00:00', '2018-05-11 23:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-11 23:00:00', '2018-05-12 01:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-12 01:30:00', '2018-05-12 03:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (9, '2018-05-11 20:00:00', '2018-05-11 23:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (9, '2018-05-11 23:00:00', '2018-05-12 01:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (9, '2018-05-12 01:30:00', '2018-05-12 03:15:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (7, '2018-05-11 14:00:00', '2018-05-11 17:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (7, '2018-05-11 17:00:00', '2018-05-11 20:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (7, '2018-05-11 20:00:00', '2018-05-11 22:00:00', 3);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (8, '2018-05-11 20:00:00', '2018-05-11 23:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (8, '2018-05-11 23:00:00', '2018-05-12 01:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (8, '2018-05-12 01:30:00', '2018-05-12 03:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (10, '2018-05-11 12:00:00', '2018-05-11 14:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (10, '2018-05-11 19:30:00', '2018-05-11 22:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (4, '2018-05-12 12:00:00', '2018-05-12 13:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-12 13:30:00', '2018-05-12 16:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-12 16:30:00', '2018-05-12 19:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-12 19:30:00', '2018-05-12 22:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-12 22:30:00', '2018-05-13 01:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-13 01:30:00', '2018-05-13 04:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-12 13:30:00', '2018-05-12 16:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-12 16:30:00', '2018-05-12 19:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-12 19:30:00', '2018-05-12 22:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-12 22:30:00', '2018-05-13 01:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-13 01:30:00', '2018-05-13 04:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (9, '2018-05-12 19:30:00', '2018-05-12 22:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (9, '2018-05-12 22:30:00', '2018-05-13 01:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (9, '2018-05-13 01:30:00', '2018-05-13 04:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (7, '2018-05-12 13:30:00', '2018-05-12 16:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (7, '2018-05-12 16:30:00', '2018-05-12 19:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (8, '2018-05-12 19:30:00', '2018-05-12 22:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (8, '2018-05-12 22:30:00', '2018-05-13 01:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (7, '2018-05-12 19:30:00', '2018-05-12 22:00:00', 3);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (8, '2018-05-13 01:30:00', '2018-05-13 04:30:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (10, '2018-05-12 12:00:00', '2018-05-12 14:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (10, '2018-05-12 19:30:00', '2018-05-12 22:30:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (4, '2018-05-13 13:00:00', '2018-05-13 14:00:00', 4);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-13 14:00:00', '2018-05-13 17:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-13 17:00:00', '2018-05-13 20:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (5, '2018-05-13 20:00:00', '2018-05-13 22:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-13 14:00:00', '2018-05-13 17:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-13 17:00:00', '2018-05-13 20:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (6, '2018-05-13 20:00:00', '2018-05-13 22:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (7, '2018-05-13 14:00:00', '2018-05-13 17:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (7, '2018-05-13 17:00:00', '2018-05-13 19:00:00', 3);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (8, '2018-05-13 19:00:00', '2018-05-13 22:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (10, '2018-05-13 12:00:00', '2018-05-13 14:00:00', 2);
INSERT INTO participer_thsf_turns (role_id, start_time, end_time, num_slot) VALUES (10, '2018-05-13 19:30:00', '2018-05-13 21:30:00', 2);

213
pdf Normal file
View File

@ -0,0 +1,213 @@
%PDF-1.4
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
1 0 obj
<<
/F1 2 0 R /F2 3 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/Contents 15 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/Contents 17 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/Contents 18 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Contents 19 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
9 0 obj
<<
/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
10 0 obj
<<
/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 14 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
12 0 obj
<<
/Outlines 23 0 R /PageMode /UseNone /Pages 14 0 R /Type /Catalog
>>
endobj
13 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20180305200013-01'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20180305200013-01'00') /Producer (ReportLab PDF Library - www.reportlab.com)
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
14 0 obj
<<
/Count 8 /Kids [ 4 0 R 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R ] /Type /Pages
>>
endobj
15 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1895
>>
stream
Gau`U?!#c?&:N_CbT!o.P1]Tu3VuX!g7Fl<PX:[aac!5K7]g14P*IWrlh?G+KVX:&qp>+'"3@dD);ViEB,XS_?\/6A9;7=nFZopBKJZt2cu8YmH:dS'-f7^OC?C2hh,/M]SrE1@`k-]T`$]defW7!(kn5M#'R"buE5:e0IF.ON=dgk`1L$eZ2#U%qf=_)!Hu8=,)YO)RO.]itK&c]%ps@[S&j,mMrC1DM)[$QJ0IeY*c-CK&a;/6iB`0cK,%7(&)4/WTOj3Z&*jj(kr(idSqH[olI?hY?'YfJpJU&kJ'_)/pHjX_m8EIkZZali!.n?VtUF)nO)DBc<eIjsP%ln;74EJm:7ni[_s4YBiW&pB25"k-f^u'e7W#^m&6OH19MCr64(&,<u>[QBKB5bH9\M0c)=k`\9_KSRLraBr>MM.T<qR\h6[GbZ^jSQCA^!QEYl0R3/MBrcSHM?eu]@Xs,koAncAuQ+Bg\IlKc1m`L.\p%pl6tUaF-O9bd;j[5o;C47=V?D4=L,XHMVLf=Kq^MZMf;<$.k7o'cGCEKC124S=mA55=>:dO9K1f1Iuq`mPHD4B$r9?^pBkl#2cIT"\^L_;O=bRc"nP./$F<_q(C_aEAmJAFe_<"6M_--O.&+T^ONoF\Q!dpoCo1,=LYc9B[j]Zr_@AJ-[:jl8pRk5hg[0/Yj+kO=N8Mre[B8?dqEBQHRJ%EK$J>h&S*MPWo5'fs1bD<S-[TD%pe-VVI)6^5mK[u5/b=Y+$]dSV'fK5@+%.$6fZ>oc.JV&op:08O3S&)^*+4S2^EJ?AhKXTc\OlkJ&&0h7K^f/l1,g)+MCmadINrG/Zo'QN5#8&jS&b!(7&1(:mpORZ$G#ngA'So3`j/+21YTI,eg^2"R>`<C,4!I1hr2dO?usVc(lk#oI,pJJg@EIanea8"U'7tB_%A7qV*n-I)orHR6s2qs@\rd0K"VM#p?I`$7kYLkmfi?c=GekO62X2la=?upd0F=Rq,;a,B;mGP9'QG#0PDjon!p))F14^un0.`^UgPTs^kHL/_d[]d7W:gg-8#2pY,6(7L(a>e7n]Iu=>6mHjHaD2+P"QU=-)Lk1>nRH_(g;,:t*dqH[PM=^2.-u#a50tglV-(F<HYp6%TPp+PY"12`SMTWH,2h]d>/G&eW$HK[l>oGRFGfDXWX=Ak`,e/i&5i_kJi.)DT1(<%3FP)%p0_]59/GoAi@s[(qR,D&`2<Z0`Pg9X0m1^S'H\42HB9(q?O:qJ##PjA$4S_bPk+P@d\BC<iW8MZF$@8gUf?WI9jQr?%sa:hn'i$Gn150S3]cOjLLUdsY).*)#XD&rg26C#r&lqQHqJA5(QuG_0:N%Ya,)+HAb4^*p!e%.qQ[0A%psJZ#*S9Q:>S]uG*hOhoUa.`!2GC^oJ"$XYE*WI+\[@8fbsSpM6C_gf['#HsJ(oI#?p6'l1l[p.2'@8m""L.)?I(D_gHg!>huE0YqQl)'Fdo$%;>[:N`2b*f>CD&P<td\kV9XTYXYY-Y7^rg*EQ206dc'!spUT<IMQrRJmM^M`Ms?<k4ZX(bQ9H2jGn^e!KQOjR.F^\<G`?>Ll?GG<]31c)J>%M(U\`&q(CaYmrbZ`g]-fg7Ye"g#E&g?A4smXV/&%L#nd?Ec*VHlt$d[ofT'T'aA(To/kBBFnIL%JNV*6uGuS38o$eWURB8<'"-LU38/kj77pLY!lA:>]$+IrO<n9@8rIn!Jg0/YJSM5+o)>[L/c0iqWX*Xc)RQhJ)CC-F#Hq+,$RDYc^]49b"E7)Tr)u2j#?P3eifN*]Fk@,[ITnW:[AVW_'>+;CN&b8+#6Vt4l<N]"]bDs6Z_,bI,KD!fbR+:1D!;cSS++B&h.-U[TIdE1LZ]b*GOF'0?[Sr""Gi6qBmB5aaYZg2qn~>endstream
endobj
16 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 955
>>
stream
Gb"/%>>O!-'RoMS30+*"?p",M%;G/QO9S+"lAM.C`DnP0:1-;9a%k?aPbOmh'MTumS/`UmJ"MeJk>3:G;1$UrJ?oqbcRDZpkmXNZqN,%!kB(f)U7Gou0o=[F:1>2nG%0iR5/O"8*A)fK^dnkN0p,RGi]-+qOa/Q99EjXKcXo\//g*;81L/lrS0a&F_(6PA=W8>hg>E2%"fuZnF(1nRaJY`:'sur``:Z7Z9;g]QNkKt/HnHO3Pa,3ETS4*^Q;s]QXr7a$#?=Bi1h=ah&A7fBdfpTO[Gl>Wa*ihS)#l&(m6U]0m@ZX65<Ks550IgQ#m:.?ar>`:1&HZEd##,O71K2g;&,h,Fm$FFmX3fF1IKRkrMOq;]CPesR(]D$-92<JW+V*Q[<[;59qo0INeteEk#+&5P4JOgOLjQXf@k&l8_)=bXfe[TC9eC.-E`#KTp42r#)s*[j2S1tOt0k^Wl3'.0]@)5^bq&k`KeqV(1MZu0+UAZaM[?nh(r3.7T<qJCc8/22Nq:-[sN#Z@&dsA/V3(f<TVZs8YO0p+0al_f6-g5*6-iN-jeX+OrG)sWJ"-)M'3279d)Rr)i+UO2'Ot<,H/!^el(A"@%)^UL.aDESko6[(WhDRlhn<mJdr=dG=+S=6.4nuKTj4LJiIdR'SO7=+P=#1)t=*PK6,/b<'GA,KjO<i-5QEEDV'qYhkPZ;fafsAS^VL'*isYD^1]M[H#EIoM2T+^J)H&f'I<d5,[Ae8Jp=qX2SKG`n64Q0"!'JP'?A*3=j:9uMY^ZdJQ&3r[QAJ-Z>4>MOr$g+8M9L;9"T\0op-S"[k!P/DZY^Wa);$7+!q64nK%Xf^(+HH!.D'_Qj3dqSSoQi\ERD/-F.&']h@,UBH/>#T4unH`i(b`G&>0Y/Sd31g?=<`24Cs04r+m'me)9/W(`%\[4Va<s.FK89A[)E?0g@TmU03'UYkMbV=F~>endstream
endobj
17 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1197
>>
stream
Gb"/)>B?5u'RodXS@>;^$/,3mX)6<BPA-N9ku1q_7E:@ML1'oQHPR0s$7]$.UeIMPS6an\c?HEA"bJrqIfD,-kRZV2Oof2K(]tM5JAD]-+7u.N1gT?sbD>2iSr4DXSAb!1&%LOV^)SFUOWKCs8M>U8BmunZ*N#Y7`ZXoIJ(D6VY/Fn].Euhic?$q>J<C`O1C-aZrP`IH"pPSl3&>esj%m`NC!Wou`3bUc$LJ0qs"1Ogmp+;GGmjQnK'-f,*gZqn%cpl@'"!f>hVrGC__6E8b+i%RPP`6TOPY_[EF#mhiK*_g:Y?I+DukpU=GiT2ZFttSHYgEBF,WZ1j#B+;_.hsp7kjLBmY<$:$08b/Uk%_K?Os]9*@=Y2"q]fK8#8iL`sf\Z+;5>iOcXr^1ATOEPU,S=QjV#Y.@;-eN-0rf,ch/TEdSobeKs;Q.u5e++$,Br7s7"m&MA\&/jnU#<mWC=)760l/i6K1?f*?XmYBa21ujmHZW:f6U*C=LNje'&^4_5Y5DS2ORQ^ol*s52mB!^QjkNO6/cHLYmI9QC\>Z;"GV7+A)nUug"f&Ir#?>V;%ii&]ol[QX]ar"6J=G+JX[KR!SmK&tO^*sO+Uh_rCnlj&<br:H$h]SqHK?+g.q1\mk5\$K*1EdPc(7Hrk$H6MK),"4mP\ijqDi_55&FJI8Qn9ITUi&QOEuU(%g;Ke?k0.'bf&Fmpf%LB<jgjQqc(lbr^eQeT6j^bak?5YUh5l!BiqaMLRNq>s.FK@a<&74'#uqBSVG8kZHX'6$B%fZ8JF_qL`N36n?:")'buZDWb]haF@Joo_]71$-(mEbt8!d)e8u'=/[)4;;ZSlQ2Pd\O%",5'iQsP?KH@T"$>Fd)'`qd!Tpu@'l_#Ln=gT02r._jdi5H\i`\8>@DFnDu&?IG"f<R]*dUR.8<@[?YgRc`6C]e#lo-J:erPSNm.^\fMNiT,GBS,"Gdo0^H*LC(>U+1f<$eKmmt`K?+Q/gbAY67orM_'HFAEk9q*.]B:Q2=$5B"^T[3:o0_BUZ)id['O`t*8;mef.#"G.*f.f><3!=m2W&kk<Q8)pLYr_#HX#!*'#!40a^<t38NP\R!RR;5hBuA:'#0E/ETF$]2O7*-p+32_'nh>agSeZDhfZs#Ep1F345UAT713/0'p0@`lmdaAG#HPI_SqNe'#A_EL+[bm-+*Tklnab-2,Qo~>endstream
endobj
18 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1099
>>
stream
Gb"/*>u03/'RfGR\8O&HJpmT_]N3b+-8b6)n[+V_.4P[5KQR+gZ]gK(PpC47)QU8ZCUeBLPMC4sI'3t_U46kHH2@k$%H6@U"c<l:J9$]<GQseXan(0d4Tk29GeP9pq?1X_L&LSCmdN`]4inENJMq*6P8'%2GmRi'W.Y:AaC:MX9eP]eFZ2H3X`<Qe+#O6*8dYu?&0,#sl!5(^bs+Mh!9'&kFA[EW"%rfH+"UArka!`k1\uU;?2eAdcHJcoV`DENd;7A?<-XWV'aRW6e9K`C(&,K9ol'94"BJj$mK;!NQm9=`H>E;^p#/h3,m6sCWGXe;;pmN]=)<l4hd.9XI6Ys'n.#reFn"G"\V9+VIQoZloh]>b)A(!J;*.[C?P9lKJ<_JP`^6+HjhZGlUSR6hg</(05E9p^YOJ%T+5tLp_uA,9U-F*D02Q2;F>urb4fT5RF(&N@R`STX+-gFs;76PNQmL/(d;2OGdBTG!H"JJu/'=T6rfj"%9D-4N8]j8(k4`SSR,e#?GnKf?:R:nlb*Fa%9)d3IT;IhU)tQk0pZU570&]&p<o@B@[fQf$_8nTjU&n\^$G8#An@DP*<(Jk[BON`7D.FUoUM4KgQ`Mm0[Nh^V1"N\sqC.9LI$K55[QLd='en-L!<X4mR$a*n:G+H0D-fT0gI!`^8D_`]#JG?n=@+Z@c%h+Mf+-2$8NZ-9d)s^n\`o-Dmd0%WV\,c>6p!e>jWK5J1+,[Ab.SQ:,BemK&>Kg`F/!cNge1KK^r]]:XQSH.pLC]a^m$FOWidnWK6\CL[T&f&=^W'1KpG]/5$A_@oWjWd`0RFP#8:?40,s/0^*WH9O/?ZP?5Cp=!0hT$Ugq"2*Tq\V#<.PdM%>?i%n,:b$+TV;LEt6nMqF3K](BKVkTsRp!E.g<m']YRE5P[I^3NFK]j&raIC3,/=_lhC%Z[*feh,htU,ZAS3:!>!X#gd?3+Z+MPcYiOa--1om?h$Q3S*?7Bp?HMm8J<MBDDo+$At-cO*(7&ga^C:YC\U*,hs#0]);\*PYRHs./fl@T]G=2FSc63./-u2!JmCGj-D(Fo-B1a31I,@^B>r:M&C2Sm:+(A`XJ74"heu=!W~>endstream
endobj
19 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 484
>>
stream
Gb"/%gJ.f)&:O"KbbL=tAu><(N(T$^BRdfG&;cK69`0=nm]s&@&+C^28G4g.1RF[kF4qqUFA[=u/c^V<#'U3g&>qcsC5@pi)oYEG*GM<Lc34:kKChA&PUE[pf/'\Ra-+bqfbRPIcTo3B`j0j%cdD%b9RC6=gq*`hiL&m^;LNH5^LB&eKE33T'OF.s6gWo3a/.32S++eWK].a'gQOEc;Kg&=\:VuXr>g!@#IE_!&#F\%G\A&Abi9j0o=F_G]o['gX&*1-$SYoMNt6K<BiP_Q&DW">c6%=b<EusAa@lc][K2ZU!bU2;g8Y3d>D)<3CddO#m=m$I(pbX@Ls/;P(K(/r;$Lu?2elT7-UVSt"OaU)-<]`K<qR+"9tlU2P+Ak77:,6;naa;;q4+PDQa;=eg[5o9]!5`V=3!;bA5Ci43:/Z&Fs`?7G/#nYi?P`+G>-;YP$\?3W4gQ%UqK*;+>KM^Q[!5JZaUa,DeX0S&H~>endstream
endobj
20 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1099
>>
stream
Gb"/*>u03/'RfGR\8O&HJpmS4^K0(.-8b6)m:e3S;H/m8&^p(8cV@)qY,#DuNRq@@2;C1a8U\m`mu=#+bRZ:4mj8%P#gPV2TErpN-kA:*([s-t4:gYWVhPaS&;E5&-^ZkBj4BntLYq538?Rcq-Bp_'1,p$B+[Qp:&eYa!:LJE&\>mk3h9;P!`aM?.(^p\7PShak3514sao1u:7#qnM=?osL>;W";%[V`5`J&$:$9=m](>AEl-KrObj@NCf9+d4SSNoV/R8R4=P+otN*j,A4>Alb#fh5`'opIut]*s$3$uKnbJ$_4`MW]Z.)'CeR-Z2Pf`o`.q0_kRb+Z#aG4W<.o0t\!Fg((TRHo96jAnhD^Ao]I/A2#_O.I8]:grt:K+YjiW9pb/'?ft9aos=$:fJS-uh3brthTYm0iTG.Pr#)83mU!L]ML:kp[L$Y;Fb45^-8qWeD#&F]1qSb=2/'uLEALCs%;A</EjNsE;XA%da2Rg&48Y2/Khn.<J%U^^Z8f84Mt-Bq=i*C+nP:RRClYggpd^1tRX]q&7DDlF!=,?>!XIbZ'0*MO4l]Z+$@L59pbGq/Ff/fRO;TCl12,E'a>fJOgX\YC=W+S`LT(R,f6>j@V_l\VFUS@Xq8-V(X>C?"g+<^8c.UhaP>W1;7:s!G)b>*q8GWt@)g^_@(Y@@QHH@&<[&;Lsd7[&$kbTeU+`9e'^YJA&Sc=)JdtlFWU(a7P`FpcJe;Mb5o"[]9L`<'>`Fq]^]dUQB%oSW139f`UlXb#IHs9kl@*4bHWkL$gLNsgPY#Ls>=^W?9Ks"CGhH:W`oQ$+$`0%(KHP9X[0,Nl,Td05opSM\s?4P@U"%q9GdP79k8r=gDUYBLO-:b,c&&cg($2F.&O!N*!Mn"r+qXe9IkTsRp!Rf>[m']JM[bHJS\$,%q2f!_S=fk\Ielm9TK4APrF[nDl;4K49S1UKqX#mG^EQVFD;mpG2*%Tn#[UWl7ARCoSLN*eoG>HmtOr%o8.k&!74jrd8n97/.q^tDa9#BBqV;Era7Hm6DM)$Vt"\SJ@9#"`KMt4YPWBo`;NB:HSO4,p7P'jp^rt:uH`&%>Vg3^h*H3OfW!.i^^Sc~>endstream
endobj
21 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 480
>>
stream
Gb"/%9htgF&A@P9Qm7bj3n*OJ7O?%^g-8sZLdXcV-N8ETh.:$64hsA78.%H>bsu-1H*\X7ZnO^OQ2mZc_cm6hLbh:J1rKX/M8d'A.aeU'S-oP6%)Vo";dLWRF#,JXq(h-^6+-s9nI4nZ)6<,oSte5CR.ZA/Epjg_RU`A,9_GA^^IL+]&r%M`fdhjdLje-&Ot1CU;7>kO1*!QEo5I/*A7p:D*c1Qlm%FYHLg2<P..'+46ecQl*r"p2W/<gr'nQjAAdY>A$-$c[nIA>0XV-d+7.*B6kI3R<AK9GC,f0tqX[/d4K5>=">EqL_("g1;>*R=eDMU'+O[Z6&U:c<Q?]$UhZas2tH?U3"7mR]PS2SK(kI%=mG[U;V]G'@b9e?(:8&rgAqY=X4H!ANtQ+*5"$G/#ION*G2#3o_pQl)(Mc)I7jW.[<Mc$f?mXo9B*F/e4*6+"%doGqYL:qi%^^VrimrPeJ^iX9_/P7[~>endstream
endobj
22 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 981
>>
stream
Gb"/i>u03/'Sc)R/'].DM-qET`OFY>S'Wuo[&`#9/nIIg(a7`7rp1o+@3nVeTuL%I["]6lIM4qpm2h#a0gE$iT.Bu^&Y+9B5S9_>:`aS30&V.7\f]>"VM5XR&;E5&-^ZkBi`@3rKtlab8?Rcq-C!>F;E,Eb=+i%g,pXU"QGE9+FBP5MWgQ=jXi%NY0H(KN.5[\tE;XU^LssJLj:+Md&=lopQc4`]A18O:P(ec$;fWUn7UK3OeIO"je@oKcR_qK6:T(<]76m8=>(Dd1f%Y#C\GqPLIrb90Yk%Wa*N1U.*XP2<gW6#T/t_,`+`IWB1hE_*9%r"]MXAdcdu-S*mY<qDrIe[*D#CXXDCG<s?FQu<%8`G;[3C^K_K;TXJ'?kU-*.N;-P<Ya;BGc)4A*bB[]G'trQ!<JrX6pK6g"9L]DmgeD"b(2)+Cu-]59m'<o(QmZ3d<JZY_C[%=]H6Zg0U*!]/trRsE5$_i,)eAm[?Pf^CeXo@[Dqp*hj,TD?.tk@&Ot`cHJhTA;0TQYdq.-%A7l@WSVS%InoF;5/F/-eNM8254kI&=!ni(_<1a_5/2T8!baqF][Lj>_1Ll2Ru.h8>&DuQ?A://:t=3K3Y]+BZh/i&Rq]LlhfL#:@i<2"'q>!G\%_lT4&ie]tpM/"`g0XApB*)\j%K.%?bAD6OR4*CIdkleU!$r]FG3#-/HA;^rkYj*j8Y+QNf);:u;J/Mr78&[*=`E!PY27%g`(O0@nbYnC+hdl_X,LNpPHoC&<GMd_Gu)B\FL\s!2l]^sVgq5#73M-ZIYh4n02q*s+)O"Q\T,"DV$/^srQ_FjE0(n"4MtcfQ1sVED)!bldE7#KPCXn=F`D+jSXNB:BHFFet,rX/tlqZB9Xu=E8?'(@+IJ<2t'\B5f]<9.*;sDdiS-bju=uYC-o#prrdUlu@unELp^IoVS++lInTBI<R&%q[TmG_Fno>nC$L$HME);]Numj0_u#;V"F<~>endstream
endobj
23 0 obj
<<
/Count 0 /Type /Outlines
>>
endobj
xref
0 24
0000000000 65535 f
0000000073 00000 n
0000000114 00000 n
0000000221 00000 n
0000000333 00000 n
0000000538 00000 n
0000000743 00000 n
0000000948 00000 n
0000001153 00000 n
0000001358 00000 n
0000001563 00000 n
0000001769 00000 n
0000001975 00000 n
0000002062 00000 n
0000002346 00000 n
0000002450 00000 n
0000004437 00000 n
0000005483 00000 n
0000006772 00000 n
0000007963 00000 n
0000008538 00000 n
0000009729 00000 n
0000010300 00000 n
0000011372 00000 n
trailer
<<
/ID
[<daed391e39bf0175680b4a859bbc42f7><daed391e39bf0175680b4a859bbc42f7>]
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
/Info 13 0 R
/Root 12 0 R
/Size 24
>>
startxref
11419
%%EOF

5411
reportlab-userguide.pdf Normal file

File diff suppressed because one or more lines are too long

BIN
static/images/print.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

View File

@ -59,3 +59,15 @@ function save_turn() {
return false;
}
}
function update_sheet(turn, slot) {
document.location = '/staffsheet/update/'+turn+'/'+slot;
return true;
}
function clear_sheet(turn, slot) {
if (confirm("Voulez-vous vraiment libérer ce créneau ? \n\n Confirmer ?")) {
document.location = '/staffsheet/clear/'+turn+'/'+slot;
}
return false;
}

View File

@ -247,3 +247,10 @@ function upload_file_from_ajax(obj, url, err_code) {
}
xhttp.send(formData);
}
/* **************************************************************************************
* Printing
* **************************************************************************************/
function print_page() {
document.location = '/staffsheet/pdf';
}

View File

@ -24,4 +24,5 @@
--search_icon: url(/static/images/search.png);
--trash_icon: url(/static/images/trash.png);
--upload_icon: url(/static/images/upload.png);
--print_icon: url(/static/images/print.png);
}

View File

@ -3,3 +3,65 @@ main > article > p.note {
display: block;
font-size: 12px;
}
article > div.table_header > div.sheet_day {
background-color: var(--coloured-bg);
width: 100%;
font-size: 18px;
color: var(--white);
text-align: left;
padding: 5px 5px 5px 5px;
}
article > div.table_header > div.sheet_role {
background-color: var(--light-coloured-bg);
width: 100%;
text-align: left;
padding: 5px 5px 5px 10px;
border-color: var(--coloured-bg);
border-style: none none solid none;
border-width: 1px;
}
article > div.table_row > border_right {
border-right-style: solid;
border-right-color: var(--coloured-bg);
border-right-width: 1px;
}
article > div.table_row > div.sheet_time,
article > div.table_row > div.sheet_user1,
article > div.table_row > div.sheet_user2,
article > div.table_row > div.sheet_user3,
article > div.table_row > div.sheet_user4 {
width: 125px;
text-align: left;
padding: 5px 5px 5px 10px;
}
article > div.table_row > div.sheet_user1:hover,
article > div.table_row > div.sheet_user2:hover,
article > div.table_row > div.sheet_user3:hover,
article > div.table_row > div.sheet_user4:hover {
cursor: pointer;
}
article > div.table_row > div.sheet_user1 {
text-align: center;
width: 780px;
}
article > div.table_row > div.sheet_user2 {
text-align: center;
width: 388px;
}
article > div.table_row > div.sheet_user3 {
text-align: center;
width: 257px;
}
article > div.table_row > div.sheet_user4 {
text-align: center;
width: 192px;
}

View File

@ -6,6 +6,11 @@
* HTML header section of the index.html template file.
*/
@page {
size: 21cm 29.7cm;
margin: 30mm 45mm 30mm 45mm;
}
* {
box-sizing: border-box;
}
@ -23,12 +28,12 @@ div.content {
main > article {
flex: 1;
background-color: var(--clear-bg);
background-color: var(--clear-bg);
}
main > section.inline {
display: flex;
background-color: var(--clear-bg);
background-color: var(--clear-bg);
}
main > section.inline > article.left {
@ -169,11 +174,13 @@ main > article > img {
border-radius: 4px;
}
main > article > p > a {
main > article > p > a,
main > article > ul > li > a {
color: var(--coloured-bg);
}
main > article > p > a:hover {
main > article > p > a:hover,
main > article > ul > li > a:hover {
text-decoration: none;
}
@ -226,6 +233,9 @@ footer {
input[type='text'],
input[type='password'],
input[type='number'],
input[type='email'],
input[type='url'],
textarea,
select,
pre {
@ -298,7 +308,8 @@ input.refresh,
input.save,
input.search,
input.trash,
input.upload {
input.upload,
input.print {
width: 20px;
height: 20px;
margin: 0;
@ -315,7 +326,8 @@ input.refresh:hover,
input.save:hover,
input.search:hover,
input.trash:hover,
input.upload:hover {
input.upload:hover,
input.print:hover {
border-color: var(--text-color);
border-style: solid;
border-width: 1px;
@ -369,6 +381,12 @@ input.upload {
background-position: center center;
}
input.print {
background: var(--print_icon);
background-repeat: no-repeat;
background-position: center center;
}
form {
width: 450px;
text-align: center;
@ -379,7 +397,12 @@ form > label {
float: left;
}
form > input[type='text'], form > input[type='password'], form > select {
form > input[type='text'],
form > input[type='password'],
form > input[type='number'],
form > input[type='email'],
form > input[type='url'],
form > select {
float: right;
width: 200px;
}

View File

@ -0,0 +1,534 @@
/*
* Do NOT modify this file:
* ------------------------
* If you want to add or modify classes, create a new
* CSS files and make it loaded after this one in the
* HTML header section of the index.html template file.
*/
@page {
size: 21cm 29.7cm;
margin: 3mm 5mm 3mm 5mm;
}
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 400;
src: url(http://participer.redatomik.org/static/fonts/RobotoCondensed-Regular.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto Condensed';
font-style: normal;
font-weight: 700;
src: url(http://participer.redatomik.org/static/fonts/RobotoCondensed-Bold.ttf) format('truetype');
}
* {
box-sizing: border-box;
}
body {
margin: 10px;
font-family: 'Roboto Condensed';
background-color: #2B2B2B;
}
div.content {
display: flex;
min-height: calc(100vh - 110px);
}
main > article {
flex: 1;
background-color: #E5E5E5;
}
main > section.inline {
display: flex;
background-color: #E5E5E5;
}
main > section.inline > article.left {
flex: 0 0 50%;
margin-left: 10px;
}
main > section.inline > article.right {
flex: 1;
margin-left: 10px;
}
div.content > nav.vertical {
flex: 0 0 200px;
background-color: #E5E5E5;
border-right-color: #BBBBBB;
border-right-style: solid;
border-right-width: 1px;
}
div.content > nav.vertical {
order: -1;
display: block;
}
div.content > nav.vertical > a {
display: block;
background-color: #E5E5E5;
font-size: 20px;
color: #555555;
padding: 5px;
text-decoration: none;
}
div.content > nav.vertical > a:hover {
background-color: #FF5D00;
color: #FFFFFF;
cursor: pointer;
}
div.content > nav.vertical > a.selected {
background-color: #FFB387;
}
main {
color: #555555;
background-color: #E5E5E5;
width: 100%;
}
main > div.navbar_container {
text-align: center;
padding: 0;
margin: 0;
}
main > div.navbar_container > ul.horizontal {
display: inline-block;
list-style-type: none;
margin: 10px;
padding: 0;
overflow: hidden;
background-color: #FFFFFF;
border-color: #FF5D00;
border-style: solid;
border-width: 1px;
color: #555555;
border-radius: 2px;
}
main > div.navbar_container > ul.horizontal > li {
float: left;
}
main > div.navbar_container > ul.horizontal > li > a {
display: block;
color: #555555;
text-align: center;
padding: 5px;
text-decoration: none;
}
main > div.navbar_container > ul.horizontal > li > a:hover {
background-color: #FF5D00;
color: #FFFFFF;
}
main > div.navbar_container > ul.horizontal > li > a.right_border {
border-right-color: #FF5D00;
border-right-style: solid;
border-right-width: 1px;
}
main > div.navbar_container > ul.horizontal > li > a.selected {
background-color: #FFB387;
}
main > article {
padding: 10px;
color: #555555;
display: block;
}
main > article.error,
main > article.error > p {
padding: 10px;
color: #555555;
display: block;
text-align: center;
}
main > article > h3,
main > section.inline > article > h3 {
font-size: 30px;
color: #555555;
margin-bottom: 10px;
}
main > article > p,
main > article > ul,
main > article > ol {
color: #555555;
text-align: justify;
text-justify: distribute;
}
main > hr {
border-color: #BBBBBB;
border-style: solid;
border-width: 1px;
}
main > article > img {
display:inline-block;
border-color: #BBBBBB;
border-style: solid;
border-width: 1px;
border-radius: 4px;
}
main > article > p > a,
main > article > ul > li > a {
color: #FF5D00;
}
main > article > p > a:hover,
main > article > ul > li > a:hover {
text-decoration: none;
}
main > article.right > img {
float: right;
margin: 0 0 0px 10px;
}
main > article.left > img {
float: left;
margin: 0 10px 0px 0;
}
header {
height: 65px;
font-size: 34px;
padding: 10px;
text-align: right;
color: #FFFFFF;
background: url(http://participer.redatomik.org/static/images/logo.png);;
background-repeat: no-repeat;
background-position: 10px;
text-shadow: 0 0 1px #000000;
border-bottom-color: #888888;
border-bottom-style: solid;
border-bottom-width: 1px;
border-top-color: #FFFFFF;
border-top-style: solid;
border-top-width: 1px;
}
footer {
height: 35px;
font-size: 12px;
text-align: center;
padding: 1em;
border-bottom-color: #FFFFFF;
border-bottom-style: solid;
border-bottom-width: 1px;
border-top-color: #888888;
border-top-style: solid;
border-top-width: 1px;
}
header,
footer {
background-color: #FF5D00;
color: #FFFFFF;
}
input[type='text'],
input[type='password'],
input[type='number'],
input[type='email'],
input[type='url'],
textarea,
select,
pre {
border-color: #888888;
border-style: solid;
border-width: 1px;
background-color: #FFFFFF;
color: #555555;
padding: 5px;
font-family: 'Roboto Condensed';
margin: 5px;
}
pre {
border-color: #FF5D00;
}
button,
input[type='button'],
input[type='submit'] {
border-color: #888888;
border-style: solid;
border-width: 1px;
background-color: #FF5D00;
color: #FFFFFF;
font-weight: bold;
padding: 5px;
font-family: 'Roboto Condensed';
margin: 5px;
border-radius: 4px;
}
button:hover,
input[type='button']:hover,
input[type='submit']:hover,
input[type='file']:hover {
background-color: #FFB387;
color: #555555;
cursor: pointer;
}
div.file_upload {
display: inline-block;
position: relative;
width: 20px;
height: 20px;
margin: 0;
padding: 0;
border-radius: 2px;
border-style: solid;
border-width: 1px;
border-color: #E5E5E5;
}
input[type='file'] {
position: absolute;
width: 18px;
height: 18px;
left: 0;
top: 1px;
opacity: 0;
}
input.add,
input.edit,
input.login,
input.logout,
input.refresh,
input.save,
input.search,
input.trash,
input.upload,
input.print {
width: 20px;
height: 20px;
margin: 0;
padding: 0;
border-radius: 2px;
border-style: none;
}
input.add:hover,
input.edit:hover,
input.login:hover,
input.logout:hover,
input.refresh:hover,
input.save:hover,
input.search:hover,
input.trash:hover,
input.upload:hover,
input.print:hover {
border-color: #555555;
border-style: solid;
border-width: 1px;
background-color: #FF5D00;
cursor: pointer;
}
input.add {
background: url(http://participer.redatomik.org/static/images/add.png);
background-repeat: no-repeat;
background-position: center center;
}
input.edit {
background: url(http://participer.redatomik.org/static/images/edit.png);
background-repeat: no-repeat;
background-position: center center;
}
input.login {
background: url(http://participer.redatomik.org/static/images/login.png);
background-repeat: no-repeat;
background-position: center center;
}
input.logout {
background: url(http://participer.redatomik.org/static/images/logout.png);
background-repeat: no-repeat;
background-position: center center;
}
input.refresh {
background: url(http://participer.redatomik.org/static/images/refresh.png);
background-repeat: no-repeat;
background-position: center center;
}
input.save {
background: url(http://participer.redatomik.org/static/images/save.png);
background-repeat: no-repeat;
background-position: center center;
}
input.search {
background: url(http://participer.redatomik.org/static/images/search.png);
background-repeat: no-repeat;
background-position: center center;
}
input.trash {
background: url(http://participer.redatomik.org/static/images/trash.png);
background-repeat: no-repeat;
background-position: center center;
}
input.upload {
background: url(http://participer.redatomik.org/static/images/upload.png);
background-repeat: no-repeat;
background-position: center center;
}
input.print {
background: url(http://participer.redatomik.org/static/images/print.png);
background-repeat: no-repeat;
background-position: center center;
}
form {
width: 450px;
text-align: center;
line-height: 40px;
}
form > label {
float: left;
}
form > input[type='text'],
form > input[type='password'],
form > input[type='number'],
form > input[type='email'],
form > input[type='url'],
form > select {
float: right;
width: 200px;
}
div.table_header {
background-color: #FF5D00;
text-align: left;
width: 910px;
}
div.table_row {
text-align: left;
width: 910px;
}
div.table_header > div, div.table_row > div {
display: inline-block;
text-align: center;
font-weight: bold;
width: 200px;
}
div.table_row > div {
border-color: #FF5D00;
border-width: 1px;
font-weight: normal;
}
div.even {
background-color: #FFB387;
border-color: #FF5D00;
border-width: 1px;
border-style: none none solid none;
}
div.odd {
background-color: #FFFFFF;
border-color: #FF5D00;
border-width: 1px;
border-style: none none solid none;
}
div.table_row > div.border_left {
border-style: none none none solid;
}
div.table_row > div.border_right {
border-style: none solid none none;
}
main > article > p.note {
color: #555555;
display: block;
font-size: 12px;
}
article > div.table_header > div.sheet_day {
background-color: #FF5D00;
width: 100%;
font-size: 18px;
color: #FFFFFF;
text-align: left;
padding: 5px 5px 5px 5px;
}
article > div.table_header > div.sheet_role {
background-color: #FFB387;
width: 100%;
text-align: left;
padding: 5px 5px 5px 10px;
border-color: #FF5D00;
border-style: none none solid none;
border-width: 1px;
}
article > div.table_row > border_right {
border-right-style: solid;
border-right-color: #FF5D00;
border-right-width: 1px;
}
article > div.table_row > div.sheet_time,
article > div.table_row > div.sheet_user1,
article > div.table_row > div.sheet_user2,
article > div.table_row > div.sheet_user3,
article > div.table_row > div.sheet_user4 {
width: 125px;
text-align: left;
padding: 5px 5px 5px 10px;
}
article > div.table_row > div.sheet_user1:hover,
article > div.table_row > div.sheet_user2:hover,
article > div.table_row > div.sheet_user3:hover,
article > div.table_row > div.sheet_user4:hover {
cursor: pointer;
}
article > div.table_row > div.sheet_user1 {
text-align: center;
width: 780px;
}
article > div.table_row > div.sheet_user2 {
text-align: center;
width: 388px;
}
article > div.table_row > div.sheet_user3 {
text-align: center;
width: 257px;
}
article > div.table_row > div.sheet_user4 {
text-align: center;
width: 192px;
}

View File

@ -9,16 +9,17 @@
</p>
<p>
Notez que:
<ul>
<li>Votre adresse mail doit être valide et consultée régulièrement si vous ne voulez pas manquez des informations importantes telels que les dates de réunions de staff</li>
<li>Votre numéro de téléphone nous permettra de vous contacter pendant l'évènement</li>
<li>Si vous avez un régime alimentaire particulier (intolérences, veganisme, religieux), merci de le préciser dans le champs prévu à cet effet</li>
<li>Aucune des données que vous nous transmettrez ne sera fournie à un tiers</li>
</ul>
</p>
<ul>
<li>Votre adresse mail doit être <strong>valide et consultée</strong> régulièrement si vous ne voulez pas manquez des informations importantes telels que les dates de réunions de staff</li>
<li>Un nom ou psedonyme est nécessaire pour pouvoir s'enregistrer sur <a href='/staffsheet'>la feuille de staff</a></li>
<li>Votre numéro de téléphone nous permettra de vous contacter pendant l'évènement</li>
<li>Si vous avez un régime alimentaire particulier (intolérences, veganisme, religieux), merci de le préciser dans le champs prévu à cet effet</li>
<li>Hormis votre pseudonyme aucune des données que vous nous transmettrez ne sera exposée au public ou fournie à un tiers</li>
</ul>
<form method='POST' action='/account/update'>
<label>Adresse email: </label><input id='login' name='login' type='text' value='{{ mail }}' disabled='disabled'/><br/>
<label>Prénom ou pseudo: </label><input id='name' name='name' type='text' value='{{ name }}'/><br/>
<label>Pseudonyme: </label><input id='name' name='name' type='text' value='{{ name }}' required maxlength=20/><br/>
<label>Nouveau mot de passe: </label><input id='password' name='password' type='password'/><br/>
<label>Confirmation mot de passe: </label><input id='confirm' name='confirm' type='password'/><br/>
<label>Numéro de téléphone: </label><input id='phone' name='phone' type='text' value='{{ phone }}'/><br/>

View File

@ -4,7 +4,7 @@
<article>
<h3>Informations du compte</h3>
<form method='POST' action='/account/update/{{ user.id }}'>
<label>Adresse email: </label><input id='login' name='login' type='text' value='{{ user.mail }}' disabled='disabled'/><br/>
<label>Adresse email: </label><input id='login' name='login' type='text' value='{{ user.mail }}' disabled='disabled' required/><br/>
<label>Prénom ou pseudo: </label><input id='name' name='name' type='text' value='{{ user.name }}'/><br/>
<label>Nouveau mot de passe: </label><input id='password' name='password' type='password'/><br/>
<label>Confirmation mot de passe: </label><input id='confirm' name='confirm' type='password'/><br/>

19
templates/confirm.html Normal file
View File

@ -0,0 +1,19 @@
{% extends "index.html" %}
{% block title %}Login{% endblock %}
{% block nav %}{% endblock %}
{% block main %}
<article class='login'>
<h3>Confirmation de votre inscription</h3>
<p>Merci de confirmer l'adresse de courriel et le mot de passe avec lesquels vous vous êtes enregistré.</p>
</article>
<article class='left'>
<form method='POST' action='/confirm/link/{{ link_id }}'>
<label>Adresse mail: </label><input id='login' name='login' type='text' /><br/>
<label>Mot de passe: </label><input id='password' name='password' type='password' /><br/>
<input type='submit' value='Log me in'>
</form>
<p class='note'>
Mot de passe oublié ? Envoyez une <a href='https://bofh.tetalab.org/?do=newtask&project=2'>demande de réinitialisation de votre mot de passe</a>.
</p>
</article>
{% endblock %}

View File

@ -32,7 +32,7 @@
{% endblock %}
</nav>
{% endblock%}
<main>
<main id='main'>
{% if navbar %}
<div class='navbar_container'>
<ul class='horizontal'>
@ -62,19 +62,10 @@
est précieuse.
</p>
<p>
Ce site vous permettra:
<ul>
<li>
de vous enregistrer afin de faire partie du "Staff", cette équipe de super-héros sans qui le THSF ne
saurait être un moment agréable, sûr et convivial.
</li>
<li>
de sélectionner le poste et les créneaux horaires pendant lesquels vous souhaitez vous rendre disponible.
</li>
<li>
de poser vos questions, obtenir des réponses et être tenu informé des évolutions de l'organisation et du déroulement du THSF via la mailing list du staff.
</li>
</ul>
Une fois vos <a href='/account'>informations personnelles</a> duement renseignées vous pourrez <a href='/staffsheet'>sélectionner les postes et les créneaux horaires</a> pendant lesquels vous souhaitez vous rendre disponible.
</p>
<p>
Le <a href='https://www.tetalab.org'>Tetal@b</a> et l'ensemble de l'équipe d'organisation du THSF vous remercie pour votre aide.
</p>
</article>
{% endblock %}

View File

@ -1,29 +1,34 @@
{% extends "index.html" %}
{% block title %}Liste des tours de staff{% endblock %}
{% block main %}
{{ A }}
<article>
<h3>Liste des tours de staff enregistrés <input class='add' value='' title='Ajouter un tour de staff' onclick='javascript:document.location="/turn/new"'/></h3>
<div class='table_header'>
<div class='border_right' style='width: 40px;'>ID</div>
<div class='border_right'>Role</div>
<div class='border_right'>Jour</div>
<div class='border_right'>Début</div>
<div class='border_right'>Fin</div>
<div style='width: 50px;'>Action</div>
<div class='border_right' style='width: 240px;'>Role</div>
<div class='border_right' style='width: 150px;'>Jour</div>
<div class='border_right' style='width: 150px;'>Début</div>
<div class='border_right' style='width: 150px;'>Fin</div>
<div class='border_right' style='width: 100px;'>Slots</div>
<div style='width: 50px;' style='width: 50px;'>Action</div>
</div>
{% set row_class = cycler('odd', 'even') %}
{% for turn in turns %}
{% for day in turns %}
{% for turn in day[1] %}
<div class='table_row {{ row_class.next() }}'>
<div class='border_right' style='width: 40px;'>{{ turn[0].id }}</div>
<div class='border_right'>{{ turn[1] }}</div>
<div class='border_right'>{{ turn[0].wday }}</div>
<div class='border_right'>{{ turn[0].start_time }}</div>
<div class='border_right'>{{ turn[0].end_time }}</div>
<div class='border_right' style='width: 240px;'>{{ turn[1] }}</div>
<div class='border_right' style='width: 150px;'>{{ day[0] }}</div>
<div class='border_right' style='width: 150px;'>{{ turn[0].start_time }}</div>
<div class='border_right' style='width: 150px;'>{{ turn[0].end_time }}</div>
<div class='border_right' style='width: 100px;'>{{ turn[0].num_slot }}</div>
<div style='width: 50px;'>
<input class='edit' value='' onclick='javascript:document.location="/turn/{{ turn[0].id }}"' title='Éditer'/>
<input class='trash' value='' onclick='javascript:delete_turn({{ turn[0].id }});' title='Supprimer'/>
</div>
</div>
{% endfor %}
{% endfor %}
</article>
{% endblock %}

View File

@ -14,25 +14,25 @@
</p>
<p>
Ce site vous permettra:
<ul>
<li>
de vous enregistrer afin de faire partie du "Staff", cette équipe de super-héros sans qui le THSF ne
saurait être un moment agréable, sûr et convivial.
</li>
<li>
de sélectionner le poste et les créneaux horaires pendant lesquels vous souhaitez vous rendre disponible.
</li>
<li>
d'être tenu informé des évolutions de l'organisation et du déroulement du THSF via la mailing list du staff.
</li>
</ul>
</p>
<ul>
<li>
de vous enregistrer afin de faire partie du "Staff", cette équipe de super-héros sans qui le THSF ne
saurait être un moment agréable, sûr et convivial.
</li>
<li>
de sélectionner le poste et les créneaux horaires pendant lesquels vous souhaitez vous rendre disponible.
</li>
<li>
d'être tenu informé des évolutions de l'organisation et du déroulement du THSF via la mailing list du staff.
</li>
</ul>
<p>
Nul besoin de compétences particulières pour rejoindre notre équipe sinon votre meilleure volonté et votre bonne humeur
qui feront du THSF un moment unique de partage.
</p>
<p>
Pour vous enregistrer, munissez vous de votre adresse email et renseignez les champs de la section "<strong>Inscription</strong>".
Pour vous enregistrer, munissez vous de <strong>votre plus belle adresse email</strong> et renseignez les champs de la section "<strong>Inscription</strong>".
</p>
<p>
Si vous vous être préalablement enregistré, renseignez uniquement les champs de la section "<strong>Connexion</strong>"
@ -48,7 +48,7 @@
<form method='POST' action='/login'>
<label>Adresse mail: </label><input id='login' name='login' type='text' /><br/>
<label>Mot de passe: </label><input id='password' name='password' type='password' /><br/>
<input type='submit' value='Log me in' onclick='javascript:return verify_login();'>
<input type='submit' value='Log me in'>
</form>
<p class='note'>
Mot de passe oublié ? Envoyez une <a href='https://bofh.tetalab.org/?do=newtask&project=2'>demande de réinitialisation de votre mot de passe</a>.
@ -59,12 +59,13 @@
<h3>Inscription</h3>
</article>
<article class='left'>
<form method='POST' action='/register'>
<label>Adresse mail: </label><input id='reg_mail' name='login' type='text' /><br/>
<label>Mot de passe (8 char min.): </label><input id='reg_password' name='password' type='password' /><br/>
<label>Confirmation: </label><input id='reg_confirm' name='confirm' type='password' /><br/>
<input type='submit' value='Register me NOW !' onclick='javascript:return register();'>
<form method='POST' action='/register' onsubmit='return register();'>
<label>Adresse mail: </label><input id='reg_mail' name='login' type='text' required /><br/>
<label>Mot de passe (8 char min.): </label><input id='reg_password' name='password' type='password' required /><br/>
<label>Confirmation: </label><input id='reg_confirm' name='confirm' type='password' required /><br/>
<input type='submit' value='Register me NOW !'>
</form>
<p class='note'>En vous inscrivant vous déclarez être prèt à découvrir l'insondabilité de l'improbable</p>
</article>
<hr/>
{% endblock %}

View File

@ -11,11 +11,12 @@
</select><br/>
<label>Jour de la semaine: </label><select id='day' name='day'>
{% for day in days %}
<option value='{{ day }}'>{{ day }}</option>
<option value='{{ day[1] }}'>{{ day[0] }}</option>
{% endfor %}
</select><br/>
<label>Début (HH:MM:SS) </label><input id='start' name='start' type='text' maxlength=8/><br/>
<label>Fin: (HH:MM:SS) </label><input id='end' name='end' type='text' maxlength=8/><br/>
<label>Nombre de slots (max. 4) </label><input id='num_slot' name='num_slot' type='number' min='1' max='4'/><br/>
<input type='submit' value='Enregistrer' onclick='javascript:return save_turn();'/>
</form>
</article>

84
templates/staffsheet.html Normal file
View File

@ -0,0 +1,84 @@
{% extends "index.html" %}
{% block title %}Feuille de staff{% endblock %}
{% block main %}
<article id='roles'>
<h3>Fiches de poste<input class='print' value='' title='Imprimer' onclick='javascript:print_page();'/></h3>
<p>Les postes de référents (référent staff, référent bar, référent run) sont réservés à des personnes ayant une bonne connaissance du lieu et de l'évènement.</p>
{% for role in roles %}
<div class='table_header'><div class='sheet_day'>{{ role.role }}</div></div>
<div class='table_row'>
<ul>
{% set desc = role.description.split('|') %}
{% for point in desc %}
<li>{{ point }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</article>
<hr/>
<article id='staff_sheet'>
<h3>Feuille de staff</h3>
<ul>
<li><strong>Ménage le soir même pour tous les derniers créneaux du jour</strong></li>
<li><strong>Tâches dévolues à tous:</strong>
<ul>
<li>Veiller à la sécurité générale du lieu</li>
<li>Ramassage bouteilles ou objets en verre</li>
<li>Séparation des bagarres (rarissime)</li>
<li>Sécurisation des personnes en difficulté (ou trop alcoolisées), etc...</li>
<li>Sourire et bonne humeur quel que soit le niveau de fatigue ;)</li>
</ul>
</li>
</ul>
<p>Mode d'emploi:</p>
<ul>
<li>Cliquez sur l'un des créneaux vacants pour le réserver</li>
<li>Cliquez sur l'un des créneaux que vous occupez pour le libérez</li>
</ul>
{% for day in turns %}
{% set wday = day[0] %}
{% set day_turns = day[1] %}
{% set cur_role = '' %}
<div class='table_header'><div class='sheet_day' style='page-break-before: always;'>{{ wday }}</div></div>
{% for turn in day_turns %}
{% set role = turn[1] %}
{% set start_time = turn[0].start_time %}
{% set end_time = turn[0].end_time %}
{% set num_slot = turn[0].num_slot %}
{% set role_id = turn[0].role_id %}
{% set turn_id = turn[0].id %}
{% if role != cur_role %}
{% set cur_role = role %}
<div class='table_header'>
<div class='sheet_role'>{{ role }}</div>
</div>
{% endif %}
<div class='table_row' style='border-bottom-color: #FF5D00; border-bottom-width: 1px; border-bottom-style: solid;'>
<div class='sheet_time border_right'>{{ start_time.strftime('%HH%M') }} / {{ end_time.strftime('%HH%M') }}</div>
{% for slot in range(0, num_slot) %}
{% set allocated_slot = [] %}
{% set border = '' %}
{% if slot != num_slot - 1 %}
{% set border = 'border_right' %}
{% endif %}
{% for sslot in staffs %}
{% if sslot[0].turn_id == turn_id and sslot[0].slot_num == slot %}
{% if allocated_slot.append(sslot[0].slot_num) %}
{% endif %}
{% if user_id == sslot[0].user_id %}
<div class='sheet_user{{ num_slot }} {{ border }}' onclick='javascript:clear_sheet({{ turn_id }}, {{ slot }})'>{{ sslot[1] }}</div>
{% else %}
<div class='sheet_user{{ num_slot }} {{ border }}' style='cursor: text;'>{{ sslot[1] }}</div>
{% endif %}
{% endif %}
{% endfor %}
{% if slot not in allocated_slot %}
<div class='sheet_user{{ num_slot }} {{ border }}' onclick='javascript:update_sheet({{ turn_id }}, {{ slot }})'>&nbsp;</div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
{% endfor %}
</article>
{% endblock %}

View File

@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang='zxx'>
<head>
<title>We Make THSF - {% block title %}Feuille de staff{% endblock %}</title>
<meta name="viewport" content="initial-scale=1.0" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="/static/styles/tetawebapp_pdf.css" />
<link rel="icon" type="image/png" href="/static/images/favicon.png" />
</head>
<body>
<div class='content'>
<main id='main'>
<article id='roles'>
<h3>Fiches de poste</h3>
<p>Les postes de référents (référent staff, référent bar, référent run) sont réservés à des personnes ayant une bonne connaissance du lieu et de l'évènement.</p>
{% for role in roles %}
<div class='table_header'><div class='sheet_day'>{{ role.role }}</div></div>
<div class='table_row'>
<ul>
{% set desc = role.description.split('|') %}
{% for point in desc %}
<li>{{ point }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</article>
<hr/>
<article id='staff_sheet'>
<h3>Feuille de staff</h3>
<ul>
<li><strong>Ménage le soir même pour tous les derniers créneaux du jour</strong></li>
<li><strong>Tâches dévolues à tous:</strong>
<ul>
<li>Veiller à la sécurité générale du lieu</li>
<li>Ramassage bouteilles ou objets en verre</li>
<li>Séparation des bagarres (rarissime)</li>
<li>Sécurisation des personnes en difficulté (ou trop alcoolisées), etc...</li>
<li>Sourire et bonne humeur quel que soit le niveau de fatigue ;)</li>
</ul>
</li>
</ul>
{% for day in turns %}
{% set wday = day[0] %}
{% set day_turns = day[1] %}
{% set cur_role = '' %}
<div class='table_header'><div class='sheet_day'>{{ wday }}</div></div>
{% for turn in day_turns %}
{% set role = turn[1] %}
{% set start_time = turn[0].start_time %}
{% set end_time = turn[0].end_time %}
{% set num_slot = turn[0].num_slot %}
{% set role_id = turn[0].role_id %}
{% set turn_id = turn[0].id %}
{% if role != cur_role %}
{% set cur_role = role %}
<div class='table_header'>
<div class='sheet_role'>{{ role }}</div>
</div>
{% endif %}
<div class='table_row' style='border-bottom-color: #FF5D00; border-bottom-width: 1px; border-bottom-style: solid;'>
<div class='sheet_time border_right'>{{ start_time.strftime('%HH%M') }} / {{ end_time.strftime('%HH%M') }}</div>
{% for slot in range(0, num_slot) %}
{% set allocated_slot = [] %}
{% set border = '' %}
{% if slot != num_slot - 1 %}
{% set border = 'border_right' %}
{% endif %}
{% for sslot in staffs %}
{% if sslot[0].turn_id == turn_id and sslot[0].slot_num == slot %}
{% if allocated_slot.append(sslot[0].slot_num) %}
{% endif %}
<div class='sheet_user{{ num_slot }} {{ border }}' style='cursor: text;' id='staff_{{ staff }}'>{{ sslot[1] }}</div>
{% endif %}
{% endfor %}
{% if slot not in allocated_slot %}
<div class='sheet_user{{ num_slot }} {{ border }}' id='staff_{{ staff }}' onclick='javascript:update_sheet({{ turn_id }}, {{ slot }})'>&nbsp;</div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
{% endfor %}
</article>
</main>
</div>
</body>
</html>

View File

@ -0,0 +1,58 @@
("Fiches de poste",)
("Les postes de référents (référent staff, référent bar, référent run) sont réservés à des personnes ayant une bonne connaissance du lieu et de l'évènement.",)
{% for role in roles %}
(" ",)
("{{ role.role }}",)
{% set desc = role.description.split('|') %}
{% for point in desc %}
("{{ point }}",)
{% endfor %}
{% endfor %}
(" ",)
("Feuille de staff",)
("Ménage le soir même pour tous les derniers créneaux du jour",)
("",)
("Tâches dévolues à tous:",)
("Veiller à la sécurité générale du lieu",)
("Ramassage bouteilles ou objets en verre",)
("Séparation des bagarres (rarissime)",)
("Sécurisation des personnes en difficulté (ou trop alcoolisées), etc...",)
("Sourire et bonne humeur quel que soit le niveau de fatigue ;)",)
(" ",)
{% for day in turns %}
{% set wday = day[0] %}
{% set day_turns = day[1] %}
{% set cur_role = '' %}
("{{ wday }}",)
{% for turn in day_turns %}
{% set role = turn[1] %}
{% set start_time = turn[0].start_time %}
{% set end_time = turn[0].end_time %}
{% set num_slot = turn[0].num_slot %}
{% set role_id = turn[0].role_id %}
{% set turn_id = turn[0].id %}
{% if role != cur_role %}
{% set cur_role = role %}
("{{ role }}",)
{% endif %}
("{{ start_time.strftime('%HH%M') }} / {{ end_time.strftime('%HH%M') }}",
{% for slot in range(0, num_slot) %}
{% set allocated_slot = [] %}
{% set border = '' %}
{% if slot != num_slot - 1 %}
{% set border = 'border_right' %}
{% endif %}
{% for sslot in staffs %}
{% if sslot[0].turn_id == turn_id and sslot[0].slot_num == slot %}
{% if allocated_slot.append(sslot[0].slot_num) %}
{% endif %}
"{{ sslot[1] }}",
{% endif %}
{% endfor %}
{% if slot not in allocated_slot %}
" ",
{% endif %}
{% endfor %}
)
{% endfor %}
{% endfor %}

View File

@ -3,7 +3,7 @@
{% block main %}
<article>
<h3>Tour de staff:</h3>
<form method='POST' action='/turn/update/{{ turn.id }}'>
<form method='POST' action="/turn/update/{{ turn.id }}">
<label>Role: </label><select id='role_id' name='role_id'>
{% for role in roles %}
{% set selected = '' %}
@ -16,14 +16,15 @@
<label>Jour de la semaine: </label><select id='day' name='day'>
{% for day in days %}
{% set selected = '' %}
{% if turn.wday == day %}
{% if turn.day == day[0] %}
{% set selected="selected" %}
{% endif %}
<option {{ selected }} value='{{ day }}'>{{ day }}</option>
<option {{ selected }} value='{{ day[1] }}'>{{ day[0] }}</option>
{% endfor %}
</select><br/>
<label>Début (HH:MM:SS) </label><input id='start' name='start' type='text' maxlength=8 value='{{ turn.start_time }}'/><br/>
<label>Fin: (HH:MM:SS) </label><input id='end' name='end' type='text' maxlength=8 value='{{ turn.end_time }}'/><br/>
<label>Nombre de slots (max. 4) </label><input id='num_slot' name='num_slot' type='number' min='1' max='4' value='{{ turn.num_slot }}'/><br/>
<input type='submit' value='Enregistrer' onclick='javascript:return save_turn();'/>
</form>
</article>