#!/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//', u'/staffsheet/update//']}, 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/', u'/turn/new', u'/turn/add', u'/turn/delete/', u'/turn/update/']}, 0], [u'Feuille de staff', {u'/staffsheet': [u'/staffsheet', u'/staffsheet/clear//', u'/staffsheet/update//', u'/staffsheet/pdf']}, 0], [u'Liste des staffers', {u'/users': [u'/users', u'/account/', u'/account/delete/']}, 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/']}, 0, 0], [u'Second article', {u'/articles/2': [u'/articles', u'/articles/']}, 0, 0], [u'Third article', {u'/articles/3': [u'/articles', u'/articles/']}, 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_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/", 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/", 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: 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("Fiches de poste", 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("%s" % 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("Feuilles de staff", styles['master_title'])) elements.append(Paragraph("Ménage le soir même pour tous les derniers créneaux du jour", styles['basic_text'], bulletText='-')) elements.append(Paragraph("Tâches dévolues à tous et à tous moments:", 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("Sourire et bonne humeur 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("%s" % 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("%s" % role, styles['role_title'])) row = (Paragraph("%s / %s" % (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(" ", 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//", 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 " " 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//", 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/", 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/", 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/", 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/", 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/", 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/", 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')