Réécriture complète du code pour la recherche des salles pour des résultats exacts.
Ajouts de propriétés à la classe Room. Ajout d'une constante pour l'utilisation du cache. Corrections mineures.
This commit is contained in:
parent
4d66319a29
commit
430e8938a2
8
app.py
8
app.py
@ -15,6 +15,7 @@ import datetime as dti
|
|||||||
import pytz
|
import pytz
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import traceback
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
@ -295,8 +296,11 @@ def free_rooms(api = False, rq = None) :
|
|||||||
try :
|
try :
|
||||||
free_rooms = ro.getrooms(date, depts, ignore_list)
|
free_rooms = ro.getrooms(date, depts, ignore_list)
|
||||||
except ValueError as err :
|
except ValueError as err :
|
||||||
return render_template("error.html", error="Le serveur Unistra a rencontré une erreur ! Veuillez réessayer plus tard.")
|
errdetails = str(''.join(traceback.format_exception(None, err, err.__traceback__)))
|
||||||
#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__))))
|
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 :
|
# Création d'un dictionnaire avec les infos des salles :
|
||||||
frooms_disp = dict() # Mise en forme des infos pour la page Web
|
frooms_disp = dict() # Mise en forme des infos pour la page Web
|
||||||
|
16
objects.py
16
objects.py
@ -42,7 +42,16 @@ class Room :
|
|||||||
|
|
||||||
end : datetime.datetime
|
end : datetime.datetime
|
||||||
Salle occupée : heure de fin de la prochaine période de disponibilité.
|
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
|
is_free : bool
|
||||||
Indique si la salle est libre ('True') ou non ('False').
|
Indique si la salle est libre ('True') ou non ('False').
|
||||||
@ -55,10 +64,13 @@ class Room :
|
|||||||
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.name = name
|
||||||
self.start = start
|
self.start = start
|
||||||
self.end = end
|
self.end = end
|
||||||
|
self.nostart = nostart
|
||||||
|
self.noend = noend
|
||||||
self.is_free = is_free
|
self.is_free = is_free
|
||||||
self.id = self.getId(name)
|
self.id = self.getId(name)
|
||||||
self.dept_name = dept_name
|
self.dept_name = dept_name
|
||||||
|
151
rooms_get.py
151
rooms_get.py
@ -17,7 +17,7 @@ Created on Thu Feb 24 08:51:58 2022
|
|||||||
# Modules :
|
# Modules :
|
||||||
import requests
|
import requests
|
||||||
import icalendar
|
import icalendar
|
||||||
# import ics as icalendar
|
import ics
|
||||||
import pytz
|
import pytz
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@ -36,6 +36,9 @@ CACHE_TTL = 5
|
|||||||
# Nombres maximum de fichier dans le cache :
|
# Nombres maximum de fichier dans le cache :
|
||||||
CACHE_SIZE = 10
|
CACHE_SIZE = 10
|
||||||
|
|
||||||
|
# Flag pour utiliser le cache :
|
||||||
|
NO_CACHE = False
|
||||||
|
|
||||||
# Globales :
|
# Globales :
|
||||||
last_cache_init = -999
|
last_cache_init = -999
|
||||||
|
|
||||||
@ -111,7 +114,7 @@ def sched_get(date, link, enddate = None, nocache = False) :
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
bytes
|
str
|
||||||
Le texte du résultat de la requête.
|
Le texte du résultat de la requête.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -140,7 +143,13 @@ def sched_get(date, link, enddate = None, nocache = False) :
|
|||||||
finallink = finallink.replace("$YEAR2$", year1)
|
finallink = finallink.replace("$YEAR2$", year1)
|
||||||
|
|
||||||
if nocache :
|
if nocache :
|
||||||
return requests.get(finallink).content
|
# Utilisation du module 'ics' pour le tri du calendrier dans l'ordre
|
||||||
|
# chronologique :
|
||||||
|
result = requests.get(finallink).text
|
||||||
|
cal = ics.Calendar(result)
|
||||||
|
cal.events = sorted(cal.events)
|
||||||
|
result = cal.serialize()
|
||||||
|
return result
|
||||||
else :
|
else :
|
||||||
# Vérifie la TTL :
|
# Vérifie la TTL :
|
||||||
elapsed = time.time() - last_cache_init
|
elapsed = time.time() - last_cache_init
|
||||||
@ -155,12 +164,17 @@ def sched_get(date, link, enddate = None, nocache = False) :
|
|||||||
cachepath = os.path.join(CACHE_DIR, trim(finallink))
|
cachepath = os.path.join(CACHE_DIR, trim(finallink))
|
||||||
if os.path.isfile(cachepath) :
|
if os.path.isfile(cachepath) :
|
||||||
result = ""
|
result = ""
|
||||||
with open(cachepath,'rb') as f :
|
with open(cachepath,'r') as f :
|
||||||
result = f.read()
|
result = f.read()
|
||||||
return result
|
return result
|
||||||
else:
|
else :
|
||||||
result = requests.get(finallink).content
|
result = requests.get(finallink).text
|
||||||
with open(cachepath,'wb') as f :
|
# 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)
|
f.write(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -221,52 +235,57 @@ def getrooms(datet, depts, ignore_list) :
|
|||||||
|
|
||||||
# Marge de temps (en mois) pour le début du calendrier.
|
# Marge de temps (en mois) pour le début du calendrier.
|
||||||
# Certaines salles ne sont pas utilisées tous les jours,
|
# Certaines salles ne sont pas utilisées tous les jours,
|
||||||
# donc on télécharge l'EDT d'un mois complet pour être tranquille.
|
# donc on télécharge l'EDT du reste du mois et du suivant
|
||||||
|
# pour être tranquille.
|
||||||
margintime = 1
|
margintime = 1
|
||||||
|
|
||||||
# Récupération des calendriers correspondants aux liens des départements,
|
# Récupération du calendrier de chaque département,
|
||||||
# sur une période de 'margintime' mois :
|
# sur une période de 'margintime' mois :
|
||||||
cals = list() # Liste des EDT des départements choisis
|
cals = list() # Liste des EDT des départements choisis
|
||||||
for d in depts :
|
for d in depts :
|
||||||
if datet.month < 12 :
|
if datet.month < 12 :
|
||||||
result = sched_get(datet, d.link,
|
result = sched_get(datet, d.link,
|
||||||
datet.replace(month = datet.month + margintime))
|
datet.replace(month = datet.month + margintime),
|
||||||
|
NO_CACHE)
|
||||||
else :
|
else :
|
||||||
result = sched_get(datet, d.link,
|
result = sched_get(datet, d.link,
|
||||||
datet.replace(month = 1, year = datet.year + 1))
|
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))
|
cals.append(icalendar.Calendar.from_ical(result))
|
||||||
# cals.append(icalendar.Calendar(result.decode("utf-8")))
|
# cals.append(icalendar.Calendar(result.decode("utf-8")))
|
||||||
|
|
||||||
# Parcours de ces calendriers :
|
# Parcours de ces calendriers :
|
||||||
dept_index = 0
|
dept_index = 0
|
||||||
for cal in cals :
|
for cal in cals :
|
||||||
# Première boucle pour la liste des salles + déterminer les occupées :
|
|
||||||
for comp in cal.walk() : # Événements
|
for comp in cal.walk() : # Événements
|
||||||
if comp.name == "VEVENT" :
|
if comp.name == "VEVENT" :
|
||||||
# Récupération des infos :
|
# Récupération des infos :
|
||||||
roomname = str(comp.get("location"))
|
evtloc = str(comp.get("location"))
|
||||||
datestart = comp.decoded("dtstart")
|
evtstart = comp.decoded("dtstart")
|
||||||
dateend = comp.decoded("dtend")
|
evtend = comp.decoded("dtend")
|
||||||
roomname = str(comp.get("location"))
|
|
||||||
|
|
||||||
# Contient le nom de toutes les salles indiquées
|
# Contient le nom de toutes les salles indiquées
|
||||||
# dans la section "LOCATION" :
|
# dans la section "LOCATION" :
|
||||||
rnamelist = list()
|
rnamelist = list()
|
||||||
|
|
||||||
# Séparation des salles multiples, le cas échéant :
|
# Séparation des salles multiples, le cas échéant :
|
||||||
if "," in roomname :
|
if "," in evtloc :
|
||||||
rnamelist = roomname.split(",")
|
rnamelist = evtloc.split(",")
|
||||||
else :
|
else :
|
||||||
rnamelist.append(roomname)
|
rnamelist.append(evtloc)
|
||||||
|
|
||||||
for rname in rnamelist :
|
for roomname in rnamelist :
|
||||||
rname = rname.strip()
|
roomname = roomname.strip()
|
||||||
if rname not in ignore_list :
|
if roomname not in ignore_list :
|
||||||
# Création d'une nouvelle salle
|
# Création d'une nouvelle salle
|
||||||
# si elle n'existe pas déjà :
|
# si elle n'existe pas déjà :
|
||||||
exists = False
|
exists = False
|
||||||
for room in total_rooms :
|
for room in total_rooms :
|
||||||
if room.name == rname :
|
if room.name == roomname :
|
||||||
exists = True
|
exists = True
|
||||||
r = room
|
r = room
|
||||||
|
|
||||||
@ -277,28 +296,45 @@ def getrooms(datet, depts, ignore_list) :
|
|||||||
# - L'heure de fin de la prochaine période
|
# - L'heure de fin de la prochaine période
|
||||||
# de disponibilité est aujourd'hui à 23:59.
|
# de disponibilité est aujourd'hui à 23:59.
|
||||||
# - La salle est libre.
|
# - La salle est libre.
|
||||||
r = Room(rname,
|
r = Room(roomname,
|
||||||
datet.replace(hour = 0, minute = 0, second = 0),
|
datet.replace(hour = 0, minute = 0, second = 0),
|
||||||
datet.replace(hour = 23, minute = 59, second = 59),
|
datet.replace(hour = 23, minute = 59, second = 59),
|
||||||
True,
|
True,
|
||||||
|
True,
|
||||||
|
True,
|
||||||
depts[dept_index].name
|
depts[dept_index].name
|
||||||
)
|
)
|
||||||
|
|
||||||
# Si l'événement se passe aujourd'hui :
|
# Si l'événement se passe aujourd'hui :
|
||||||
if datestart.day == datet.day and \
|
if evtstart.day == datet.day and \
|
||||||
datestart.month == datet.month and \
|
evtstart.month == datet.month and \
|
||||||
datestart.year == datet.year :
|
evtstart.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
|
# Si l'événement se passe maintenant
|
||||||
# (salle occupée maintenant) :
|
# (salle occupée maintenant) :
|
||||||
if datestart.timestamp() <= datet.timestamp() and \
|
if evtstart.timestamp() <= datet.timestamp() and \
|
||||||
dateend.timestamp() > datet.timestamp() :
|
evtend.timestamp() > datet.timestamp() :
|
||||||
r.is_free = False
|
r.is_free = False
|
||||||
|
r.nostart = False
|
||||||
# L'heure de début de la prochaine dispo est
|
# L'heure de début de la prochaine dispo est
|
||||||
# la fin de l'événement,
|
# la fin de l'événement :
|
||||||
# si différente de la fin de dispo :
|
r.start = evtend
|
||||||
r.start = dateend
|
# 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 :
|
||||||
|
r.noend = False
|
||||||
|
r.end = evtstart
|
||||||
|
|
||||||
# Réglage du fuseau horaire :
|
# Réglage du fuseau horaire :
|
||||||
r.start = r.start.astimezone(pytz.timezone('Europe/Paris'))
|
r.start = r.start.astimezone(pytz.timezone('Europe/Paris'))
|
||||||
@ -307,53 +343,6 @@ def getrooms(datet, depts, ignore_list) :
|
|||||||
if not exists :
|
if not exists :
|
||||||
total_rooms.append(r)
|
total_rooms.append(r)
|
||||||
|
|
||||||
# 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"))
|
|
||||||
|
|
||||||
# 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 :
|
|
||||||
rname = rname.strip()
|
|
||||||
if rname not in ignore_list :
|
|
||||||
# Recherche de la salle :
|
|
||||||
for room in total_rooms :
|
|
||||||
if room.name == rname :
|
|
||||||
r = room
|
|
||||||
|
|
||||||
# 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é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
|
dept_index += 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return total_rooms
|
return total_rooms
|
@ -262,12 +262,12 @@ footer {
|
|||||||
display:flex;
|
display:flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.room-collumn{
|
.room-column{
|
||||||
width: 50%;
|
width: 50%;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-collumn{
|
.room-column{
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
{% if favs: %}
|
{% if favs: %}
|
||||||
<div class="flex-pc">
|
<div class="flex-pc">
|
||||||
{% if favs_free_rooms|length>0: %}
|
{% if favs_free_rooms|length>0: %}
|
||||||
<div class="room-collumn">
|
<div class="room-column">
|
||||||
<br>
|
<br>
|
||||||
<h1>Favoris disponibles maintenant</h1>
|
<h1>Favoris disponibles maintenant</h1>
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
@ -45,7 +45,7 @@
|
|||||||
<div class="room-row">
|
<div class="room-row">
|
||||||
<div>
|
<div>
|
||||||
{{ room.name }} {% if DEBUG :%}( {{ room.id }} ){% endif %}
|
{{ 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>
|
<p class=details>Jusqu'à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -60,7 +60,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if favs_soon_rooms|length>0: %}
|
{% if favs_soon_rooms|length>0: %}
|
||||||
<div class="room-collumn">
|
<div class="room-column">
|
||||||
<br>
|
<br>
|
||||||
<h1>Favoris disponibles prochainement</h1>
|
<h1>Favoris disponibles prochainement</h1>
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
@ -70,7 +70,7 @@
|
|||||||
<div class="room-row">
|
<div class="room-row">
|
||||||
<div>
|
<div>
|
||||||
{{ room.name }} {% if DEBUG :%}( {{ room.id }} ){% endif %}
|
{{ 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>
|
<p class=details>À {{ frooms_disp[room.name]["start"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class=details>De {{ frooms_disp[room.name]["start"] }} à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
<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 %}
|
{% endif %}
|
||||||
<div class="flex-pc">
|
<div class="flex-pc">
|
||||||
{% if free_rooms|length>0 %}
|
{% if free_rooms|length>0 %}
|
||||||
<div class="room-collumn">
|
<div class="room-column">
|
||||||
<br>
|
<br>
|
||||||
<h1>Disponibles maintenant</h1>
|
<h1>Disponibles maintenant</h1>
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
@ -100,7 +100,7 @@
|
|||||||
<div class="room-row">
|
<div class="room-row">
|
||||||
<div>
|
<div>
|
||||||
{{ room.name }} {% if DEBUG :%}( {{ room.id }} ){% endif %}
|
{{ 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>
|
<p class=details>Jusqu'à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -115,7 +115,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if soon_rooms|length>0 %}
|
{% if soon_rooms|length>0 %}
|
||||||
<div class="room-collumn">
|
<div class="room-column">
|
||||||
<br>
|
<br>
|
||||||
<h1>Disponibles prochainement</h1>
|
<h1>Disponibles prochainement</h1>
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
@ -125,7 +125,7 @@
|
|||||||
<div class="room-row">
|
<div class="room-row">
|
||||||
<div>
|
<div>
|
||||||
{{ room.name }} {% if DEBUG :%}( {{ room.id }} ){% endif %}
|
{{ 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>
|
<p class=details>À {{ frooms_disp[room.name]["start"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class=details>De {{ frooms_disp[room.name]["start"] }} à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
<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