Compare commits
13 Commits
9e01bfbfd1
...
12fb20d91f
Author | SHA1 | Date | |
---|---|---|---|
12fb20d91f | |||
f79250216b | |||
057285717b | |||
7060885e7b | |||
a6c017e6a2 | |||
38ec94eb5d | |||
b5ccf85a7d | |||
b4e1a0b9a4 | |||
c5e0582f21 | |||
912dd9b5ca | |||
d79af7ef9b | |||
430e8938a2 | |||
4d66319a29 |
@ -20,10 +20,12 @@ Cette application dispose d'une interface Web fonctionnant avec Flask. Une insta
|
||||
|
||||
## Dépendances
|
||||
|
||||
Le modules Python suivant sont requis (ils peuvent être installés avec `pip`) :
|
||||
Le modules Python suivants sont requis (ils peuvent être installés avec `pip`) :
|
||||
|
||||
- `datetime`
|
||||
- `icalendar`
|
||||
- `ics`
|
||||
- `tatsu` (dépendance de `ics`)
|
||||
- `requests`
|
||||
- `flask` (requis pour l'interface Web)
|
||||
|
||||
|
197
app.py
197
app.py
@ -1,16 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
################
|
||||
### UniSquat ###
|
||||
################
|
||||
|
||||
"""
|
||||
Une application pour afficher les salles libres dans les différents
|
||||
départements de l'Université de Strasbourg.
|
||||
"""
|
||||
|
||||
# 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
|
||||
@ -29,22 +28,52 @@ 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
|
||||
# Le maximum de départements qu'il est possible de sélectionner :
|
||||
MAX_DEPT = 5
|
||||
|
||||
# Globales
|
||||
# Le nombre de jours pendant lesquels les logs sont conservés :
|
||||
MAX_LOG_DAYS = 30
|
||||
|
||||
# Le nombre maximal de départements les plus cherchés à afficher :
|
||||
MAX_LOG_DEPT = 5
|
||||
|
||||
# Le nombre maximal de salles favorites à afficher :
|
||||
MAX_LOG_FAVS = 10
|
||||
|
||||
# Nombre d'utilisations à partir duquel un message d'avertissement est affiché :
|
||||
PING_WARN = 500
|
||||
|
||||
# Contient des stats sur les salles les plus mises en favoris,
|
||||
# et sur les départements les plus recherchés :
|
||||
LOG_FILE = "log.json"
|
||||
|
||||
# Contexte constant pour les templates Jinja :
|
||||
GLOBAL_CONTEXT = {}
|
||||
|
||||
# Le lien vers le code source :
|
||||
GLOBAL_CONTEXT["SOURCE"] = "https://forge.chapril.org/Wantoo/UniSquat_Python"
|
||||
|
||||
# Le lien vers le site de l'organisation :
|
||||
GLOBAL_CONTEXT["CREDITSLINK"] = "https://forge.chapril.org/Wantoo"
|
||||
|
||||
# Le nom de l'organisation :
|
||||
GLOBAL_CONTEXT["CREDITSNAME"] = "Wantoo"
|
||||
|
||||
# Mode bavard :
|
||||
GLOBAL_CONTEXT["DEBUG"] = False
|
||||
|
||||
# Le domaine sur lequel est hébergée l'instance officielle :
|
||||
GLOBAL_CONTEXT["DOMAIN"] = "https://unisquat.alwaysdata.net"
|
||||
|
||||
# Timezone du serveur :
|
||||
TIMEZONE = pytz.timezone("Europe/Paris")
|
||||
|
||||
# Globales :
|
||||
app = Flask(__name__)
|
||||
logs = [] # Stocke les différentes requêtes faite sur la route /free_rooms/, sous la forme {"timestamp":timestamp,"depts":[]}
|
||||
|
||||
# Stocke les différentes requêtes faites sur /free_rooms/
|
||||
# sous la forme {"timestamp":timestamp,"depts":[]} :
|
||||
logs = []
|
||||
if os.path.isfile(LOG_FILE):
|
||||
with open(LOG_FILE,"r") as f:
|
||||
logs = json.loads(f.read())
|
||||
@ -79,6 +108,7 @@ def home() :
|
||||
Returns
|
||||
-------
|
||||
flask.render_template
|
||||
|
||||
"""
|
||||
return render_template("index.html", **GLOBAL_CONTEXT)
|
||||
|
||||
@ -95,6 +125,7 @@ def select_dept() :
|
||||
Returns
|
||||
-------
|
||||
flask.render_template
|
||||
|
||||
"""
|
||||
dept_filen = "data/dept_list.txt"
|
||||
|
||||
@ -108,52 +139,63 @@ def select_dept() :
|
||||
@app.route("/stats")
|
||||
def stats():
|
||||
"""
|
||||
Statistiques d'utilisation de l'instance
|
||||
Statistiques d'utilisation de l'instance :
|
||||
Salles marquées comme favorites
|
||||
Départements les plus cherchés
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None (utilise la variable globale 'log')
|
||||
None
|
||||
|
||||
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
|
||||
|
||||
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"] :
|
||||
fav_map = fav[0] + ";" + fav[1]
|
||||
if fav_map in counts_favs.keys() :
|
||||
counts_favs[fav_map][2] += 1
|
||||
else:
|
||||
fav.append(1)
|
||||
counts_favs[fmap(fav)]=fav
|
||||
counts_favs[fav_map] = 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 = [[x, counts[x]] for x in counts.keys()]
|
||||
|
||||
sort_favs = [ counts_favs[x] for x in counts_favs.keys() ]
|
||||
sort_favs.sort(key = lambda x: x[2],reverse = True )
|
||||
# Tri selon la valeur du deuxieme élément de la liste :
|
||||
sort.sort(key = lambda x: x[1], 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)
|
||||
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(api = False, rq = None) :
|
||||
"""
|
||||
Affiche les salles libres dans les départements sélectionnés
|
||||
dans la page des départements.
|
||||
Affiche les salles libres dans les départements sélectionnés.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@ -166,6 +208,7 @@ def free_rooms(api = False, rq = None) :
|
||||
Returns
|
||||
-------
|
||||
flask.render_template
|
||||
|
||||
"""
|
||||
if not api :
|
||||
rq = request
|
||||
@ -174,36 +217,34 @@ def free_rooms(api = False, rq = None) :
|
||||
print(f"dept:\n\t{rq.args.getlist('dept')}")
|
||||
print(f"favs:\n\t{rq.args.getlist('favs')}")
|
||||
print(f"date:\n\t{rq.args.get('date')}\t{rq.args.get('time')}")
|
||||
|
||||
# Récupération des ID des départements depuis le formulaire :
|
||||
dident_list = list(rq.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.")
|
||||
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 !")
|
||||
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) :
|
||||
# Récupération de l'éventuelle date personnalisée :
|
||||
date_uf = str(rq.args.get("date"))
|
||||
date_uf_sav = date_uf
|
||||
if date_uf == "None" :
|
||||
date_uf = [""]
|
||||
else :
|
||||
date_uf = date_uf.split("-")
|
||||
|
||||
date_uf = date_tools.d_t_format(date_uf, False)
|
||||
|
||||
if date_uf != [""] and not (date_tools.check_date(date_uf)) :
|
||||
return render_template("error.html", error="Date incorrecte !")
|
||||
|
||||
time_uf = str(rq.args.get("time"))
|
||||
time_uf_sav = time_uf
|
||||
if time_uf == "None" :
|
||||
time_uf = [""]
|
||||
else :
|
||||
time_uf = time_uf.split(":")
|
||||
|
||||
time_uf = date_tools.d_t_format(time_uf, True)
|
||||
|
||||
if time_uf != [""] and not (date_tools.check_time(time_uf)) :
|
||||
return render_template("error.html", error="Heure incorrecte !")
|
||||
|
||||
|
||||
date = dti.datetime.now()
|
||||
date = dti.datetime.now(TIMEZONE)
|
||||
|
||||
date_str = "" # Date affichée sur la page (si personnalisée)
|
||||
|
||||
@ -215,25 +256,27 @@ def free_rooms(api = False, rq = None) :
|
||||
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(rq.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 :
|
||||
# Vérification qu'il n'y a pas de mauvais départements sélectionnés :
|
||||
for d in dident_list :
|
||||
try :
|
||||
int(d)
|
||||
except:
|
||||
return render_template("error.html", error="Identifiant de département invalide !", **GLOBAL_CONTEXT)
|
||||
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)
|
||||
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 :
|
||||
@ -253,8 +296,11 @@ def free_rooms(api = False, rq = None) :
|
||||
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__))))
|
||||
errdetails = str(''.join(traceback.format_exception(None, err, err.__traceback__)))
|
||||
if GLOBAL_CONTEXT["DEBUG"] :
|
||||
print(errdetails)
|
||||
return render_template("error.html",
|
||||
error="Le serveur Unistra a rencontré une erreur ! Veuillez réessayer plus tard.")
|
||||
|
||||
# Création d'un dictionnaire avec les infos des salles :
|
||||
frooms_disp = dict() # Mise en forme des infos pour la page Web
|
||||
@ -324,7 +370,7 @@ def free_rooms(api = False, rq = None) :
|
||||
"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) :
|
||||
ctimestamp = dti.datetime.now().timestamp()
|
||||
ctimestamp = dti.datetime.now(TIMEZONE).timestamp()
|
||||
log = {}
|
||||
log["timestamp"] = ctimestamp
|
||||
log["depts"] = [ x.name for x in depts ] # Liste les noms de départements
|
||||
@ -366,7 +412,12 @@ def free_rooms(api = False, rq = None) :
|
||||
return response
|
||||
else :
|
||||
url_for("static", filename="style.css")
|
||||
return render_template("free-rooms.html", **context, **GLOBAL_CONTEXT)
|
||||
# Vérifie si le mode billboard est demandé :
|
||||
billboard = str(rq.args.get("billboard"))
|
||||
if billboard in ("1", "true", "True") :
|
||||
return render_template("billboard.html", **context, **GLOBAL_CONTEXT)
|
||||
else:
|
||||
return render_template("free-rooms.html", **context, **GLOBAL_CONTEXT)
|
||||
|
||||
@app.route("/app/date-select", methods=["POST", "GET"])
|
||||
def date_select() :
|
||||
@ -381,6 +432,7 @@ def date_select() :
|
||||
Returns
|
||||
-------
|
||||
flask.render_template
|
||||
|
||||
"""
|
||||
dident_list = list(request.args.getlist("dept"))
|
||||
favs_ids = list(request.args.getlist("favs"))
|
||||
@ -434,14 +486,17 @@ def sitemap():
|
||||
@app.errorhandler(404)
|
||||
def error(e):
|
||||
"""
|
||||
Affiche la page d'erreur
|
||||
Affiche la page d'erreur.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None.
|
||||
None
|
||||
|
||||
Returns
|
||||
-------
|
||||
flask.render_template
|
||||
|
||||
"""
|
||||
return render_template("error.html", error="Page non trouvée !", **GLOBAL_CONTEXT)
|
||||
template = render_template("error.html", error="Page non trouvée !", **GLOBAL_CONTEXT)
|
||||
response = make_response(template, 404)
|
||||
return response
|
||||
|
@ -10,21 +10,15 @@ Created on Thu Feb 24 16:36:32 2022
|
||||
### UniSquat ###
|
||||
################
|
||||
|
||||
"""
|
||||
Une application pour afficher les salles libres dans les différents
|
||||
départements de l'Université de Strasbourg.
|
||||
"""
|
||||
|
||||
|
||||
### Fichier contenant diverses fonctions relatives à la date du jour et à l'heure ##
|
||||
# Une application pour afficher les salles libres dans les différents
|
||||
# départements de l'Université de Strasbourg.
|
||||
|
||||
### Fichier contenant diverses fonctions relatives à la date du jour et à l'heure ###
|
||||
|
||||
# Modules :
|
||||
import datetime
|
||||
|
||||
|
||||
# Fonctions :
|
||||
|
||||
def minutes_convert(time_min) :
|
||||
"""
|
||||
Convertit un temps en minute en un temps en heures:minutes.
|
||||
@ -44,7 +38,6 @@ def minutes_convert(time_min) :
|
||||
|
||||
def bissextile(year) :
|
||||
"""
|
||||
|
||||
Indique si l'année 'year' est bissextile ou non.
|
||||
|
||||
Parameters
|
||||
@ -81,7 +74,7 @@ def month_days(month, year) :
|
||||
return 29
|
||||
else :
|
||||
return 28
|
||||
elif month in (4,6,9,11) :
|
||||
elif month in (4, 6, 9, 11) :
|
||||
return 30
|
||||
else :
|
||||
return 31
|
||||
@ -251,4 +244,30 @@ def check_time(time) :
|
||||
if minute > 59 :
|
||||
return False
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
def d_t_format(d_t, t_mode) :
|
||||
"""
|
||||
Formate une date ou une heure récupérée depuis une URL
|
||||
sous la forme d'une liste.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
d_t : str
|
||||
Date ou heure à formater.
|
||||
|
||||
t_mode : bool
|
||||
Indique si on est en mode heure.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Date formatée en liste avec jour, mois, année.
|
||||
|
||||
"""
|
||||
sep = ":" if t_mode else "-"
|
||||
|
||||
if d_t == "None" :
|
||||
return [""]
|
||||
else :
|
||||
return d_t.split(sep)
|
@ -1,7 +0,0 @@
|
||||
data
|
||||
static
|
||||
templates
|
||||
app.py
|
||||
date_tools.py
|
||||
objects.py
|
||||
rooms_get.py
|
64
objects.py
64
objects.py
@ -10,23 +10,23 @@ Created on Sat May 7 17:29:11 2022
|
||||
### UniSquat ###
|
||||
################
|
||||
|
||||
"""
|
||||
Une application pour afficher les salles libres dans les différents
|
||||
départements de l'Université de Strasbourg.
|
||||
"""
|
||||
# Une application pour afficher les salles libres dans les différents
|
||||
# départements de l'Université de Strasbourg.
|
||||
|
||||
### Définition des objets ###
|
||||
|
||||
# Modules
|
||||
import random # Nécessaire pour la génération d'ID des salles
|
||||
# Modules :
|
||||
import random
|
||||
|
||||
# Constantes
|
||||
ID_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" # Caractères disponibles pour la création d'ID
|
||||
ID_LEN = 4 # Nombres de caractères composant l'ID
|
||||
# Constantes :
|
||||
# Caractères disponibles pour la création d'ID :
|
||||
ID_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
# Nombres de caractères composant l'ID :
|
||||
ID_LEN = 4
|
||||
|
||||
|
||||
# Objets :
|
||||
|
||||
class Room :
|
||||
"""
|
||||
Structure des salles.
|
||||
@ -35,43 +35,57 @@ class Room :
|
||||
----------
|
||||
name : string
|
||||
Le nom de la salle.
|
||||
|
||||
|
||||
start : datetime.datetime
|
||||
Salle occupée : heure de début de la prochaine période de disponibilité.
|
||||
Salle libre : inutilisé (vaut la date du jour à 00:00).
|
||||
|
||||
|
||||
end : datetime.datetime
|
||||
Salle occupée : heure de fin de la prochaine période de disponibilité.
|
||||
Salle libre : heure de fin de disponibilité.
|
||||
|
||||
Salle libre : heure de fin de disponibilité (inutilisé s'il n'y en a
|
||||
pas (vaut alors la date du jour à 23:59:59)).
|
||||
|
||||
nostart : bool
|
||||
Indique si la salle a un début de disponibilité ('False' dans le cas
|
||||
des salles libres).
|
||||
|
||||
noend : bool
|
||||
Indique si la salle a une fin de disponibilité ('False' dans le cas
|
||||
où la salle est dispo pour le reste de la journée).
|
||||
|
||||
is_free : bool
|
||||
Indique si la salle est libre ('True') ou non ('False').
|
||||
|
||||
id : string
|
||||
Identifiant 'unique' (avec un très faible risque de conflit) de la salle (généré à partir de son nom)
|
||||
Identifiant 'unique' de la salle (généré à partir de son nom).
|
||||
(Cet identifiant a environ une chance sur 15 millions d'être unique).
|
||||
|
||||
dept_name : string
|
||||
Le nom du département auquel la salle appartient
|
||||
Le nom du département auquel la salle appartient.
|
||||
|
||||
"""
|
||||
def __init__(self, name, start, end, is_free, dept_name="DEFAULT DEPT") :
|
||||
def __init__(self, name, start, end, nostart, noend, is_free,
|
||||
dept_name="DEFAULT DEPT") :
|
||||
self.name = name
|
||||
self.start = start
|
||||
self.end = end
|
||||
self.nostart = nostart
|
||||
self.noend = noend
|
||||
self.is_free = is_free
|
||||
self.id = self.getId(name)
|
||||
self.dept_name = dept_name
|
||||
|
||||
def getId(self,name):
|
||||
def getId(self, name) :
|
||||
random.seed(name)
|
||||
id = ""
|
||||
for i in range(ID_LEN) :
|
||||
id += random.choice(ID_CHARS)
|
||||
return id # A peu près une chance sur 15 millions d'être unique, je considère ça viable
|
||||
return id
|
||||
|
||||
|
||||
class Dept :
|
||||
"""
|
||||
Classe des départements.
|
||||
Structure des départements.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@ -80,12 +94,12 @@ class Dept :
|
||||
|
||||
name : string
|
||||
Nom du département.
|
||||
|
||||
|
||||
link : string
|
||||
Lien qui permet d'accéder au fichier iCal du département.
|
||||
|
||||
|
||||
rooms : list
|
||||
La liste des salles de ce département
|
||||
La liste des salles de ce département.
|
||||
"""
|
||||
def __init__(self, ident, name, link, rooms) :
|
||||
self.ident = ident
|
||||
@ -93,5 +107,5 @@ class Dept :
|
||||
self.link = self.genlink(link)
|
||||
self.rooms = rooms
|
||||
|
||||
def genlink(self,link):
|
||||
return "https://adecons.unistra.fr/jsp/custom/modules/plannings/anonymous_cal.jsp?resources="+link.strip()+"&projectId=1&calType=ical"
|
||||
def genlink(self, link) :
|
||||
return "https://adecons.unistra.fr/jsp/custom/modules/plannings/anonymous_cal.jsp?resources="+link.strip()+"&projectId=1&calType=ical"
|
@ -13,3 +13,5 @@ requests==2.28.0
|
||||
six==1.16.0
|
||||
urllib3==1.26.9
|
||||
Werkzeug==2.1.2
|
||||
ics==0.7.2
|
||||
TatSu==5.11.3
|
||||
|
359
rooms_get.py
359
rooms_get.py
@ -9,18 +9,15 @@ Created on Thu Feb 24 08:51:58 2022
|
||||
### UniSquat ###
|
||||
################
|
||||
|
||||
"""
|
||||
Une application pour afficher les salles libres dans les différents
|
||||
départements de l'Université de Strasbourg.
|
||||
"""
|
||||
|
||||
# Une application pour afficher les salles libres dans les différents
|
||||
# départements de l'Université de Strasbourg.
|
||||
|
||||
### Fichier du backend (récupération des salles libres et des départements ###
|
||||
|
||||
|
||||
# Modules :
|
||||
import requests
|
||||
import icalendar
|
||||
import ics
|
||||
import pytz
|
||||
import os
|
||||
import shutil
|
||||
@ -32,15 +29,24 @@ from objects import Dept
|
||||
|
||||
# Constantes :
|
||||
CACHE_DIR = "cache"
|
||||
CACHE_TTL = 5 # Intervalle de temps entre les réinitialisations du cache, en minutes
|
||||
CACHE_SIZE = 10 # Nombres maximum de fichier dans le cache
|
||||
|
||||
# Globales
|
||||
# Délai de réinitialisation du cache, en minutes :
|
||||
CACHE_TTL = 5
|
||||
|
||||
# Nombres maximum de fichier dans le cache :
|
||||
CACHE_SIZE = 10
|
||||
|
||||
# Flag pour utiliser le cache :
|
||||
NO_CACHE = False
|
||||
|
||||
# Fuseau horaire :
|
||||
TIMEZONE = "Europe/Paris"
|
||||
|
||||
# Globales :
|
||||
last_cache_init = -999
|
||||
|
||||
# Fonctions :
|
||||
def reinit_cache() :
|
||||
global last_cache_init
|
||||
"""
|
||||
Vide le dossier CACHE_DIR et l'initialise.
|
||||
Modifie la variable globale 'last_cache_init'.
|
||||
@ -48,12 +54,14 @@ def reinit_cache() :
|
||||
Parameters
|
||||
----------
|
||||
None
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
|
||||
"""
|
||||
if os.path.isdir(CACHE_DIR):
|
||||
global last_cache_init
|
||||
if os.path.isdir(CACHE_DIR) :
|
||||
shutil.rmtree(CACHE_DIR)
|
||||
os.mkdir(CACHE_DIR)
|
||||
last_cache_init = time.time()
|
||||
@ -67,24 +75,25 @@ def trim(link) :
|
||||
Parameters
|
||||
----------
|
||||
link: str
|
||||
Le lien à simplifier
|
||||
|
||||
Le lien à simplifier.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
La chaine de caractères correspondante
|
||||
La chaine de caractères correspondante.
|
||||
|
||||
"""
|
||||
result = ""
|
||||
for i in link.lower() :
|
||||
if i not in "/:;\\'\" *?":
|
||||
result+=i
|
||||
result += i
|
||||
|
||||
return result
|
||||
|
||||
def sched_get(date, link, enddate = None, nocache = False) :
|
||||
def sched_get(date, link, enddate = None, nocache = False, sortcal = False) :
|
||||
"""
|
||||
Récupère l'emploi du temps de toutes les salles (pour le moment, juste
|
||||
de l'UFR) sur ADE depuis le site de l'Unistra.
|
||||
Récupère l'emploi du temps de toutes les salles sur ADE
|
||||
depuis le site de l'Unistra.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@ -93,27 +102,30 @@ def sched_get(date, link, enddate = None, nocache = False) :
|
||||
'enddate' est indiquée).
|
||||
|
||||
link:
|
||||
Un lien vers lequel effectuer la recherche, des informations seront remplacées :
|
||||
$YEAR$ : l'année
|
||||
$MONTH$ : le mois
|
||||
$DAY$ : le jour
|
||||
Par défaut, sera un lien des salles de l'UFR.
|
||||
|
||||
Un lien vers lequel effectuer la recherche :
|
||||
$YEAR$ : l'année
|
||||
$MONTH$ : le mois
|
||||
$DAY$ : le jour
|
||||
|
||||
Optional :
|
||||
enddate : datetime.datetime()
|
||||
Date de fin du calendrier à télécharger (par défaut, il s'agit de
|
||||
la date de début).
|
||||
|
||||
nocache : booléen
|
||||
Si mit à 'True', ne modifie pas, ni ne lit, le dossier CACHE_DIR
|
||||
|
||||
nocache : bool
|
||||
Si mis à 'True', ne lit et ne modifie pas le dossier CACHE_DIR.
|
||||
|
||||
sortcal : bool
|
||||
Si 'True', trie le calendrier par ordre chronologique.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bytes
|
||||
str
|
||||
Le texte du résultat de la requête.
|
||||
|
||||
"""
|
||||
link += "&firstDate=$YEAR1$-$MONTH1$-$DAY1$&lastDate=$YEAR2$-$MONTH2$-$DAY2$"
|
||||
|
||||
|
||||
day1 = str(date.day)
|
||||
month1 = str(date.month)
|
||||
year1 = str(date.year)
|
||||
@ -136,27 +148,41 @@ def sched_get(date, link, enddate = None, nocache = False) :
|
||||
finallink = finallink.replace("$MONTH2$", month1)
|
||||
finallink = finallink.replace("$YEAR2$", year1)
|
||||
|
||||
if nocache:
|
||||
return requests.get(finallink).content
|
||||
if nocache :
|
||||
result = requests.get(finallink).text
|
||||
if sortcal :
|
||||
# Utilisation du module 'ics' pour le tri du calendrier dans l'ordre
|
||||
# chronologique :
|
||||
cal = ics.Calendar(result)
|
||||
cal.events = sorted(cal.events)
|
||||
result = cal.serialize()
|
||||
return result
|
||||
else :
|
||||
# Vérifie la TTL
|
||||
# Vérifie la TTL :
|
||||
elapsed = time.time() - last_cache_init
|
||||
if elapsed>CACHE_TTL*60:
|
||||
reinit_cache()
|
||||
# Vérifie que le nombre total de fichiers dans le cache n'est pas dépassé
|
||||
if len(os.listdir(CACHE_DIR))>CACHE_SIZE:
|
||||
# Vérifie que le nombre total de fichiers dans le cache
|
||||
# n'est pas dépassé :
|
||||
if len(os.listdir(CACHE_DIR)) > CACHE_SIZE:
|
||||
reinit_cache()
|
||||
|
||||
# Vérifie que le lien est dans le cache
|
||||
cachepath = os.path.join(CACHE_DIR,trim(finallink))
|
||||
# Vérifie que le lien est dans le cache :
|
||||
cachepath = os.path.join(CACHE_DIR, trim(finallink))
|
||||
if os.path.isfile(cachepath) :
|
||||
result = ""
|
||||
with open(cachepath,'rb') as f :
|
||||
with open(cachepath,'r') as f :
|
||||
result = f.read()
|
||||
return result
|
||||
else:
|
||||
result = requests.get(finallink).content
|
||||
with open(cachepath,'wb') as f :
|
||||
else :
|
||||
result = requests.get(finallink).text
|
||||
if sortcal :
|
||||
# Utilisation du module 'ics' pour le tri du calendrier dans l'ordre
|
||||
# chronologique :
|
||||
cal = ics.Calendar(result)
|
||||
cal.events = sorted(cal.events)
|
||||
result = cal.serialize()
|
||||
with open(cachepath,'w') as f :
|
||||
f.write(result)
|
||||
return result
|
||||
|
||||
@ -168,34 +194,28 @@ def get_depts(filename) :
|
||||
Parameters
|
||||
----------
|
||||
filename : str
|
||||
Nom du fichier contenant les départements, et les liens
|
||||
permettant d'accéder au fichier iCal des salles du département.
|
||||
Nom du fichier contenant les départements, et les IDs ADE de ceux-ci.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dept_list : list
|
||||
Liste des départements.
|
||||
|
||||
"""
|
||||
dept_list = list()
|
||||
|
||||
dept_file = open(filename, "r")
|
||||
|
||||
i = 0
|
||||
|
||||
# dept = Dept("", "", [])
|
||||
|
||||
dfile_content = dept_file.readlines()
|
||||
|
||||
ident = 0 # Compteur pour les identifiants des départements
|
||||
|
||||
|
||||
for i in range(0, len(dfile_content) - 1, 2) :
|
||||
dept_list.append(Dept(ident, dfile_content[i], dfile_content[i + 1], []))
|
||||
ident += 1
|
||||
|
||||
dept_file.close()
|
||||
|
||||
return dept_list
|
||||
|
||||
dept_file.close()
|
||||
|
||||
return dept_list
|
||||
|
||||
def get_tot_rooms(datet, depts, ignore_list) :
|
||||
"""
|
||||
@ -218,67 +238,90 @@ def get_tot_rooms(datet, depts, ignore_list) :
|
||||
Liste des salles.
|
||||
"""
|
||||
total_rooms = list()
|
||||
|
||||
margintime = 1 # Marge de temps (en mois) pour le début du calendrier (il se peut que des salles existent et soient dispos, mais qu'elles ne sont pas affichées dans l'EDT du jour choisi, donc on prend l'EDT du mois)
|
||||
|
||||
cal_start = datet
|
||||
|
||||
if cal_start.month == 1 :
|
||||
cal_start.replace(year = cal_start.year - 1)
|
||||
cal_start.replace(month = 12)
|
||||
else :
|
||||
cal_start.replace(month = cal_start.month - margintime)
|
||||
|
||||
# Récupération des calendriers correspondants aux liens des départements, sur une période de 'margintime' mois :
|
||||
cals = list() # Liste des emplois du temps des départements choisis
|
||||
|
||||
# Marge de temps (en mois) pour le début du calendrier.
|
||||
# Certaines salles ne sont pas utilisées tous les jours,
|
||||
# donc on télécharge l'EDT pour 30 jours.
|
||||
margintime = 1
|
||||
|
||||
# Récupération du calendrier de chaque département,
|
||||
# sur une période de 'margintime' mois :
|
||||
cals = list() # Liste des EDT des départements choisis
|
||||
for d in depts :
|
||||
if datet.month < 12 :
|
||||
result = sched_get(datet, d.link, datet.replace(month = datet.month + margintime))
|
||||
result = sched_get(datet, d.link,
|
||||
datet.replace(month = datet.month + margintime),
|
||||
NO_CACHE)
|
||||
else :
|
||||
result = sched_get(datet, d.link, datet.replace(month = 1, year = datet.year + 1))
|
||||
result = sched_get(datet, d.link,
|
||||
datet.replace(month = 1, year = datet.year + 1),
|
||||
NO_CACHE)
|
||||
# # Utilisation du module 'ics' pour le tri du calendrier dans l'ordre
|
||||
# # chronologique :
|
||||
# cal = ics.Calendar(result)
|
||||
# cal.events = sorted(cal.events)
|
||||
cals.append(icalendar.Calendar.from_ical(result))
|
||||
|
||||
roomnames = [] # Contient le nom de toutes les salles indiquées dans la section "LOCATION"
|
||||
# cals.append(icalendar.Calendar(result.decode("utf-8")))
|
||||
|
||||
# Parcours de ces calendriers, pour faire la liste de toutes les salles :
|
||||
# Parcours de ces calendriers :
|
||||
dept_index = 0
|
||||
for cal in cals :
|
||||
for comp in cal.walk() : # Événements
|
||||
if comp.name == "VEVENT" :
|
||||
# Ajout de la salle dans le dictionnaire, si elle n'y est pas :
|
||||
roomname = str(comp.get("location"))
|
||||
for comp in cal.walk() : # Composants du calendrier
|
||||
if comp.name == "VEVENT" : # Événements
|
||||
# Récupération de l'emplacement :
|
||||
evtloc = str(comp.get("location"))
|
||||
|
||||
rnamelist = list() # Contient le nom de toutes les salles indiquées dans la section "LOCATION" (il peut y en avoir plusieurs, séparées par des virgules)
|
||||
# Contient le nom de toutes les salles indiquées
|
||||
# dans la section "LOCATION" :
|
||||
rnamelist = list()
|
||||
|
||||
if "," in roomname :
|
||||
rnamelist = roomname.split(",")
|
||||
# Séparation des salles multiples, le cas échéant :
|
||||
if "," in evtloc :
|
||||
rnamelist = evtloc.split(",")
|
||||
else :
|
||||
rnamelist.append(roomname)
|
||||
rnamelist.append(evtloc)
|
||||
|
||||
for rname in rnamelist :
|
||||
rname = rname.strip()
|
||||
if (rname not in roomnames) and (rname not in ignore_list) :
|
||||
roomnames.append(rname)
|
||||
for roomname in rnamelist :
|
||||
roomname = roomname.strip()
|
||||
if roomname not in ignore_list :
|
||||
# Création d'une nouvelle salle
|
||||
# si elle n'existe pas déjà :
|
||||
exists = False
|
||||
for room in total_rooms :
|
||||
if room.name == roomname :
|
||||
exists = True
|
||||
r = room
|
||||
|
||||
start = datet.replace(hour = 0, minute = 0, second=0) # Par défaut, l'heure de début de disponibilité est aujourd'hui à 00:00
|
||||
end = datet.replace(hour = 23, minute = 59, second = 59) # Par défaut, l'heure de fin de la prochaine période disponibilité est aujourd'hui à 23:59
|
||||
if not exists :
|
||||
# Par défaut :
|
||||
# - L'heure de début de disponibilité
|
||||
# est aujourd'hui à 00:00.
|
||||
# - L'heure de fin de la prochaine période
|
||||
# de disponibilité est aujourd'hui à 23:59.
|
||||
# - La salle est libre.
|
||||
r = Room(roomname,
|
||||
datet.replace(hour = 0, minute = 0, second = 0),
|
||||
datet.replace(hour = 23, minute = 59, second = 59),
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
depts[dept_index].name
|
||||
)
|
||||
|
||||
is_free = True # Par défaut, la salle est libre
|
||||
# Réglage du fuseau horaire :
|
||||
r.start = r.start.astimezone(pytz.timezone(TIMEZONE))
|
||||
r.end = r.end.astimezone(pytz.timezone(TIMEZONE))
|
||||
|
||||
# Réglage du fuseau horaire :
|
||||
start = start.astimezone(pytz.timezone('Europe/Paris'))
|
||||
end = end.astimezone(pytz.timezone('Europe/Paris'))
|
||||
total_rooms.append(r)
|
||||
|
||||
total_rooms.append(Room(rname, start, end, is_free, depts[dept_index].name))
|
||||
dept_index += 1
|
||||
|
||||
return total_rooms
|
||||
|
||||
|
||||
def getrooms(datet, depts, ignore_list) :
|
||||
"""
|
||||
Ajout des informations supplémentaires à la liste des salles
|
||||
(heures de début et fin de dispo, indicateur de dispo).
|
||||
Ajout des heures de début et fin de dispo aux salles,
|
||||
et indicateur de dispo.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@ -286,7 +329,7 @@ def getrooms(datet, depts, ignore_list) :
|
||||
Date pour la recherche de salles.
|
||||
|
||||
depts : list
|
||||
Liste des départements dans lesquel chercher des salles.
|
||||
Liste des départements dans lesquels chercher des salles.
|
||||
|
||||
ignore_list : list
|
||||
Liste des noms de salles à ignorer.
|
||||
@ -295,84 +338,82 @@ def getrooms(datet, depts, ignore_list) :
|
||||
-------
|
||||
total_rooms : list
|
||||
Liste des salles.
|
||||
|
||||
"""
|
||||
# Création de la liste de toutes les salles :
|
||||
total_rooms = get_tot_rooms(datet, depts, ignore_list)
|
||||
|
||||
# Récupération des calendriers correspondants au lien du département :
|
||||
cals = list() # Liste des emplois du temps des départements choisis
|
||||
|
||||
# Récupération du calendrier de chaque département :
|
||||
cals = list() # Liste des EDT des départements choisis
|
||||
for d in depts :
|
||||
result = sched_get(datet, d.link, datet)
|
||||
result = sched_get(datet, d.link, datet, NO_CACHE, True)
|
||||
cals.append(icalendar.Calendar.from_ical(result))
|
||||
|
||||
# Ajout des infos supplémentaires sur les salles (heures de début-fin de dispo, indicateur de dispo), s'il y en a :
|
||||
# cals.append(icalendar.Calendar(result.decode("utf-8")))
|
||||
|
||||
# Parcours de ces calendriers :
|
||||
dept_index = 0
|
||||
for cal in cals :
|
||||
# Première boucle, pour déterminer les salles occupées :
|
||||
for comp in cal.walk() : # Événements
|
||||
if comp.name == "VEVENT" :
|
||||
# Récupération des infos :
|
||||
datestart = comp.decoded("dtstart")
|
||||
dateend = comp.decoded("dtend")
|
||||
roomname = str(comp.get("location"))
|
||||
evtloc = str(comp.get("location"))
|
||||
evtstart = comp.decoded("dtstart")
|
||||
evtend = comp.decoded("dtend")
|
||||
|
||||
rnamelist = list() # Contient le nom de toutes les salles indiquées dans la section "LOCATION" (il peut y en avoir plusieurs, séparées par des virgules)
|
||||
# Contient le nom de toutes les salles indiquées
|
||||
# dans la section "LOCATION" :
|
||||
rnamelist = list()
|
||||
|
||||
if "," in roomname :
|
||||
rnamelist = roomname.split(",")
|
||||
# Séparation des salles multiples, le cas échéant :
|
||||
if "," in evtloc :
|
||||
rnamelist = evtloc.split(",")
|
||||
else :
|
||||
rnamelist.append(roomname)
|
||||
rnamelist.append(evtloc)
|
||||
|
||||
for rname in rnamelist :
|
||||
# L'événement se passe maintenant (salle occupée maintenant) :
|
||||
if datestart.timestamp() <= datet.timestamp() and dateend.timestamp() > datet.timestamp() :
|
||||
start = dateend # L'heure de début de la prochaine période de disponibilité est la fin de l'événement
|
||||
end = datet.replace(hour = 23, minute = 59, second = 59) # Par défaut, l'heure de fin de la prochaine période disponibilité est aujourd'hui à 23:59
|
||||
for roomname in rnamelist :
|
||||
roomname = roomname.strip()
|
||||
if roomname not in ignore_list :
|
||||
exists = False
|
||||
for room in total_rooms :
|
||||
if room.name == roomname :
|
||||
exists = True
|
||||
r = room
|
||||
|
||||
is_free = False
|
||||
|
||||
# Réglage du fuseau horaire :
|
||||
start = start.astimezone(pytz.timezone('Europe/Paris'))
|
||||
end = end.astimezone(pytz.timezone('Europe/Paris'))
|
||||
|
||||
for r in total_rooms :
|
||||
if r.name == rname :
|
||||
r.start = start
|
||||
r.end = end
|
||||
r.is_free = is_free
|
||||
|
||||
# Deuxième boucle, pour ajouter les heures de dispos des salles :
|
||||
for comp in cal.walk() : # Événements
|
||||
if comp.name == "VEVENT" :
|
||||
# Récupération des infos :
|
||||
datestart = comp.decoded("dtstart")
|
||||
dateend = comp.decoded("dtend")
|
||||
roomname = str(comp.get("location"))
|
||||
|
||||
rnamelist = list() # Contient le nom de toutes les salles indiquées dans la section "LOCATION" (il peut y en avoir plusieurs, séparées par des virgules)
|
||||
|
||||
if "," in roomname :
|
||||
rnamelist = roomname.split(",")
|
||||
else :
|
||||
rnamelist.append(roomname)
|
||||
|
||||
for rname in rnamelist :
|
||||
# L'événement se passe prochainement (salle occupée à l'occasion de cet événement) :
|
||||
if datestart.timestamp() > datet.timestamp() :
|
||||
for r in total_rooms :
|
||||
if r.name == roomname :
|
||||
if datestart.timestamp() < r.end.timestamp() :
|
||||
if not(r.is_free) and (datestart.timestamp() == r.start.timestamp()) :
|
||||
start = dateend
|
||||
end = r.end
|
||||
if exists :
|
||||
# Si l'événement se passe aujourd'hui :
|
||||
if evtstart.day == datet.day and \
|
||||
evtstart.month == datet.month and \
|
||||
evtstart.year == datet.year :
|
||||
# Si l'événement se passe maintenant
|
||||
# (salle occupée maintenant) :
|
||||
if evtstart.timestamp() <= datet.timestamp() and \
|
||||
evtend.timestamp() > datet.timestamp() :
|
||||
r.is_free = False
|
||||
r.nostart = False
|
||||
# L'heure de début de la prochaine dispo est
|
||||
# la fin de l'événement :
|
||||
r.start = evtend
|
||||
# Si l'événement se passe prochainement
|
||||
# (salle occupée à l'occasion de cet événement)
|
||||
# et que le début de l'événement est avant la date
|
||||
# de fin de dispo actuelle (on cherche la date la
|
||||
# plus proche de maintenant) :
|
||||
elif evtstart.timestamp() > datet.timestamp() and \
|
||||
evtstart.timestamp() < r.end.timestamp() :
|
||||
if evtstart.timestamp() == r.start.timestamp() :
|
||||
r.nostart = False
|
||||
# Dans ce cas, l'événement en cours suit
|
||||
# celui qui a défini le début de dispo
|
||||
# de la salle. Alors, c'est la fin de cet
|
||||
# événement qui marque le début de la dispo.
|
||||
r.start = evtend
|
||||
else :
|
||||
start = r.start
|
||||
end = datestart
|
||||
r.noend = False
|
||||
r.end = evtstart
|
||||
|
||||
# Réglage du fuseau horaire :
|
||||
start = start.astimezone(pytz.timezone('Europe/Paris'))
|
||||
end = end.astimezone(pytz.timezone('Europe/Paris'))
|
||||
# Réglage du fuseau horaire :
|
||||
r.start = r.start.astimezone(pytz.timezone(TIMEZONE))
|
||||
r.end = r.end.astimezone(pytz.timezone(TIMEZONE))
|
||||
|
||||
r.start = start
|
||||
r.end = end
|
||||
|
||||
return total_rooms
|
||||
dept_index += 1
|
||||
|
||||
return total_rooms
|
67
static/billboardstyle.css
Normal file
67
static/billboardstyle.css
Normal file
@ -0,0 +1,67 @@
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-dark: #303355;
|
||||
--fg: #303355;
|
||||
--special: #c09f80;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: "ubuntu", sans-serif;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
text-align: center;
|
||||
font-size: 6vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 5vh;
|
||||
font-weight: normal;
|
||||
margin-bottom: 8vh;
|
||||
}
|
||||
|
||||
.slider { overflow: hidden;
|
||||
}
|
||||
|
||||
.slide-track {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 3vw;
|
||||
animation: scroll 40s linear infinite;
|
||||
width: 3000vh; /* Pas bô, mais on va faire avec en attendant... */
|
||||
}
|
||||
|
||||
.room {
|
||||
text-align: center;
|
||||
background: var(--bg-dark);
|
||||
border-style: solid;
|
||||
border-width: 1vw;
|
||||
border-color: var(--bg-dark);
|
||||
border-radius: 3vw;
|
||||
padding: 1vw;
|
||||
height: 17vh;
|
||||
color: var(--bg);
|
||||
font-size: 8vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@keyframes scroll {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(calc(-25vw * 7)); }
|
||||
}
|
||||
|
||||
.details {
|
||||
margin: 1vh;
|
||||
font-size: 6vh;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 8vh;
|
||||
font-size: 3vh;
|
||||
text-align: center;
|
||||
}
|
@ -262,12 +262,12 @@ footer {
|
||||
display:flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.room-collumn{
|
||||
.room-column{
|
||||
width: 50%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.room-collumn{
|
||||
.room-column{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
50
templates/billboard.html
Normal file
50
templates/billboard.html
Normal file
@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>UniSquat</title>
|
||||
<link rel="stylesheet" type="text/css" href="../static/billboardstyle.css">
|
||||
<meta name="viewport" content="width=300, initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
<h1><b>Salles disponibles</b></h1>
|
||||
<h2>{{ depts_str }}</h2>
|
||||
<div class="slider">
|
||||
<div class="slide-track">
|
||||
{% if favs: %}
|
||||
<!-- Afficher les favoris -->
|
||||
<!-- Deux fois pour que l'animation boucle -->
|
||||
{% for i in range(2) : %}
|
||||
{% if favs_free_rooms|length > 0 : %}
|
||||
{% for room in favs_free_rooms : %}
|
||||
<div class="room">
|
||||
<b>{{ room.name }}</b> {% if DEBUG :%}( {{ room.id }} ){% endif %}
|
||||
{% if not(room.noend) : %}
|
||||
<p class=details>Jusqu'à {{ frooms_disp[room.name]["end"] }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<!-- Si les favoris ne sont pas définis, afficher les salles classiques -->
|
||||
{% for i in range(2):%}
|
||||
{% if free_rooms|length>0 %}
|
||||
{% for room in free_rooms: %}
|
||||
<div class="room">
|
||||
<b>{{ room.name }}</b> {% if DEBUG : %}( {{ room.id }} ){% endif %}
|
||||
{% if not room.noend : %}
|
||||
<p class=details>Jusqu'à {{ frooms_disp[room.name]["end"] }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
Propulsé par UniSquat : <a href="https://unisquat.alwaysdata.net">https://unisquat.alwaysdata.net</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
@ -34,8 +34,8 @@
|
||||
</div>
|
||||
{% if favs: %}
|
||||
<div class="flex-pc">
|
||||
{% if favs_free_rooms|length>0: %}
|
||||
<div class="room-collumn">
|
||||
{% if favs_free_rooms|length > 0 : %}
|
||||
<div class="room-column">
|
||||
<br>
|
||||
<h1>Favoris disponibles maintenant</h1>
|
||||
<div class="flex-container">
|
||||
@ -45,7 +45,7 @@
|
||||
<div class="room-row">
|
||||
<div>
|
||||
{{ room.name }} {% if DEBUG :%}( {{ room.id }} ){% endif %}
|
||||
{% if not(room.end.hour == 23 and room.end.minute == 59 and room.end.second == 59) : %}
|
||||
{% if not(room.noend) : %}
|
||||
<p class=details>Jusqu'à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -60,7 +60,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if favs_soon_rooms|length>0: %}
|
||||
<div class="room-collumn">
|
||||
<div class="room-column">
|
||||
<br>
|
||||
<h1>Favoris disponibles prochainement</h1>
|
||||
<div class="flex-container">
|
||||
@ -70,7 +70,7 @@
|
||||
<div class="room-row">
|
||||
<div>
|
||||
{{ room.name }} {% if DEBUG :%}( {{ room.id }} ){% endif %}
|
||||
{% if room.end.hour == 23 and room.end.minute == 59 and room.end.second == 59 : %}
|
||||
{% if room.noend : %}
|
||||
<p class=details>À {{ frooms_disp[room.name]["start"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||
{% else %}
|
||||
<p class=details>De {{ frooms_disp[room.name]["start"] }} à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||
@ -90,7 +90,7 @@
|
||||
{% endif %}
|
||||
<div class="flex-pc">
|
||||
{% if free_rooms|length>0 %}
|
||||
<div class="room-collumn">
|
||||
<div class="room-column">
|
||||
<br>
|
||||
<h1>Disponibles maintenant</h1>
|
||||
<div class="flex-container">
|
||||
@ -100,7 +100,7 @@
|
||||
<div class="room-row">
|
||||
<div>
|
||||
{{ room.name }} {% if DEBUG :%}( {{ room.id }} ){% endif %}
|
||||
{% if not(room.end.hour == 23 and room.end.minute == 59 and room.end.second == 59) : %}
|
||||
{% if not room.noend : %}
|
||||
<p class=details>Jusqu'à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -115,7 +115,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if soon_rooms|length>0 %}
|
||||
<div class="room-collumn">
|
||||
<div class="room-column">
|
||||
<br>
|
||||
<h1>Disponibles prochainement</h1>
|
||||
<div class="flex-container">
|
||||
@ -125,7 +125,7 @@
|
||||
<div class="room-row">
|
||||
<div>
|
||||
{{ room.name }} {% if DEBUG :%}( {{ room.id }} ){% endif %}
|
||||
{% if room.end.hour == 23 and room.end.minute == 59 and room.end.second == 59 : %}
|
||||
{% if room.noend : %}
|
||||
<p class=details>À {{ frooms_disp[room.name]["start"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||
{% else %}
|
||||
<p class=details>De {{ frooms_disp[room.name]["start"] }} à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||
|
Loading…
Reference in New Issue
Block a user