################ ### UniSquat ### ################ """ Une application pour afficher les salles libres dans les différents départements de l'Université de Strasbourg. """ ### Fichier de l'interface Web Flask ### # Modules : import datetime as dti import pytz import json import os import traceback from flask import Flask from flask import render_template from flask import url_for from flask import request from flask.helpers import make_response # Fichiers locaux : import date_tools import rooms_get as ro # Constantes : MAX_DEPT = 5 # Le maximum de départements qu'il est possible de sélectionner MAX_LOG_DAYS = 30 # Le nombre de jours pendant lesquels les logs sont conservés MAX_LOG_DEPT = 5 # Le nombre maximum affiché de départements qui ont été le plus cherché MAX_LOG_FAVS = 10 # Le nombre maximum affiché de salles qui ont été le plus mises en favoris PING_WARN = 500 # Nombre d'utilisations à partir du quel un message d'avertissement est affiché LOG_FILE = "log.json" # Contient des stats sur les salles les plus mises en favoris, et les départements GLOBAL_CONTEXT = {} # Contexte constant pour les templates Jinja GLOBAL_CONTEXT["SOURCE"] = "https://forge.chapril.org/Wantoo/UniSquat_Python" # Le lien du code source GLOBAL_CONTEXT["CREDITSLINK"] = "https://forge.chapril.org/Wantoo" # Le lien de l'organisation GLOBAL_CONTEXT["CREDITSNAME"] = "Wantoo" # Le nom de l'organisation GLOBAL_CONTEXT["DEBUG"] = False # Fait en sorte que le logiciel soit un peu plus expressif GLOBAL_CONTEXT["DOMAIN"] = "https://unisquat.alwaysdata.net" # Le domaine sur lequel est host l'instance # Globales app = Flask(__name__) logs = [] # Stoque les différentes requêtes faite sur la route /free_rooms/, sous la forme {"timestamp":timestamp,"depts":[]} if os.path.isfile(LOG_FILE): with open(LOG_FILE,"r") as f: logs = json.loads(f.read()) # Fonctions : def save_logs(logs): with open(LOG_FILE,"w") as f: f.write(json.dumps(logs)) @app.route("/") def home() : """ Page d'accueil du site Web. Parameters ---------- None Returns ------- flask.render_template """ return render_template("index.html", **GLOBAL_CONTEXT) @app.route("/app") def select_dept() : """ Permet de sélectionner un ou plusieurs départements dans lesquels chercher des salles libres. Parameters ---------- None Returns ------- flask.render_template """ dept_filen = "data/dept_list.txt" dept_list = ro.get_depts(dept_filen) context = {"dept_list":dept_list} url_for("static", filename="style.css") return render_template("dept-select.html", **context, **GLOBAL_CONTEXT) @app.route("/stats") def stats(): """ Statistiques d'utilisation de l'instance Parameters ---------- None (utilise la variable globale 'log') Returns ------- flask.render_template """ # Compte le nombre de fois que les différents départements ont été cherchés pings = 0 counts = {} counts_favs = {} def fmap(fav): return fav[0]+";"+fav[1] for log in logs: if log["type"]=="deptcount": for dept in log["depts"]: pings+=1 if dept in counts.keys(): counts[dept]+=1 else: counts[dept]=1 elif log["type"]=="favs": for fav in log["favs"]: if fmap(fav) in counts_favs.keys(): counts_favs[fmap(fav)][2] += 1 else: fav.append(1) counts_favs[fmap(fav)]=fav sort = [ [x,counts[x]] for x in counts.keys() ] sort.sort(key = lambda x: x[1],reverse = True ) # Trie selon la valeur du deuxieme élément de la liste sort_favs = [ counts_favs[x] for x in counts_favs.keys() ] sort_favs.sort(key = lambda x: x[2],reverse = True ) context = {"MAX_LOG_DAYS":MAX_LOG_DAYS,"PING_WARN":PING_WARN,"depts":sort[:MAX_LOG_DEPT],"favs":sort_favs[:MAX_LOG_FAVS],"nbping":pings} return render_template("stats.html",**context, **GLOBAL_CONTEXT) @app.route("/app/free-rooms", methods=["POST", "GET"]) def free_rooms() : """ Affiche les salles libres dans les départements sélectionnés dans la page des départements. Parameters ---------- None Returns ------- flask.render_template """ if GLOBAL_CONTEXT["DEBUG"]: print(f"dept:\n\t{request.args.getlist('dept')}") print(f"favs:\n\t{request.args.getlist('favs')}") print(f"date:\n\t{request.args.get('date')}\t{request.args.get('time')}") # Récupération des ID des départements depuis le formulaire : dident_list = list(request.args.getlist("dept")) if len(dident_list) > MAX_DEPT : return render_template("error.html", error="Trop de départements sélectionnés ! Vous pouvez en sélectionner "+str(MAX_DEPT)+" au maximum.") if len(dident_list) == 0 : return render_template("error.html", error="Il faut choisir au moins un département !") # Récupération de l'éventuelle date personnalisée (depuis la page de sélection de date) : date_uf = str(request.args.get("date")) date_uf_sav = date_uf if date_uf == "None" : date_uf = [""] else : date_uf = date_uf.split("-") if date_uf != [""] and not (date_tools.check_date(date_uf)) : return render_template("error.html", error="Date incorrecte !") time_uf = str(request.args.get("time")) time_uf_sav = time_uf if time_uf == "None" : time_uf = [""] else : time_uf = time_uf.split(":") if time_uf != [""] and not (date_tools.check_time(time_uf)) : return render_template("error.html", error="Heure incorrecte !") date = dti.datetime.now() date_str = "" # Date affichée sur la page (si personnalisée) if date_uf != [""] : date = date.replace(year = int(date_uf[0]), month = int(date_uf[1]), day = int(date_uf[2])) date_str += date_uf[2] + "/" + date_uf[1] + "/" + date_uf[0] if time_uf != [""] : date = date.replace(hour = int(time_uf[0]), minute = int(time_uf[1])) date_str += ", à " + time_uf[0] + ":" + time_uf[1] # Récupération des IDs des salles favorites : favs_ids = list(request.args.getlist("favs")) if favs_ids == [None] : favs_ids = [] # Récupération de la liste des départements existants : dept_filen = "data/dept_list.txt" dept_list = ro.get_depts(dept_filen) # Vérification qu'il n'y a pas de mauvais départements demandés : for d in dident_list : try : int(d) except: return render_template("error.html", error="Identifiant de département invalide !", **GLOBAL_CONTEXT) if int(d) < 0 or int(d) >= len(dept_list) : return render_template("error.html", error="Identifiant de département invalide !", **GLOBAL_CONTEXT) dident_list.sort() # Récupération des départements choisis à partir des données du formulaire : i = 0 depts = list() depts_str = "" # Noms des départements pour l'affichage for d in dept_list : if i < len(dident_list) and d.ident == int(dident_list[i]) : depts.append(d) depts_str += d.name if (i + 1) < len(dident_list) : depts_str += ", " i += 1 ignore_list = ["salle non définie", "salle en Distanciel"] try : free_rooms = ro.getrooms(date, depts, ignore_list) except ValueError as err : return render_template("error.html", error="Le serveur Unistra a rencontré une erreur ! Veuillez réessayer plus tard.") #return render_template("error.html", error="Le serveur Unistra a rencontré une erreur ! Détails de l'erreur : " + str(''.join(traceback.format_exception(None, err, err.__traceback__)))) # Création d'un dictionnaire avec les infos des salles : frooms_disp = dict() # Mise en forme des infos pour la page Web i = 0 for r in free_rooms : remain_time_str = "" if r.is_free : remain_time_str = date_tools.remain_time(date, r.end) else : remain_time_str = date_tools.remain_time(date, r.start) frooms_disp[r.name] = {"start":date_tools.hour_disp(r.start), "end":date_tools.hour_disp(r.end), "rtime":remain_time_str} # Ajout des arguments favoris, et départements à l'URL : change_date_str = "?" if favs_ids != [] : i = 0 for f in favs_ids : change_date_str += "favs=" + str(f) if i < len(favs_ids) - 1: change_date_str += "&" i+=1 change_date_str += "&" for v in dident_list: i = 0 change_date_str += "dept="+str(v) if i0,"nofavslink":nofavslink} # Création d'un log de la date et des départements demandés (pour les stats du site) : ctimestamp = dti.datetime.now().timestamp() log = {} log["timestamp"] = ctimestamp log["depts"] = [ x.name for x in depts ] # Liste les noms de départements log["type"] = "deptcount" # Type du log logs.append(log) # Création d'un log de la date et des salles favorites favs = favs_soon_rooms+favs_free_rooms if len(favs)>0: log = {} log["timestamp"] = ctimestamp log["favs"] = [ [x.name,x.dept_name] for x in favs] # Liste les noms des salles favorites log["type"] = "favs" # Type du log logs.append(log) # Suppression des logs vieux de MAX_LOG_DAYS : while (ctimestamp - logs[0]["timestamp"]) / (60*60*24) > MAX_LOG_DAYS : del(logs[0]) # Sauvegarde les logs dans un fichier cache save_logs(logs) url_for("static", filename="style.css") return render_template("free-rooms.html", **context, **GLOBAL_CONTEXT) @app.route("/app/date-select", methods=["POST", "GET"]) def date_select() : """ Permet de sélectionner une date à laquelle chercher des salles libres. Parameters ---------- None. Returns ------- flask.render_template """ dident_list = list(request.args.getlist("dept")) favs_ids = list(request.args.getlist("favs")) if GLOBAL_CONTEXT["DEBUG"]: print(f"dept:{dident_list}") print(f"favs:{favs_ids}") context = {"dident_list":dident_list, "favs_ids":favs_ids} return render_template("date-select.html", **context, **GLOBAL_CONTEXT) @app.route("/sitemap.xml") def sitemap(): sitemap_xml = render_template("sitemap.xml", **GLOBAL_CONTEXT) response= make_response(sitemap_xml) response.headers["Content-Type"] = "application/xml" return response @app.errorhandler(404) def error(e): """ Affiche la page d'erreur Parameters ---------- None. Returns ------- flask.render_template """ return render_template("error.html", error="Page non trouvée !", **GLOBAL_CONTEXT)