L'application de gestion du staff du THSF: https://participer.thsf.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1320 lines
54 KiB

#!/usr/bin/env python
# -*- coding: utf-8
# Required modules
import os
import inspect
import random
import binascii
import bcrypt
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
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
app.debug = True
# Various configuration settings belong here (optionnal)
app.config.from_pyfile('config.local.py')
# Generate a new key: head -n 40 /dev/urandom | md5sum | cut -d' ' -f1
app.secret_key = '9ac80548e3a8d8dfd1aefcd9a3a73473'
# Feel free to use SQLAlchemy (or not)
db = SQLAlchemy(app)
########################################################################
# Sample user database
########################################################################
class Tetawebapp_users(db.Model):
__tablename__ = 'participer_thsf_users'
id = db.Column(db.Integer, primary_key=True)
mail = db.Column(db.Text, nullable=False)
password = db.Column(db.Text, nullable=False)
name = db.Column(db.Text, nullable=True)
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'
id = db.Column(db.Integer, primary_key=True)
role = db.Column(db.Text, nullable=False)
description = db.Column(db.Text, nullable=False)
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)
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
########################################################################
def get_menu(page):
""" The main menu is a list of lists in the followin format:
[unicode caption,
{unicode URL endpoint: [unicode route, ...]},
int 0]
- The URL end point is the URL where to point to (href)
- One of the routes MUST match the route called by request
- The int 0 is used to determine which menu entry is actally called.
The value MUST be 0."""
menu = [[u'Accueil', {u'/': [u'/']}, 0],
[u'Mon compte', {u'/account': [u'/account', u'/account/update']}, 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'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],
]
#~ print '[+] Page: %s' % page
for item in menu:
for url in item[1]:
for route in item[1][url]:
#~ print " [+] Route: %s" %route
if route == page:
#~ print " [+] Selected page: %s" % page
item[2] = 1
return menu
# This should never happen
return menu
def get_navbar(page, selected):
""" The horizontal navbar is a list of lists in the followin format:
[unicode caption, {unicode URL endpoint: [unicode route, ...]}, int 0]
- The URL end point is the URL where to point to (href)
- One of the routes MUST match the route called by request
- The int 0 is used to de """
navbars = [[u'First article', {u'/articles/1': [u'/articles', u'/articles/<ID>']}, 0, 0],
[u'Second article', {u'/articles/2': [u'/articles', u'/articles/<ID>']}, 0, 0],
[u'Third article', {u'/articles/3': [u'/articles', u'/articles/<ID>']}, 0, 0]
]
navbar = []
for item in navbars:
for url in item[1]:
if url == selected:
item[2] = 1
for route in item[1][url]:
if route == page:
navbar.append(item)
navbar[len(navbar) - 1][3] = 1
return navbar
########################################################################
# Session management
########################################################################
def sync_session(request, session):
""" Synchronize cookies with session """
for key in request.cookies:
session[key] = request.cookies[key].encode('utf8')
def sync_cookies(response, session):
""" Synchronize session with cookies """
for key in session:
response.set_cookie(key, value=str(session[key]))
def check_session(func):
""" Check if the session has required token cookie set.
If not, redirects to the login page. """
@wraps(func)
def check(*args, **kwargs):
try:
if session['token'] == request.cookies['token'] and len(session['token']) > 0:
# User is logged in and identified
return func(*args, **kwargs)
else:
# User is not logged in or session expired
session['token'] = ''
response = app.make_response(render_template('login_or_register.html', message=''))
sync_cookies(response, session)
return response
except KeyError:
# User is not logged in
return render_template('login_or_register.html', message='')
return check
def gen_token(size=42):
""" Generate a random token to be stored in session and cookie """
token = binascii.hexlify(os.urandom(size))
return token
########################################################################
# User management
########################################################################
def check_login(login, password):
""" Puts the login verification code here """
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
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
def register_user(login, password, confirm):
""" Register new user """
if password != confirm:
# Password does not match confirmation
print "[+] Password mismatch confirmation"
return False
check_user = Tetawebapp_users.query.filter_by(mail=login).count()
if check_user != 0:
# 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, link_id=link_id)
try:
db.session.add(user)
commit = db.session.commit()
except Exception as e:
db.session.rollback()
print "[+] Error at register_user:"
print "------------------------------"
print "%s" % e.message
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 by setting link_id == None """
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:
# Password does not match confirmation
print "[+] Password mismatch confirmation"
return False
check_user = Tetawebapp_users.query.filter_by(mail=login).count()
if check_user == 0:
# User does not exist
print "[+] User does not exist"
return False
user = Tetawebapp_users.query.filter_by(mail=login).first()
if len(password) > 0:
# User requested password modification
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
setattr(user, 'password', hashed_password)
# Password has been updated if necessary
# Now let's update other data
setattr(user, 'name', name)
setattr(user, 'phone', phone)
setattr(user, 'diet', diet)
try:
db.session.add(user)
commit = db.session.commit()
except Exception as e:
db.session.rollback()
print "[+] Error at update_user:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
if commit != None:
return False
return True
def update_user_by_id(user_id, login, password, confirm, name, phone, diet):
""" Update user infos with provided data """
if password != confirm:
# Password does not match confirmation
print "[+] Password mismatch confirmation"
return False
check_user = Tetawebapp_users.query.filter_by(id=user_id).count()
if check_user == 0:
# User does not exist
print "[+] User does not exist"
return False
user = Tetawebapp_users.query.filter_by(id=user_id).first()
if len(password) > 0:
# User requested password modification
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
setattr(user, 'password', hashed_password)
# Password has been updated if necessary
# Now let's update other data
setattr(user, 'name', name)
setattr(user, 'phone', phone)
setattr(user, 'diet', diet)
try:
db.session.add(user)
commit = db.session.commit()
except Exception as e:
db.session.rollback()
print "[+] Error at update_user_by_id:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
if commit != None:
return False
return True
def reset_password(login, password, confirm):
if password != confirm:
# Password does not match confirmation
print "[+] [Reset password] Password mismatch confirmation"
return False
check_user = Tetawebapp_users.query.filter_by(mail=login).count()
if check_user == 0:
# User does not exist
print "[+] [Reset password] User does not exist"
return False
user = Tetawebapp_users.query.filter_by(mail=login).first()
if len(password) > 0:
# User requested password modification
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
setattr(user, 'password', hashed_password)
# We also ask for a new account confirmation
link_id = gen_token(20)
setattr(user, 'link_id', link_id)
try:
db.session.add(user)
commit = db.session.commit()
send_reset_mail(login, link_id)
except Exception as e:
db.session.rollback()
print "[+] Error at reset_password:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
if commit != None:
return False
return True
def delete_user(user_id):
""" Delete user """
try:
Tetawebapp_users.query.filter_by(id=int(user_id)).delete()
db.session.commit()
return True
except ValueError as e:
return False
except Exception as e:
db.session.rollback()
print "[+] Error at delete_user:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
def get_user_name(user_id):
""" Get pseudo from user ID """
try:
user = Tetawebapp_users.query.filter_by(id=int(user_id)).first()
return user.name
except Exception as e:
print "[+] Error at get_pseudo:"
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 = ''
user = Tetawebapp_users.query.filter_by(mail=session['login']).first()
name = user.name
phone = user.phone
diet = user.diet
if name == None or phone == None or \
len(name) == 0 or len(phone) == 0:
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')
########################################################################
# Turns
########################################################################
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'),
start_time=start.encode('utf-8'),
end_time=end.encode('utf-8'),
num_slot=num_slot.encode('utf-8'),
)
try:
db.session.add(turn)
commit = db.session.commit()
except Exception as e:
db.session.rollback()
print "[+] Error at save_turn:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
if commit != None:
return False
return True
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 "[+] 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, 'start_time', start)
setattr(turn, 'end_time', end)
setattr(turn, 'num_slot', num_slot)
try:
db.session.add(turn)
commit = db.session.commit()
except Exception as e:
db.session.rollback()
print "[+] Error at update_turn:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
if commit != None:
return False
return True
def drop_turn(turn_id):
""" Delete staff turn """
try:
Tetawebapp_turns.query.filter_by(id=int(turn_id)).delete()
db.session.commit()
return True
except ValueError as e:
print e
return False
except Exception as e:
db.session.rollback()
print "[+] Error at drop_turn:"
print "------------------------------"
print "%s" % e.message
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 08/05', tuesday_turns))
turns.append(('Mercredi 09/05', wenesday_turns))
turns.append(('Jeudi 10/05', thirsday_turns))
turns.append(('Vendredi 11/05', friday_turns))
turns.append(('Samedi 12/05', saturday_turns))
turns.append(('Dimanche 13/05', sunday_turns))
return turns
########################################################################
# Staffs
########################################################################
def get_staffers():
try:
staffers = Tetawebapp_users.query(Tetawebapp_users.id > 1).all()
return staffers
except Exception as e:
print "[+] Error at get_staffers:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
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.query.filter(Tetawebapp_staffs.turn_id==turn_id, Tetawebapp_staffs.slot_num==slot_id).count()
if slot == 0:
slot = Tetawebapp_staffs.query.filter(Tetawebapp_staffs.turn_id==turn_id, Tetawebapp_staffs.user_id==user_id).count()
if slot == 0:
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
return False
def check_user_availability(turn_id, user_id):
""" Check if user is available for this turn """
turn_start, turn_end = Tetawebapp_turns.query.filter(Tetawebapp_turns.id==turn_id).with_entities(Tetawebapp_turns.start_time, Tetawebapp_turns.end_time).first()
user_turns = Tetawebapp_staffs.query.filter(Tetawebapp_staffs.user_id==user_id).with_entities(Tetawebapp_staffs.turn_id).all()
for turn in user_turns:
t_start, t_end = Tetawebapp_staffs.query.filter(Tetawebapp_turns.id==turn[0]).with_entities(Tetawebapp_turns.start_time, Tetawebapp_turns.end_time).first()
if t_start <= turn_start and t_end > turn_start:
return False
if t_start >= turn_start and t_start < turn_end:
return False
return True
########################################################################
# Role
########################################################################
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_roles:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
########################################################################
# Mail
########################################################################
def send_mail(email, link_id):
msg = Message("[THSF] 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 += "%s/confirm/%s\n\n" % (str(app.config['DOMAIN_URL']), 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)
def send_reset_mail(email, link_id):
msg = Message("[THSF] Confirmation de réinitialisation de votre mot de passe",
sender="dave.null@tetalab.org",
recipients=[email])
msg.body = "Bonjour,\nVous recevez ce courriel car vous avez demandé la réinitialisation de votre mot de passe.\n\n"
msg.body += "Pour confirmer votre noueau mot de passe, rendez vous à la page suivante:\n"
msg.body += "%s/confirm/%s\n\n" % (str(app.config['DOMAIN_URL']), str(link_id))
msg.body += "Si vous n'êtes pas à l'origine de cette demande de réinitialisation, envoyez un email à contact@tetalab.org afin que soient prises les dispositions nécessaires.\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:
# -------
# Except for the index function, the function name MUST have the same
# name than the URL endpoint to make the menu work properly
########################################################################
@app.errorhandler(404)
def page_not_found(e):
""" 404 not found """
return render_template('error.html'), 404
########################################################################
# Entry
########################################################################
@app.route("/", methods=['GET', 'POST'])
@check_session
def index():
""" Index page """
page = str(request.url_rule)
menu = get_menu(page)
message = check_user_info()
return render_template('index.html', menu=menu, message=message, login=session['login'])
########################################################################
# Session
########################################################################
@app.route("/login", methods=['GET', 'POST'])
def login():
""" Login """
try:
login = request.form.get('login').encode('utf-8')
password = request.form.get('password').encode('utf-8')
if check_login(login, password):
# 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('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")
########################################################################
# User
########################################################################
@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 """
try:
login = request.form.get('login').lower().encode('utf-8')
password = request.form.get('password').encode('utf-8')
confirm = request.form.get('confirm').encode('utf-8')
message = "Erreur lors de l'enregsitrement: L'utilisateur existe t-il déjà ?"
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")
@app.route("/account", methods=['GET', 'POST'])
@check_session
def account():
""" Account page """
page = str(request.url_rule)
menu = get_menu(page)
user = Tetawebapp_users.query.filter_by(mail=session['login']).first()
mail = '' if user.mail == None else user.mail
name = '' if user.name == None else user.name
phone = '' if user.phone == None else user.phone
diet = '' if user.diet == None else user.diet
message = check_user_info()
return render_template('account.html', menu=menu, mail=mail, name=name, phone=phone, diet=diet, message=message)
@app.route("/account/update", methods=['GET', 'POST'])
@check_session
def update_account():
""" Update current account """
try:
page = str(request.url_rule)
menu = get_menu(page)
login = session['login']
password = request.form.get('password').encode('utf-8')
confirm = request.form.get('confirm').encode('utf-8')
name = request.form.get('name').encode('utf-8')
phone = request.form.get('phone').encode('utf-8')
diet = request.form.get('diet').encode('utf-8')
if update_user(login, password, confirm, name, phone, diet):
message = check_user_info()
else:
message = "Erreur lors de l'enregistrement des données."
return render_template('account.html',
menu=menu,
mail=login.decode('utf-8'),
name=name.decode('utf-8'),
phone=phone.decode('utf-8'),
diet=diet.decode('utf-8'),
message=message)
except AttributeError:
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@app.route("/account/reset/ask", methods=['GET', 'POST'])
def ask_reset():
""" Ask for password reset page """
return render_template('reset_password.html')
@app.route("/account/reset", methods=['GET', 'POST'])
def reset_account():
""" Password reset """
try:
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 reset_password(login, password, confirm):
message = "Une confirmation de la réinitialisation de votre mot de passe vous a été envoyé par email"
return render_template('login_or_register.html', message=message.decode('utf-8'))
message="Erreur lors de la réinitialisation du mot de passe"
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")
@app.route("/logout", methods=['GET', 'POST'])
@check_session
def logout():
""" Logout user """
# Remove session token
session['token'] = None
session['login'] = None
session['is_admin'] = 0
# Return user to index page
response = app.make_response(render_template('login_or_register.html', message=''))
# Push token to cookie
sync_cookies(response, session)
return response
########################################################################
# Staffsheet
########################################################################
@app.route("/staffsheet", methods=['GET', 'POST'])
@check_session
def staffsheet():
try:
is_admin = session['is_admin']
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()
staffers = get_staffers()
roles = get_roles()
return render_template('staffsheet.html', menu=menu, turns=turns, staffs=staffs, staffers=staffers, user_id=user_id, is_admin=is_admin, 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']
if (drop_staff_slot(turn_id, slot_id, user_id)):
return "&nbsp;"
return "KO"
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']
user_name = get_user_name(user_id)
if user_name != None:
if check_user_availability(turn_id, user_id):
if (save_staff_slot(turn_id, slot_id, user_id)):
return user_name
return "KO"
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")
########################################################################
# Admin zone
########################################################################
@app.route("/users", methods=['GET', 'POST'])
@check_session
def list_users():
""" Users list """
message = check_user_info()
try:
if session['is_admin']:
page = str(request.url_rule)
menu = get_menu(page)
staffers = Tetawebapp_users.query.filter(Tetawebapp_users.is_admin==0, Tetawebapp_users.link_id==None).order_by(Tetawebapp_users.name).all()
return render_template('list_users.html', menu=menu, staffers=staffers, message=message)
# User is not admin
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@app.route("/users/print", methods=['GET', 'POST'])
@check_session
def print_users():
""" Print user list """
message = check_user_info()
try:
if session['is_admin']:
staffers = Tetawebapp_users.query.filter(Tetawebapp_users.is_admin==0, Tetawebapp_users.link_id==None).order_by(Tetawebapp_users.name).all()
return render_template('list_users_txt.html', staffers=staffers)
# User is not admin
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except Exception as e:
raise e
# User is not logged in
return render_template('login_or_register.html', message="%s" % e)
@app.route("/account/<ID>", methods=['GET', 'POST'])
@check_session
def account_by_id(ID):
""" Arcticles page """
try:
if session['is_admin']:
page = str(request.url_rule)
menu = get_menu(page)
message = "ID de l'utilisateur non conforme"
staffers = Tetawebapp_users.query.filter_by(is_admin=0).order_by(Tetawebapp_users.name).all()
user_id = int(ID.encode('utf-8'))
user = Tetawebapp_users.query.filter_by(id=user_id).first()
return render_template('account_by_id.html', menu=menu, user=user)
# User is not admin
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except AttributeError:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except ValueError:
# ID is not an integer
return render_template('list_users.html', menu=menu, staffers=staffers, message=message)
@app.route("/account/update/<ID>", methods=['GET', 'POST'])
@check_session
def update_account_by_id(ID):
""" Update given account """
try:
if session['is_admin']:
page = str(request.url_rule)
menu = get_menu(page)
login = session['login']
password = request.form.get('password').encode('utf-8')
confirm = request.form.get('confirm').encode('utf-8')
name = request.form.get('name').encode('utf-8')
phone = request.form.get('phone').encode('utf-8')
diet = request.form.get('diet').encode('utf-8')
message = "ID de l'utilisateur non conforme"
staffers = Tetawebapp_users.query.filter_by(is_admin=0).order_by(Tetawebapp_users.name).all()
user_id = int(ID.encode('utf-8'))
if update_user_by_id(user_id, login, password, confirm, name, phone, diet):
user = Tetawebapp_users.query.filter_by(id=ID).first()
message = check_user_info()
else:
message = "Erreur lors de l'enregistrement des données."
return render_template('account_by_id.html', menu=menu, user=user,message=message)
# User is not admin
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except AttributeError:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except ValueError:
# ID is not an integer
return render_template('list_users.html', menu=menu, staffers=staffers, message=message)
@app.route("/account/delete/<ID>", methods=['GET', 'POST'])
@check_session
def delete_account(ID):
""" Delete given account """
try:
if session['is_admin']:
message = "Erreur lors de la suppression.".decode('utf-8')
page = str(request.url_rule)
menu = get_menu(page)
staffers = Tetawebapp_users.query.filter_by(is_admin=0).order_by(Tetawebapp_users.name).all()
user_id = int(ID.encode('utf-8'))
if delete_user(user_id):
message = ''
staffers = Tetawebapp_users.query.filter_by(is_admin=0).order_by(Tetawebapp_users.name).all()
return render_template('list_users.html', menu=menu, staffers=staffers, message=message)
# User is not admin
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except AttributeError:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except ValueError:
# ID is not an integer
return render_template('list_users.html', menu=menu, staffers=staffers, message=message)
########################################################################
# Turns
########################################################################
@app.route("/turns", methods=['GET', 'POST'])
@check_session
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 = 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")
@app.route("/turn/new", methods=['GET', 'POST'])
@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 = [('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
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@app.route("/turn/add", methods=['GET', 'POST'])
@check_session
def add_turn():
""" Add staff turn """
try:
if session['is_admin']:
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')
page = str(request.url_rule)
menu = get_menu(page)
turns = turns_list()
message = "Erreur lors de l'enregistrement.".decode('utf-8')
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)
# User is not admin
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
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("/turn/<ID>", methods=['GET', 'POST'])
@check_session
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()
message = 'ID du tour de staff non conforme'
turns = turns_list()
turn_id = int(ID.encode('utf-8'))
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
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except ValueError:
# ID is not an integer
return render_template('list_turns.html', menu=menu, page=page, turns=turns, message=message)
@app.route("/turn/update/<ID>", methods=['GET', 'POST'])
@check_session
def update_turn(ID):
""" Update given staff turn """
try:
role_id = request.form.get('role_id').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 = 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, num_slot):
turns = turns_list()
message = ''
return render_template('list_turns.html', menu=menu, page=page, turns=turns, message=message)
# User is not admin
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except AttributeError as e:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except ValueError:
# ID is not an integer
return render_template('list_turns.html', menu=menu, page=page, turns=turns, message=message)
@app.route("/turn/delete/<ID>", methods=['GET', 'POST'])
@check_session
def delete_turn(ID):
""" Delete given staff turn """
try:
if session['is_admin']:
message = 'Erreur lors de la suppression.'
page = str(request.url_rule)
menu = get_menu(page)
turns = turns_list()
turn_id = int(ID.encode('utf-8'))
if drop_turn(turn_id):
message = ''
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
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except AttributeError:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except ValueError:
# ID is not an integer
return render_template('list_turns.html', menu=menu, page=page, turns=turns, message=message)
########################################################################
# Main
########################################################################
if __name__ == '__main__':
app.run(host='0.0.0.0')