Compare commits
19 Commits
c2a2971467
...
b7f6096aaf
Author | SHA1 | Date | |
---|---|---|---|
b7f6096aaf | |||
b5820b29be | |||
0776a7c987 | |||
462180986b | |||
5cd946bac5 | |||
d296d65215 | |||
057d0f9c90 | |||
76da00161d | |||
b2517ab152 | |||
f2fc7c8775 | |||
0877cc8210 | |||
104feb31b9 | |||
17130f7c04 | |||
333d2bf3d7 | |||
a23d5735df | |||
4c567ebe56 | |||
3efa3c36be | |||
2a03dde566 | |||
09975eb013 |
12
README.md
12
README.md
@ -5,6 +5,18 @@ C'est utile aux élèves qui cherchent un coin pour travailler ou manger, comme
|
|||||||
|
|
||||||
Cette application dispose d'une interface Web fonctionnant avec Flask ( [voir la demo][homepage] ).
|
Cette application dispose d'une interface Web fonctionnant avec Flask ( [voir la demo][homepage] ).
|
||||||
|
|
||||||
|
## Fonctionnalités
|
||||||
|
|
||||||
|
- 🔎 Visualiser les salles libres de plusieurs départements en même temps ( par exemple UFR de Math-Info et EOST )
|
||||||
|
- ⏰ Pour les salles bientôt occupées, l'heure d'occupation est précisée
|
||||||
|
- 🔄 Affiche également les salles qui sont bientôt libres, avec l'heure en question
|
||||||
|
- ⭐ Permet de sélectionner des salles comme favorites
|
||||||
|
* Ces favoris ne sont conservés que sur la page en question ( les favoris sélectionnés sont stockés dans l'URL )
|
||||||
|
* Ainsi, vous pouvez partager vos favoris simplement en partageant l'URL
|
||||||
|
- 🪶 Application légère pour l'utilisateur :
|
||||||
|
* Pas de JavaScript, tout les calculs sont fait coté serveur
|
||||||
|
* Pas de *Local Storage*, *Cookies* ou autres *bibliothèques CSS*
|
||||||
|
|
||||||
## Dépendances
|
## Dépendances
|
||||||
|
|
||||||
Pour l'instant, ce programme utilise les modules suivants :
|
Pour l'instant, ce programme utilise les modules suivants :
|
||||||
|
25
app.py
25
app.py
@ -33,6 +33,7 @@ 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["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["CREDITSLINK"] = "https://forge.chapril.org/Wantoo" # Le lien de l'organisation
|
||||||
GLOBAL_CONTEXT["CREDITSNAME"] = "Wantoo" # Le nom de l'organisation
|
GLOBAL_CONTEXT["CREDITSNAME"] = "Wantoo" # Le nom de l'organisation
|
||||||
|
GLOBAL_CONTEXT["DEBUG"] = False # Fait en sorte que le logiciel soit un peu plus expressif
|
||||||
|
|
||||||
# Globales
|
# Globales
|
||||||
logs = [] # Stoque les différentes requêtes faite sur la route /free_rooms/, sous la forme {"timestamp":timestamp,"depts":[]}
|
logs = [] # Stoque les différentes requêtes faite sur la route /free_rooms/, sous la forme {"timestamp":timestamp,"depts":[]}
|
||||||
@ -148,6 +149,11 @@ def free_rooms() :
|
|||||||
else :
|
else :
|
||||||
time_uf = time_uf.split(":")
|
time_uf = time_uf.split(":")
|
||||||
|
|
||||||
|
# Récupére les IDs des salles favorites
|
||||||
|
favs_ids = request.args.getlist("favs")
|
||||||
|
if favs_ids==None:
|
||||||
|
favs_ids = []
|
||||||
|
|
||||||
date = dti.datetime.now()
|
date = dti.datetime.now()
|
||||||
|
|
||||||
date_str = "" # Date affichée sur la page (si personnalisée)
|
date_str = "" # Date affichée sur la page (si personnalisée)
|
||||||
@ -204,6 +210,7 @@ def free_rooms() :
|
|||||||
"end":date_tools.hour_disp(r.end),
|
"end":date_tools.hour_disp(r.end),
|
||||||
"rtime":remain_time_str}
|
"rtime":remain_time_str}
|
||||||
|
|
||||||
|
|
||||||
change_date_str = "?"
|
change_date_str = "?"
|
||||||
i = 0
|
i = 0
|
||||||
for v in dident_list:
|
for v in dident_list:
|
||||||
@ -211,7 +218,23 @@ def free_rooms() :
|
|||||||
if i<len(dident_list)-1:
|
if i<len(dident_list)-1:
|
||||||
change_date_str += "&"
|
change_date_str += "&"
|
||||||
i+=1
|
i+=1
|
||||||
context = {"free_rooms":free_rooms, "frooms_disp":frooms_disp, "depts_str":depts_str, "dident_list":dident_list, "date_str":date_str, "change_date_str":change_date_str}
|
|
||||||
|
# Générer le lien pour enlever les favoris séléctionnés
|
||||||
|
nofavslink = "/app/free-rooms?"
|
||||||
|
for dept in dident_list:
|
||||||
|
nofavslink+="dept="+str(dept)+"&"
|
||||||
|
nofavslink = nofavslink[:-1] # Enlever le dernier &
|
||||||
|
|
||||||
|
# Trier les salles selon leurs catégories
|
||||||
|
favs_free_rooms = []
|
||||||
|
favs_soon_rooms = []
|
||||||
|
soon_rooms = []
|
||||||
|
final_rooms = []
|
||||||
|
|
||||||
|
for r in free_rooms:
|
||||||
|
[[soon_rooms,final_rooms],[favs_soon_rooms,favs_free_rooms]][r.id in favs_ids][r.is_free].append(r)
|
||||||
|
|
||||||
|
context = {"favs_free_rooms":favs_free_rooms, "favs_soon_rooms":favs_soon_rooms, "free_rooms":final_rooms, "soon_rooms":soon_rooms, "frooms_disp":frooms_disp, "depts_str":depts_str, "dident_list":dident_list, "date_str":date_str, "change_date_str":change_date_str, "favs":len(favs_ids)>0,"nofavslink":nofavslink}
|
||||||
|
|
||||||
# Crée un log de la date et des départements demandés ( pour des futures statistiques )
|
# Crée un log de la date et des départements demandés ( pour des futures statistiques )
|
||||||
log = {}
|
log = {}
|
||||||
|
18
objects.py
18
objects.py
@ -15,6 +15,13 @@ Created on Sat May 7 17:29:11 2022
|
|||||||
l'Université de Strasbourg.
|
l'Université de Strasbourg.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Modules
|
||||||
|
import random # Nécessaire pour la génération d'ID des salles
|
||||||
|
|
||||||
|
# Constantes
|
||||||
|
ID_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" # Caractères disponibles pour la création d'ID
|
||||||
|
ID_LEN = 4 # Nombres de caractères composant l'ID
|
||||||
|
|
||||||
|
|
||||||
### Fichier contenant les classes des salles et des départements ###
|
### Fichier contenant les classes des salles et des départements ###
|
||||||
|
|
||||||
@ -44,6 +51,9 @@ class Room :
|
|||||||
|
|
||||||
count : int
|
count : int
|
||||||
Compte le nombre d'occurences de la salle dans l'emploi du temps;
|
Compte le nombre d'occurences de la salle dans l'emploi du temps;
|
||||||
|
|
||||||
|
id : string
|
||||||
|
Identifiant 'unique' ( avec un très faible risque de collision ) de la salle ( généré à partir de son nom )
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, start, end, is_free) :
|
def __init__(self, name, start, end, is_free) :
|
||||||
@ -51,6 +61,14 @@ class Room :
|
|||||||
self.start = start
|
self.start = start
|
||||||
self.end = end
|
self.end = end
|
||||||
self.is_free = is_free
|
self.is_free = is_free
|
||||||
|
self.id = self.getId(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
|
||||||
|
|
||||||
|
|
||||||
class Dept :
|
class Dept :
|
||||||
|
@ -50,10 +50,6 @@ nav {
|
|||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#body {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
}
|
}
|
||||||
@ -69,6 +65,13 @@ main p {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.room-row{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Style The Dropdown Button */
|
/* Style The Dropdown Button */
|
||||||
.dropbtn {
|
.dropbtn {
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
@ -126,6 +129,7 @@ h1, h2, h3, h4 {
|
|||||||
border-left: solid;
|
border-left: solid;
|
||||||
border-width: 5px;
|
border-width: 5px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
background: var(--bg-dark);
|
background: var(--bg-dark);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-color: var(--hl);
|
border-color: var(--hl);
|
||||||
@ -145,18 +149,6 @@ header a {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
|
||||||
margin: 10px;
|
|
||||||
align-self: flex-start;
|
|
||||||
max-width: 200px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
max-width: 200px;
|
|
||||||
max-height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
@ -170,9 +162,10 @@ dt {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
dt .details {
|
.details {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="submit"], button, .button{
|
input[type="submit"], button, .button{
|
||||||
@ -211,6 +204,10 @@ input[type="checkbox"] {
|
|||||||
height: 0px;
|
height: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type=checkbox]:checked ~ .remove-check {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -238,11 +235,6 @@ input[type="checkbox"] + label {
|
|||||||
box-shadow: 5px 5px black;
|
box-shadow: 5px 5px black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.done {
|
|
||||||
background: var(--bg-dark);
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
background: var(--bg-dark);
|
background: var(--bg-dark);
|
||||||
}
|
}
|
||||||
@ -260,6 +252,18 @@ footer {
|
|||||||
color: var(--bg-light);
|
color: var(--bg-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Medium devices (landscape tablets, 768px and up) */
|
||||||
@media screen and (max-width: 769px) {
|
@media only screen and (min-width: 1000px) {
|
||||||
|
.flex-pc{
|
||||||
|
display:flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.room-collumn{
|
||||||
|
width: 50%;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-collumn{
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
@ -9,45 +9,144 @@
|
|||||||
<body>
|
<body>
|
||||||
{% include "base.html" %}
|
{% include "base.html" %}
|
||||||
<main>
|
<main>
|
||||||
Départements sélectionnés :
|
<div class="flex">
|
||||||
<b>{{ depts_str }}</b>
|
<p>
|
||||||
|
Départements sélectionnés : <br>
|
||||||
|
<b>{{ depts_str }}</b>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
{% if date_str != "" : %}
|
{% if date_str != "" : %}
|
||||||
<b>Le {{ date_str }}</b>
|
<b>Le {{ date_str }}</b>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<a class="button" href='/app/date-select{{change_date_str}}'>Choisir une date</a>
|
<a class="button" href='/app/date-select{{change_date_str}}'>Choisir une date</a>
|
||||||
</div>
|
</div>
|
||||||
<h1>Disponibles maintenant</h1>
|
{% if favs: %}
|
||||||
<div class="flex-container">
|
<div class="flex">
|
||||||
<ul>
|
<a class="button" href="{{ nofavslink }}">Retirer les favoris</a>
|
||||||
{% for room in free_rooms : %}
|
</div>
|
||||||
{% if room.is_free : %}
|
{% endif %}
|
||||||
<dt>{{ room.name }}
|
<form action="/app/free-rooms" method="get">
|
||||||
{% if not(room.end.hour == 23 and room.end.minute == 59 and room.end.second == 59) : %}
|
{% if favs: %}
|
||||||
<br><span class=details>Jusqu'à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</span>
|
<div class="flex-pc">
|
||||||
{% endif %}
|
{% if favs_free_rooms|length>0: %}
|
||||||
</dt>
|
<div class="room-collumn">
|
||||||
{% endif %}
|
<br>
|
||||||
{% endfor %}
|
<h1>Favoris disponibles maintenant</h1>
|
||||||
</ul>
|
<div class="flex-container">
|
||||||
</div>
|
<ul>
|
||||||
<br>
|
{% for room in favs_free_rooms : %}
|
||||||
<h1>Disponibles prochainement</h1>
|
<dt>
|
||||||
<div class="flex-container">
|
<div class="room-row">
|
||||||
<ul>
|
<div>
|
||||||
{% for room in free_rooms : %}
|
{{ room.name }} {% if DEBUG :%}( {{ room.id }} ){% endif %}
|
||||||
{% if not room.is_free : %}
|
{% if not(room.end.hour == 23 and room.end.minute == 59 and room.end.second == 59) : %}
|
||||||
<dt>{{ room.name }}
|
<p class=details>Jusqu'à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||||
{% if room.end.hour == 23 and room.end.minute == 59 and room.end.second == 59 : %}
|
{% endif %}
|
||||||
<br><span class=details>À {{ frooms_disp[room.name]["start"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</span>
|
</div>
|
||||||
{% else %}
|
<div>
|
||||||
<br><span class=details>De {{ frooms_disp[room.name]["start"] }} à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</span>
|
<input class="fav" type="checkbox" id="{{ room.id }}" name="favs" value="{{ room.id }}" checked> <label style="width:30px;height:30px" for="{{ room.id }}">⭐</label>
|
||||||
{% endif %}
|
</div>
|
||||||
</dt>
|
</div>
|
||||||
{% endif %}
|
</dt>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if favs_soon_rooms|length>0: %}
|
||||||
|
<div class="room-collumn">
|
||||||
|
<br>
|
||||||
|
<h1>Favoris disponibles prochainement</h1>
|
||||||
|
<div class="flex-container">
|
||||||
|
<ul>
|
||||||
|
{% for room in favs_soon_rooms: %}
|
||||||
|
<dt>
|
||||||
|
<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 : %}
|
||||||
|
<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>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input class="fav" type="checkbox" id="{{ room.id }}" name="favs" value="{{ room.id }}" checked> <label style="width:30px;height:30px" for="{{ room.id }}">⭐</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dt>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="flex-pc">
|
||||||
|
{% if free_rooms|length>0 %}
|
||||||
|
<div class="room-collumn">
|
||||||
|
<br>
|
||||||
|
<h1>Disponibles maintenant</h1>
|
||||||
|
<div class="flex-container">
|
||||||
|
<ul>
|
||||||
|
{% for room in free_rooms: %}
|
||||||
|
<dt>
|
||||||
|
<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) : %}
|
||||||
|
<p class=details>Jusqu'à {{ frooms_disp[room.name]["end"] }} (dans {{ frooms_disp[room.name]["rtime"] }})</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input class="fav" type="checkbox" id="{{ room.id }}" name="favs" value="{{ room.id }}"> <label style="width:30px;height:30px" for="{{ room.id }}">⭐</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dt>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if soon_rooms|length>0 %}
|
||||||
|
<div class="room-collumn">
|
||||||
|
<br>
|
||||||
|
<h1>Disponibles prochainement</h1>
|
||||||
|
<div class="flex-container">
|
||||||
|
<ul>
|
||||||
|
{% for room in soon_rooms: %}
|
||||||
|
<dt>
|
||||||
|
<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 : %}
|
||||||
|
<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>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input class="fav" type="checkbox" id="{{ room.id }}" name="favs" value="{{ room.id }}"> <label style="width:30px;height:30px" for="{{ room.id }}">⭐</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dt>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
{% for d in dident_list : %} <!-- Magie noire pour conserver les départements séléctionnés -->
|
||||||
|
<span style="display: none;"><input type="text" name="dept" value="{{ d }}"/></span>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<input type="submit" value="Valider les favoris">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</main>
|
</main>
|
||||||
{% include "footer.html" %}
|
{% include "footer.html" %}
|
||||||
</body>
|
</body>
|
||||||
|
Loading…
Reference in New Issue
Block a user