Début réécriture de la recheche de salles libres.

Config timezone global.
Écriture d'une fonction pour le formatage de la date affichée.
Corrections codestyle et commentaires.
This commit is contained in:
antux18 2023-09-22 22:28:19 -04:00
parent 9e01bfbfd1
commit 4d66319a29
4 changed files with 317 additions and 271 deletions

182
app.py
View File

@ -1,22 +1,20 @@
#!/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
import json
import os
import traceback
from flask import Flask
from flask import render_template
@ -29,22 +27,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 +107,7 @@ def home() :
Returns
-------
flask.render_template
"""
return render_template("index.html", **GLOBAL_CONTEXT)
@ -95,6 +124,7 @@ def select_dept() :
Returns
-------
flask.render_template
"""
dept_filen = "data/dept_list.txt"
@ -108,52 +138,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 +207,7 @@ def free_rooms(api = False, rq = None) :
Returns
-------
flask.render_template
"""
if not api :
rq = request
@ -174,36 +216,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 +255,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 :
@ -324,7 +366,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
@ -381,6 +423,7 @@ def date_select() :
Returns
-------
flask.render_template
"""
dident_list = list(request.args.getlist("dept"))
favs_ids = list(request.args.getlist("favs"))
@ -434,14 +477,15 @@ 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)
return render_template("error.html", error="Page non trouvée !", **GLOBAL_CONTEXT)

View File

@ -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)

View File

@ -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,23 +35,25 @@ 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é.
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") :
self.name = name
@ -61,17 +63,17 @@ class Room :
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 +82,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 +95,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"

View File

@ -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 as icalendar
import pytz
import os
import shutil
@ -32,15 +29,18 @@ 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
# 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 +48,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 +69,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) :
"""
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 +96,27 @@ 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
Si mis à 'True', ne lit et ne modifie pas le dossier CACHE_DIR.
Returns
-------
bytes
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,19 +139,20 @@ def sched_get(date, link, enddate = None, nocache = False) :
finallink = finallink.replace("$MONTH2$", month1)
finallink = finallink.replace("$YEAR2$", year1)
if nocache:
if nocache :
return requests.get(finallink).content
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 :
@ -168,117 +172,33 @@ 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
def get_tot_rooms(datet, depts, ignore_list) :
"""
Crée une liste de toutes les salles des départements choisis.
Parameters
----------
datet : datetime.datetime()
Date pour la recherche de salles.
depts : list
Liste des départements dans lesquels chercher des salles.
ignore_list : list
Liste des noms de salles à ignorer.
Returns
-------
total_rooms : 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
for d in depts :
if datet.month < 12 :
result = sched_get(datet, d.link, datet.replace(month = datet.month + margintime))
else :
result = sched_get(datet, d.link, datet.replace(month = 1, year = datet.year + 1))
cals.append(icalendar.Calendar.from_ical(result))
roomnames = [] # Contient le nom de toutes les salles indiquées dans la section "LOCATION"
# Parcours de ces calendriers, pour faire la liste de toutes les salles :
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"))
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 :
rname = rname.strip()
if (rname not in roomnames) and (rname not in ignore_list) :
roomnames.append(rname)
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
is_free = True # Par défaut, la salle est libre
# Réglage du fuseau horaire :
start = start.astimezone(pytz.timezone('Europe/Paris'))
end = end.astimezone(pytz.timezone('Europe/Paris'))
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).
Génération de la liste des salles avec heures de début et fin de dispo,
et indicateur de dispo.
Parameters
----------
@ -295,84 +215,145 @@ 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
total_rooms = list()
# 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 d'un mois complet pour être tranquille.
margintime = 1
# Récupération des calendriers correspondants aux liens des départements,
# sur une période de 'margintime' mois :
cals = list() # Liste des EDT des départements choisis
for d in depts :
result = sched_get(datet, d.link, datet)
if datet.month < 12 :
result = sched_get(datet, d.link,
datet.replace(month = datet.month + margintime))
else :
result = sched_get(datet, d.link,
datet.replace(month = 1, year = datet.year + 1))
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 :
# Première boucle pour la liste des salles + déterminer les occupées :
for comp in cal.walk() : # Événements
if comp.name == "VEVENT" :
# Récupération des infos :
roomname = str(comp.get("location"))
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)
# Contient le nom de toutes les salles indiquées
# dans la section "LOCATION" :
rnamelist = list()
# Séparation des salles multiples, le cas échéant :
if "," in roomname :
rnamelist = roomname.split(",")
else :
rnamelist.append(roomname)
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
rname = rname.strip()
if rname 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 == rname :
exists = True
r = room
is_free = False
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(rname,
datet.replace(hour = 0, minute = 0, second = 0),
datet.replace(hour = 23, minute = 59, second = 59),
True,
depts[dept_index].name
)
# Si l'événement se passe aujourd'hui :
if datestart.day == datet.day and \
datestart.month == datet.month and \
datestart.year == datet.year :
if rname == "Grand Amphi MAI Frenkel" :
print(r.start.hour, r.end.hour, datestart.hour+2, dateend.hour+2)
# Si l'événement se passe maintenant
# (salle occupée maintenant) :
if datestart.timestamp() <= datet.timestamp() and \
dateend.timestamp() > datet.timestamp() :
r.is_free = False
# L'heure de début de la prochaine dispo est
# la fin de l'événement,
# si différente de la fin de dispo :
r.start = dateend
# Réglage du fuseau horaire :
start = start.astimezone(pytz.timezone('Europe/Paris'))
end = end.astimezone(pytz.timezone('Europe/Paris'))
r.start = r.start.astimezone(pytz.timezone('Europe/Paris'))
r.end = r.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
if not exists :
total_rooms.append(r)
# Deuxième boucle, pour ajouter les heures de dispos des salles :
for comp in cal.walk() : # Événements
# Deuxième boucle, pour déterminer les périodes de dispo :
for comp in cal.walk() :
if comp.name == "VEVENT" :
# Récupération des infos :
roomname = str(comp.get("location"))
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)
# Contient le nom de toutes les salles indiquées
# dans la section "LOCATION" :
rnamelist = list()
# Séparation des salles multiples, le cas échéant :
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
else :
start = r.start
end = datestart
rname = rname.strip()
if rname not in ignore_list :
# Recherche de la salle :
for room in total_rooms :
if room.name == rname :
r = room
# Réglage du fuseau horaire :
start = start.astimezone(pytz.timezone('Europe/Paris'))
end = end.astimezone(pytz.timezone('Europe/Paris'))
# Si l'événement se passe aujourd'hui :
if datestart.day == datet.day and \
datestart.month == datet.month and \
datestart.year == datet.year :
if rname == "Grand Amphi MAI Frenkel" :
print(r.start.hour, r.end.hour, datestart.hour+2, dateend.hour+2)
# Si l'événement se passe prochainement
# (salle occupée à l'occasion de cet événement) :
if datestart.timestamp() > datet.timestamp() :
if datestart.timestamp() == r.start.timestamp() :
r.start = dateend
elif datestart.timestamp() < r.end.timestamp() :
r.end = datestart
r.start = start
r.end = end
return total_rooms
# Réglage du fuseau horaire :
r.start = r.start.astimezone(pytz.timezone('Europe/Paris'))
r.end = r.end.astimezone(pytz.timezone('Europe/Paris'))
dept_index += 1
return total_rooms