328 lines
9.8 KiB
Python
328 lines
9.8 KiB
Python
################
|
|
### 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
|
|
|
|
from flask import Flask
|
|
from flask import render_template
|
|
from flask import url_for
|
|
from flask import request
|
|
|
|
# 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 = 3 # Le nombre maximum affiché de départements qui ont été le plus cherché
|
|
PING_WARN = 500 # Nombre d'utilisations à partir du quel un message d'avertissement est affiché
|
|
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
|
|
|
|
# Globales
|
|
logs = [] # Stoque les différentes requêtes faite sur la route /free_rooms/, sous la forme {"timestamp":timestamp,"depts":[]}
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
# Fonctions :
|
|
|
|
@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 = {}
|
|
for log in logs:
|
|
for dept in log["depts"]:
|
|
pings+=1
|
|
if dept.name in counts.keys():
|
|
counts[dept.name]+=1
|
|
else:
|
|
counts[dept.name]=1
|
|
|
|
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
|
|
|
|
context = {"MAX_LOG_DAYS":MAX_LOG_DAYS,"PING_WARN":PING_WARN,"depts":sort[:MAX_LOG_DEPT],"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("-")
|
|
|
|
time_uf = str(request.args.get("time"))
|
|
time_uf_sav = time_uf
|
|
if time_uf == "None" :
|
|
time_uf = [""]
|
|
else :
|
|
time_uf = time_uf.split(":")
|
|
|
|
|
|
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"]
|
|
|
|
free_rooms = ro.getrooms(date, depts, ignore_list)
|
|
|
|
# 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 i<len(dident_list)-1:
|
|
change_date_str += "&"
|
|
i+=1
|
|
|
|
|
|
# Génération du lien pour enlever les favoris séléctionnés :
|
|
nofavslink = "/app/free-rooms?"
|
|
for dept in dident_list:
|
|
nofavslink+="dept="+str(dept)+"&"
|
|
# Ajout des éventuels date et heure :
|
|
if date_uf_sav != "None" :
|
|
nofavslink += "date=" + date_uf_sav + "&"
|
|
if time_uf_sav != "None" :
|
|
nofavslink += "time=" + time_uf_sav
|
|
# Suppression de l'éventuel '&' en trop :
|
|
if nofavslink[-1] == "&":
|
|
nofavslink = nofavslink[:-1]
|
|
|
|
# Tri des salles selon leurs catégories :
|
|
favs_free_rooms = []
|
|
favs_soon_rooms = []
|
|
soon_rooms = []
|
|
final_rooms = []
|
|
|
|
for r in free_rooms:
|
|
[[soon_rooms,final_rooms],[favs_soon_rooms,favs_free_rooms]][r.id in favs_ids][r.is_free].append(r)
|
|
|
|
# Tri des salles bientôt disponibles en fonction du temps d'attente
|
|
sortfunc = lambda x: x.start-date.astimezone(pytz.timezone('Europe/Paris'))
|
|
soon_rooms.sort(key=sortfunc)
|
|
favs_soon_rooms.sort(key=sortfunc)
|
|
|
|
context = {"favs_free_rooms":favs_free_rooms, "favs_soon_rooms":favs_soon_rooms,
|
|
"free_rooms":final_rooms, "soon_rooms":soon_rooms, "frooms_disp":frooms_disp,
|
|
"depts_str":depts_str, "dident_list":dident_list, "date_str":date_str,
|
|
"date_uf_sav":date_uf_sav, "time_uf_sav":time_uf_sav, "change_date_str":change_date_str,
|
|
"favs":len(favs_ids)>0,"nofavslink":nofavslink}
|
|
|
|
# Création d'un log de la date et des départements demandés (pour les stats du site) :
|
|
log = {}
|
|
log["timestamp"] = dti.datetime.now().timestamp()
|
|
log["depts"] = depts
|
|
logs.append(log)
|
|
|
|
# Suppression des logs vieux de MAX_LOG_DAYS :
|
|
while (log["timestamp"] - logs[0]["timestamp"]) / (60*60*24) > MAX_LOG_DAYS :
|
|
del(logs[0])
|
|
|
|
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.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)
|