Compare commits
28 Commits
fb9580c26e
...
b8573f3826
Author | SHA1 | Date |
---|---|---|
|
b8573f3826 | |
|
85db5bfa6e | |
|
efbde77062 | |
|
ea5a167d44 | |
|
872f237844 | |
|
037a7f38e0 | |
|
21ef5140f0 | |
|
20c2e68797 | |
|
db6cf3390f | |
|
d9596cf762 | |
|
a99b8f358f | |
|
e99ad98690 | |
|
a4446583be | |
|
fe1914b5a5 | |
|
54de82af42 | |
|
1dbea57525 | |
|
ed227368e6 | |
|
dcf637f880 | |
|
f04e903682 | |
|
271e1c0464 | |
|
fd4d42a2a0 | |
|
e0030b7607 | |
|
35df09d698 | |
|
a6e02eb966 | |
|
bcb94faac5 | |
|
90f721620f | |
|
35bf2e794a | |
|
5fd334f7fa |
|
@ -159,3 +159,5 @@ cython_debug/
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
kholles_web/settings.py
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
[submodule "static/unified-navigator"]
|
||||||
|
path = static/unified-navigator
|
||||||
|
url = git@mp2i-vms.fr:mp2i-vms/unified-navigator.git
|
||||||
|
branch = main
|
|
@ -1,17 +1,48 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from colloscope.models import *
|
from colloscope.models import *
|
||||||
|
|
||||||
admin.site.register(Lycee)
|
|
||||||
admin.site.register(Classe)
|
@admin.register(School)
|
||||||
admin.site.register(Periode)
|
class LyceeAdmin(admin.ModelAdmin):
|
||||||
admin.site.register(Matiere)
|
list_display = ('uai', 'description', 'vacation')
|
||||||
admin.site.register(Critere)
|
|
||||||
admin.site.register(Groupe)
|
|
||||||
admin.site.register(Etudiant)
|
admin.site.register(Class)
|
||||||
admin.site.register(Appartenance)
|
admin.site.register(Term)
|
||||||
|
admin.site.register(Subject)
|
||||||
|
admin.site.register(GroupType)
|
||||||
|
admin.site.register(Group)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Student)
|
||||||
|
class StudentAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("cls", "first_name", "last_name")
|
||||||
|
list_filter = ("cls",)
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Member)
|
||||||
admin.site.register(Colleur)
|
admin.site.register(Colleur)
|
||||||
admin.site.register(Creneau)
|
|
||||||
admin.site.register(Rotation)
|
|
||||||
admin.site.register(Amendement)
|
class ColleInline(admin.StackedInline):
|
||||||
admin.site.register(Profil)
|
model = Colle
|
||||||
admin.site.register(LienCalendrier)
|
raw_id_fields = ("slot",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Slot)
|
||||||
|
class SlotAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('subject', 'colleur', "term", 'view_day', "time", "duration")
|
||||||
|
list_filter = ("subject", "colleur", "term")
|
||||||
|
inlines = [ColleInline]
|
||||||
|
|
||||||
|
def view_day(self, obj):
|
||||||
|
jours = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
||||||
|
return jours[obj.day]
|
||||||
|
|
||||||
|
view_day.short_description = 'Day'
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Colle)
|
||||||
|
admin.site.register(Swap)
|
||||||
|
admin.site.register(Profile)
|
||||||
|
admin.site.register(CalendarLink)
|
||||||
|
|
|
@ -0,0 +1,335 @@
|
||||||
|
import pandas
|
||||||
|
import datetime
|
||||||
|
import icalendar
|
||||||
|
import uuid
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#pd_eleves = pandas.read_csv('Resources/eleves-v2.csv', delimiter=";", header=None)
|
||||||
|
#np_eleves = pd_eleves.to_numpy()
|
||||||
|
|
||||||
|
pd_colles = pandas.read_csv('static/colloscope-v3.csv', delimiter=";", header=None)
|
||||||
|
np_colles = pd_colles.to_numpy()
|
||||||
|
|
||||||
|
local_tz = "Europe/Paris"
|
||||||
|
|
||||||
|
jour_to_delta = {
|
||||||
|
"lundi": 0,
|
||||||
|
"mardi": 1,
|
||||||
|
"mercredi": 2,
|
||||||
|
"jeudi": 3,
|
||||||
|
"vendredi": 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
jour_to_byday = {
|
||||||
|
"lundi": "MO",
|
||||||
|
"mardi": "TU",
|
||||||
|
"mercredi": "WE",
|
||||||
|
"jeudi": "TH",
|
||||||
|
"vendredi": "FR",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
langues = ["Anglais", "Allemand", "Espagnol"]
|
||||||
|
|
||||||
|
option_langues = {
|
||||||
|
"EN": ["Anglais LV1"],
|
||||||
|
"EN-DE": ["Anglais LV1", "Allemand LV2"],
|
||||||
|
"EN-ES": ["Anglais LV1", "Espagnol LV2"],
|
||||||
|
"DE-EN": ["Allemand LV1", "Anglais LV2"],
|
||||||
|
"ES-EN": ["Espangol LV1", "Anglais LV2"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def display(cal):
|
||||||
|
return cal.to_ical().decode("utf-8").replace('\r\n', '\n').strip()
|
||||||
|
|
||||||
|
|
||||||
|
def open_ics(filename: str) -> icalendar.Calendar:
|
||||||
|
"""
|
||||||
|
Ouvre un fichier .ics et renvoie un objet Calendar
|
||||||
|
"""
|
||||||
|
p = Path(__file__).with_name('Resources')
|
||||||
|
p = Path(p, filename)
|
||||||
|
with p.open('r') as f:
|
||||||
|
opened_cal = icalendar.Calendar.from_ical(f.read())
|
||||||
|
|
||||||
|
return opened_cal
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def add_events_from_ics(filename: str, new_cal: icalendar.Calendar):
|
||||||
|
"""
|
||||||
|
Ajoute des évènements depuis un fichier .ics vers un objet icalendar.Calendar
|
||||||
|
"""
|
||||||
|
cal_to_import = open_ics(filename=filename)
|
||||||
|
|
||||||
|
for event in cal_to_import.walk("vevent"):
|
||||||
|
new_cal.add_component(event)
|
||||||
|
|
||||||
|
return new_cal
|
||||||
|
|
||||||
|
|
||||||
|
def get_td_groupe(groupe: str) -> str:
|
||||||
|
"""
|
||||||
|
Renvoie le groupe de TD d'un groupe de colle (A, B ou SI)
|
||||||
|
"""
|
||||||
|
for row in np_eleves[1:]:
|
||||||
|
if row[2] == groupe:
|
||||||
|
return row[3]
|
||||||
|
|
||||||
|
|
||||||
|
def debut_semaine_to_datetime(date_string: str) -> datetime.time:
|
||||||
|
"""
|
||||||
|
Transforme un string de debut de semaine au format dd/mm/yyyy en datetime.time
|
||||||
|
"""
|
||||||
|
return datetime.datetime.strptime(date_string, "%d/%m/%y")
|
||||||
|
|
||||||
|
|
||||||
|
def heure_to_timedelta(heure: str) -> datetime.timedelta:
|
||||||
|
"""
|
||||||
|
Transforme une heure au format "8h30" ou "1h30" ou "2h" en timedelta.
|
||||||
|
"""
|
||||||
|
heure = heure.split("h")
|
||||||
|
delta = datetime.timedelta(hours=int(heure[0]))
|
||||||
|
|
||||||
|
if heure[1] != '':
|
||||||
|
delta += datetime.timedelta(minutes=int(heure[1]))
|
||||||
|
|
||||||
|
return delta
|
||||||
|
|
||||||
|
def creer_evenement(titre: str, debut: datetime.datetime, duree: datetime.timedelta, description: str = None, localisation: str = None, rrule: str = None) -> icalendar.Event:
|
||||||
|
"""
|
||||||
|
Renvoie un évènement icalendar.Event()
|
||||||
|
|
||||||
|
titre str: le titre de l'évènement
|
||||||
|
debut datetime.datetime: la date de début
|
||||||
|
duree datetime.timedelta: la durée
|
||||||
|
description str: la description (Optionelle)
|
||||||
|
localisation str: localisation (Optionelle)
|
||||||
|
rrule str: règle de récurrence https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html (Optionelle)
|
||||||
|
"""
|
||||||
|
new_event = icalendar.Event()
|
||||||
|
new_event.add("summary", titre)
|
||||||
|
new_event.add("dtstart", debut, parameters={"tzid": local_tz})
|
||||||
|
debut += duree
|
||||||
|
new_event.add("dtend", debut, parameters={"tzid": local_tz})
|
||||||
|
|
||||||
|
if localisation is not None:
|
||||||
|
new_event.add("location", localisation)
|
||||||
|
if description is not None:
|
||||||
|
new_event.add("description", description)
|
||||||
|
if rrule is not None:
|
||||||
|
new_event.add("rrule", rrule)
|
||||||
|
|
||||||
|
new_event.add("uid", str(uuid.uuid4()))
|
||||||
|
new_event.add("dtstamp", datetime.datetime.now())
|
||||||
|
|
||||||
|
# nécessaire pour se conformer à la norme RFC 5545
|
||||||
|
|
||||||
|
return new_event
|
||||||
|
|
||||||
|
def get_colles_groupe(np_colles, groupe, option_langue):
|
||||||
|
"""
|
||||||
|
Renvoie les colles pour un groupe sous la forme d'une liste d'évènements ICS
|
||||||
|
|
||||||
|
np_colles: l'array 2D numpy contenant le colloscope
|
||||||
|
groupe: str, le numéro du groupe
|
||||||
|
option_langue: str, les options de langue
|
||||||
|
"""
|
||||||
|
liste_colles = []
|
||||||
|
for row in np_colles[4:40]:
|
||||||
|
if pandas.isnull(row[1]) or row[1] == "pas de colle":
|
||||||
|
# il n'y a pas de colle, on skip !
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
# Si la colle n'est pas la LV1 de l'élève, on skip
|
||||||
|
if row[1] == "Anglais" and not option_langue.startswith("EN"):
|
||||||
|
continue
|
||||||
|
elif row[1] == "Allemand" and not option_langue.startswith("DE"):
|
||||||
|
continue
|
||||||
|
elif row[1] == "Espagnol" and not option_langue.startswith("ES"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for index, colle in enumerate(row[8:]):
|
||||||
|
if pandas.isnull(colle):
|
||||||
|
continue
|
||||||
|
# les huit premières cases ne sont pas concernées
|
||||||
|
|
||||||
|
if "," in list(colle):
|
||||||
|
if groupe not in colle.split(","):
|
||||||
|
continue
|
||||||
|
elif colle != groupe:
|
||||||
|
continue
|
||||||
|
# dans le cas où plusieurs groupes de colles soient concernés
|
||||||
|
|
||||||
|
duree = heure_to_timedelta(row[4])
|
||||||
|
# transforme la durée en timedelta
|
||||||
|
|
||||||
|
date = debut_semaine_to_datetime(np_colles[1][index+8])
|
||||||
|
|
||||||
|
date += datetime.timedelta(days=jour_to_delta[row[2]])
|
||||||
|
date += heure_to_timedelta(row[3])
|
||||||
|
# génère la date à partir de la semaine, du jour et de l'heure
|
||||||
|
|
||||||
|
localisation = row[7] if not pandas.isnull(row[7]) else None
|
||||||
|
|
||||||
|
new_colle = creer_evenement(
|
||||||
|
titre=f"Colle {row[1]}",
|
||||||
|
description=f"{row[5]}" if not pandas.isnull(row[5]) else None,
|
||||||
|
localisation=localisation,
|
||||||
|
debut=date,
|
||||||
|
duree=duree
|
||||||
|
)
|
||||||
|
|
||||||
|
liste_colles.append(new_colle)
|
||||||
|
|
||||||
|
return liste_colles
|
||||||
|
|
||||||
|
def get_cours(np_colles, groupe):
|
||||||
|
"""
|
||||||
|
Renvoie les colles pour un groupe sous la forme d'une liste d'évènements ICS, cad
|
||||||
|
icalendar.Event
|
||||||
|
|
||||||
|
np_colles: l'array 2D numpy contenant le colloscope
|
||||||
|
groupe: str, le numéro du groupe
|
||||||
|
"""
|
||||||
|
listes_cours = []
|
||||||
|
groupe_td = get_td_groupe(groupe)
|
||||||
|
|
||||||
|
for row in np_colles[41:66]:
|
||||||
|
|
||||||
|
if pandas.isnull(row[1]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
first_date = np_colles[1][8]
|
||||||
|
|
||||||
|
eleves = row[8]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if "à" in list(eleves):
|
||||||
|
|
||||||
|
eleves = eleves.split(" ")
|
||||||
|
debut, fin = [grp for grp in eleves if grp.isdigit()]
|
||||||
|
|
||||||
|
if not int(debut) <= int(groupe) <= int(fin):
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif "+" in list(eleves):
|
||||||
|
eleves = eleves.split("+")
|
||||||
|
|
||||||
|
if groupe not in [grp for grp in eleves if grp.isdigit()]:
|
||||||
|
|
||||||
|
if groupe_td not in [grp for grp in eleves]:
|
||||||
|
if int(row[6]) > 1:
|
||||||
|
if groupe in [grp for grp in row[9].split("+") if grp.isdigit()]:
|
||||||
|
first_date = np_colles[1][9]
|
||||||
|
elif groupe_td in [grp for grp in row[9].split("+")]:
|
||||||
|
first_date = np_colles[1][9]
|
||||||
|
else:
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# si la 1ère occurence du cours est la 1ère ou la 2e semaine
|
||||||
|
|
||||||
|
|
||||||
|
elif groupe != eleves and groupe_td != eleves:
|
||||||
|
if pandas.isnull(row[9]) or (groupe != row[9] and groupe_td != row[9]):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
first_date = np_colles[1][9]
|
||||||
|
|
||||||
|
# vérification que le groupe est concerné par le cours
|
||||||
|
|
||||||
|
first_date = debut_semaine_to_datetime(first_date)
|
||||||
|
|
||||||
|
first_date += heure_to_timedelta(row[3])
|
||||||
|
first_date += datetime.timedelta(days=jour_to_delta[row[2]])
|
||||||
|
|
||||||
|
duree = heure_to_timedelta(row[4])
|
||||||
|
|
||||||
|
localisation = row[7] if not pandas.isnull(row[7]) else None
|
||||||
|
|
||||||
|
last_date = debut_semaine_to_datetime(np_colles[1][-1]) + datetime.timedelta(days=5)
|
||||||
|
|
||||||
|
rrule = {"FREQ": "WEEKLY", "UNTIL": last_date, "INTERVAL": row[6], "BYDAY": jour_to_byday[row[2]], "WKST": "MO"}
|
||||||
|
|
||||||
|
new_cours = creer_evenement(
|
||||||
|
titre=row[1],
|
||||||
|
description=row[5],
|
||||||
|
localisation=localisation,
|
||||||
|
debut=first_date,
|
||||||
|
duree=duree,
|
||||||
|
rrule=rrule
|
||||||
|
)
|
||||||
|
|
||||||
|
listes_cours.append(new_cours)
|
||||||
|
|
||||||
|
return listes_cours
|
||||||
|
|
||||||
|
def get_langues(langues):
|
||||||
|
"""
|
||||||
|
Renvoie la liste de cours de langue associés sous forme de list[icalendar.Event]
|
||||||
|
"""
|
||||||
|
liste_cours = []
|
||||||
|
langues = option_langues[langues]
|
||||||
|
|
||||||
|
for row in np_colles[67:]:
|
||||||
|
|
||||||
|
if row[1] in langues:
|
||||||
|
|
||||||
|
debut = debut_semaine_to_datetime(np_colles[1][8])
|
||||||
|
debut += heure_to_timedelta(row[3])
|
||||||
|
debut += datetime.timedelta(days=jour_to_delta[row[2]])
|
||||||
|
|
||||||
|
duree = heure_to_timedelta(row[4])
|
||||||
|
|
||||||
|
last_date = debut_semaine_to_datetime(np_colles[1][-1]) + datetime.timedelta(days=5)
|
||||||
|
|
||||||
|
rrule = {"FREQ": "WEEKLY", "UNTIL": last_date, "INTERVAL": row[6], "BYDAY": jour_to_byday[row[2]], "WKST": "MO"}
|
||||||
|
|
||||||
|
new_cours = creer_evenement(
|
||||||
|
titre=row[1],
|
||||||
|
debut=debut,
|
||||||
|
duree=duree,
|
||||||
|
rrule=rrule
|
||||||
|
)
|
||||||
|
|
||||||
|
liste_cours.append(new_cours)
|
||||||
|
|
||||||
|
return liste_cours
|
||||||
|
|
||||||
|
|
||||||
|
def get_calendar(groupe: str, langues: str):
|
||||||
|
""""
|
||||||
|
Renvoie un calendrier (icalendar.Calendar) pour un groupe avec des langues donné
|
||||||
|
"""
|
||||||
|
|
||||||
|
new_cal_edt = open_ics("Base_Calendar.ics")
|
||||||
|
|
||||||
|
# if split:
|
||||||
|
# new_cal_colles = open_ics("Base_Calendar.ics")
|
||||||
|
|
||||||
|
|
||||||
|
for event in get_langues(langues):
|
||||||
|
new_cal_edt.add_component(event)
|
||||||
|
|
||||||
|
for event in get_cours(np_colles, groupe):
|
||||||
|
new_cal_edt.add_component(event)
|
||||||
|
|
||||||
|
# if split:
|
||||||
|
# for event in get_colles_groupe(np_colles, groupe, langues):
|
||||||
|
# new_cal_colles.add_component(event)
|
||||||
|
# return new_cal_edt.to_ical(), new_cal_colles.to_ical()
|
||||||
|
|
||||||
|
# else:
|
||||||
|
# for event in get_colles_groupe(np_colles, groupe, langues):
|
||||||
|
# new_cal_edt.add_component(event)
|
||||||
|
|
||||||
|
|
||||||
|
return new_cal_edt.to_ical()
|
|
@ -1,60 +1,76 @@
|
||||||
from datetime import date, time, datetime, timedelta
|
from datetime import date, time, datetime, timedelta
|
||||||
from pytz import timezone
|
import pytz
|
||||||
|
from uuid import uuid4
|
||||||
|
from os import path
|
||||||
from icalendar import Calendar, Event, vCalAddress, vText
|
from icalendar import Calendar, Event, vCalAddress, vText
|
||||||
|
|
||||||
from colloscope.models import *
|
from colloscope.models import *
|
||||||
|
from .create_calendar import get_calendar
|
||||||
|
|
||||||
def emailize(nom, prenom=None):
|
LOCAL_TZ = "Europe/Paris"
|
||||||
if prenom is not None:
|
|
||||||
|
|
||||||
|
def emailize(nom, first_name=None):
|
||||||
|
if first_name is not None:
|
||||||
return "{}.{}@example.com" \
|
return "{}.{}@example.com" \
|
||||||
.format(
|
.format(
|
||||||
prenom.replace(" ", "_").lower(),
|
first_name.replace(" ", "_").lower(),
|
||||||
nom.replace(" ", "_").lower()
|
nom.replace(" ", "_").lower()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return "{}@example.com" \
|
return "{}@example.com" \
|
||||||
.format(nom.replace(" ", "_").lower())
|
.format(nom.replace(" ", "_").lower())
|
||||||
|
|
||||||
def to_calendar(etudiant, periode):
|
|
||||||
cal = Calendar()
|
|
||||||
|
|
||||||
cal.add("prodid", "-//Colloscope//colles.mp2i-vms.fr//")
|
def to_calendar(student, term, include_EDT: bool = True):
|
||||||
cal.add("version", "2.0")
|
p = path.abspath('./static/Base_Calendar.ics')
|
||||||
|
|
||||||
rotations = Rotation.objects \
|
with open(p) as f:
|
||||||
.filter(groupes__membres=etudiant) \
|
cal = Calendar.from_ical(f.read())
|
||||||
.select_related("creneau__periode__classe__lycee") \
|
|
||||||
.select_related("creneau__matiere") \
|
|
||||||
.select_related("creneau__colleur") \
|
|
||||||
|
|
||||||
for rotation in rotations:
|
colles = term.query_colles_of_student(student)
|
||||||
|
|
||||||
|
for colle in colles:
|
||||||
event = Event()
|
event = Event()
|
||||||
event.add("name", "Colle")
|
|
||||||
event.add("summary", str(rotation))
|
|
||||||
|
|
||||||
start = rotation.datetime()
|
summary = f"Colle {colle.slot.subject} ({colle.slot.colleur})"
|
||||||
fin = start + rotation.creneau.duree
|
event.add("summary", summary)
|
||||||
|
|
||||||
|
start = colle.datetime.astimezone(pytz.timezone(LOCAL_TZ))
|
||||||
|
fin = start + colle.slot.duration
|
||||||
|
|
||||||
event.add("dtstart", start)
|
event.add("dtstart", start)
|
||||||
event.add("dtend", fin)
|
event.add("dtend", fin)
|
||||||
event.add("dtstamp", datetime.now())
|
event.add("dtstamp", datetime.now())
|
||||||
|
event.add("uid", str(uuid4()))
|
||||||
|
|
||||||
event.add("location", f"{rotation.creneau.salle} ({rotation.creneau.periode.classe.lycee})")
|
event.add("location", f"{colle.slot.room} ({colle.slot.term.cls.school})")
|
||||||
event.add("matiere", str(rotation.creneau.matiere))
|
event.add("categories", "COLLE-" + str(colle.slot.subject))
|
||||||
|
|
||||||
organizer = vCalAddress(f"mailto:{emailize(rotation.creneau.colleur.nom)}")
|
description = f"Groupes: {','.join(str(group) for group in colle.groups.all())}"
|
||||||
organizer.params["cn"] = vText(str(rotation.creneau.colleur))
|
event.add("description", description)
|
||||||
|
|
||||||
|
organizer = vCalAddress(f"mailto:{emailize(colle.slot.colleur.name)}")
|
||||||
|
organizer.params["cn"] = vText(str(colle.slot.colleur))
|
||||||
organizer.params["role"] = vText("Colleur")
|
organizer.params["role"] = vText("Colleur")
|
||||||
event.add("organizer", organizer)
|
event.add("organizer", organizer)
|
||||||
|
|
||||||
for e in rotation.groupe_effectif():
|
for e in colle.final_group():
|
||||||
attendee = vCalAddress("mailto:{emailize(e.nom, prenom=e.prenom)}")
|
attendee = vCalAddress(f"mailto:{emailize(e.last_name, first_name=e.first_name)}")
|
||||||
attendee.params["cn"] = vText(str(e))
|
|
||||||
attendee.params["role"] = vText("Etudiant")
|
attendee.params["role"] = vText("Etudiant")
|
||||||
|
attendee.params["cn"] = vText(str(e))
|
||||||
|
|
||||||
event.add("attendee", attendee, encode=0)
|
event.add("attendee", attendee, encode=0)
|
||||||
|
|
||||||
cal.add_component(event)
|
cal.add_component(event)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if include_EDT:
|
||||||
|
#TODO: get le groupe de l'étudiant et ses langue
|
||||||
|
edt = get_calendar()
|
||||||
|
|
||||||
|
for event in edt.walk("VEVENT"):
|
||||||
|
cal.add_component(event)
|
||||||
|
"""
|
||||||
|
|
||||||
return cal
|
return cal
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-05-02 19:09
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0011_alter_liencalendrier_code'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Lycee',
|
||||||
|
new_name='School',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Classe',
|
||||||
|
new_name='Class',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Periode',
|
||||||
|
new_name='Term',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Matiere',
|
||||||
|
new_name='Subject',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Critere',
|
||||||
|
new_name='GroupType',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Groupe',
|
||||||
|
new_name='Group',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Etudiant',
|
||||||
|
new_name='Student',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Appartenance',
|
||||||
|
new_name='Member',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Creneau',
|
||||||
|
new_name='Slot',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Rotation',
|
||||||
|
new_name='Colle',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Amendement',
|
||||||
|
new_name='Swap',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='Profil',
|
||||||
|
new_name='Profile',
|
||||||
|
),
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name='LienCalendrier',
|
||||||
|
new_name='CalendarLink',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='colle',
|
||||||
|
name='groupes',
|
||||||
|
field=models.ManyToManyField(to='colloscope.group'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='member',
|
||||||
|
name='groupe',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.group'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,255 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-05-02 20:00
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0012_rename_lycee_school_rename_creneau_slot_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='colle',
|
||||||
|
options={'ordering': ['slot__term__cls', 'slot__subject__description', 'slot__colleur__name', 'date', 'slot__time']},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='slot',
|
||||||
|
options={'verbose_name_plural': 'slots'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='student',
|
||||||
|
options={'ordering': ['cls', 'last_name', 'first_name']},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='term',
|
||||||
|
options={'ordering': ['begin']},
|
||||||
|
),
|
||||||
|
migrations.RemoveConstraint(
|
||||||
|
model_name='calendarlink',
|
||||||
|
name='unique_etudiant_periode_combination',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='calendarlink',
|
||||||
|
old_name='code',
|
||||||
|
new_name='key',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='calendarlink',
|
||||||
|
old_name='etudiant',
|
||||||
|
new_name='student',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='calendarlink',
|
||||||
|
old_name='periode',
|
||||||
|
new_name='term',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='class',
|
||||||
|
old_name='jour_zero',
|
||||||
|
new_name='day_zero',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='class',
|
||||||
|
old_name='libelle',
|
||||||
|
new_name='description',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='class',
|
||||||
|
old_name='annee',
|
||||||
|
new_name='year',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='colle',
|
||||||
|
old_name='groupes',
|
||||||
|
new_name='groups',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='colle',
|
||||||
|
old_name='creneau',
|
||||||
|
new_name='slot',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='colleur',
|
||||||
|
old_name='civilite',
|
||||||
|
new_name='gender',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='colleur',
|
||||||
|
old_name='nom',
|
||||||
|
new_name='name',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='group',
|
||||||
|
old_name='libelle',
|
||||||
|
new_name='description',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='group',
|
||||||
|
old_name='membres',
|
||||||
|
new_name='members',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='group',
|
||||||
|
old_name='periode',
|
||||||
|
new_name='term',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='group',
|
||||||
|
old_name='critere',
|
||||||
|
new_name='type',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='grouptype',
|
||||||
|
old_name='libelle',
|
||||||
|
new_name='description',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='grouptype',
|
||||||
|
old_name='periode',
|
||||||
|
new_name='term',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='member',
|
||||||
|
old_name='groupe',
|
||||||
|
new_name='group',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='member',
|
||||||
|
old_name='etudiant',
|
||||||
|
new_name='student',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='profile',
|
||||||
|
old_name='etudiant',
|
||||||
|
new_name='student',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='profile',
|
||||||
|
old_name='utilisateur',
|
||||||
|
new_name='user',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='school',
|
||||||
|
old_name='libelle',
|
||||||
|
new_name='description',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='school',
|
||||||
|
old_name='vacances',
|
||||||
|
new_name='vacation',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='slot',
|
||||||
|
old_name='capacite',
|
||||||
|
new_name='capacity',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='slot',
|
||||||
|
old_name='jour',
|
||||||
|
new_name='day',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='slot',
|
||||||
|
old_name='duree',
|
||||||
|
new_name='duration',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='slot',
|
||||||
|
old_name='salle',
|
||||||
|
new_name='room',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='slot',
|
||||||
|
old_name='matiere',
|
||||||
|
new_name='subject',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='slot',
|
||||||
|
old_name='periode',
|
||||||
|
new_name='term',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='slot',
|
||||||
|
old_name='heure',
|
||||||
|
new_name='time',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='student',
|
||||||
|
old_name='nom',
|
||||||
|
new_name='last_name',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='student',
|
||||||
|
old_name='groupes',
|
||||||
|
new_name='groups',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='student',
|
||||||
|
old_name='prenom',
|
||||||
|
new_name='first_name',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='subject',
|
||||||
|
old_name='libelle',
|
||||||
|
new_name='description',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='swap',
|
||||||
|
old_name='rotation',
|
||||||
|
new_name='colle',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='swap',
|
||||||
|
old_name='est_positif',
|
||||||
|
new_name='enroll',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='swap',
|
||||||
|
old_name='etudiant',
|
||||||
|
new_name='student',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='term',
|
||||||
|
old_name='debut',
|
||||||
|
new_name='begin',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='term',
|
||||||
|
old_name='libelle',
|
||||||
|
new_name='description',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='term',
|
||||||
|
old_name='fin',
|
||||||
|
new_name='end',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='slot',
|
||||||
|
name='est_colle',
|
||||||
|
),
|
||||||
|
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='student',
|
||||||
|
old_name='classe',
|
||||||
|
new_name='cls',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='subject',
|
||||||
|
old_name='classe',
|
||||||
|
new_name='cls',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='term',
|
||||||
|
old_name='classe',
|
||||||
|
new_name='cls',
|
||||||
|
),
|
||||||
|
|
||||||
|
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='calendarlink',
|
||||||
|
constraint=models.UniqueConstraint(fields=('student', 'term'), name='unique_student_term_combination'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-05-02 21:40
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0013_alter_colle_options_alter_slot_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='group',
|
||||||
|
options={'ordering': ['term__cls__description', 'term__description', 'description']},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='slot',
|
||||||
|
name='type',
|
||||||
|
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='colloscope.grouptype'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-05-02 21:46
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0014_alter_group_options_slot_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='class',
|
||||||
|
old_name='lycee',
|
||||||
|
new_name='school',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-05-05 07:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0015_rename_lycee_class_school'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='colle',
|
||||||
|
name='groups',
|
||||||
|
field=models.ManyToManyField(blank=True, to='colloscope.group'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-05-06 20:22
|
||||||
|
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from pytz import timezone
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.migrations import RunPython
|
||||||
|
|
||||||
|
|
||||||
|
def replace_date_with_datetime(apps, schema_editor):
|
||||||
|
model = apps.get_model('colloscope', 'Colle')
|
||||||
|
|
||||||
|
for colle in model.objects.all():
|
||||||
|
print(colle.slot.time, end="-->")
|
||||||
|
colle.datetime = datetime.combine(colle.date, colle.slot.time)
|
||||||
|
colle.datetime = timezone("Europe/Paris").localize(colle.datetime)
|
||||||
|
print(colle.datetime)
|
||||||
|
colle.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0016_alter_colle_groups'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='slot',
|
||||||
|
options={'ordering': ['subject', 'colleur', 'day', 'time'], 'verbose_name_plural': 'slots'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='subject',
|
||||||
|
options={'ordering': ['description']},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='colle',
|
||||||
|
name='datetime',
|
||||||
|
field=models.DateTimeField(default=datetime(1970, 1, 1, 0, 0, 0)),
|
||||||
|
),
|
||||||
|
migrations.RunPython(replace_date_with_datetime),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='colle',
|
||||||
|
name='date',
|
||||||
|
)
|
||||||
|
]
|
|
@ -1,59 +1,65 @@
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Q, Count, QuerySet, Subquery, OuterRef, Sum
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from discord import Webhook
|
||||||
|
|
||||||
calendrier = {
|
calendar = {
|
||||||
"C" : [
|
"C": [
|
||||||
( date(2023, 10, 21), date(2023, 11, 6) ),
|
(date(2023, 10, 21), date(2023, 11, 6)),
|
||||||
( date(2023, 12, 23), date(2024, 1, 8) ),
|
(date(2023, 12, 23), date(2024, 1, 8)),
|
||||||
( date(2024, 2, 10), date(2024, 2, 26) ),
|
(date(2024, 2, 10), date(2024, 2, 26)),
|
||||||
( date(2024, 4, 6), date(2024, 4, 22) ),
|
(date(2024, 4, 6), date(2024, 4, 22)),
|
||||||
( date(2024, 5, 6), date(2024, 5, 11) ), # pont un peu gratté
|
(date(2024, 5, 6), date(2024, 5, 11)), # pont un peu gratté
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Lycee(models.Model):
|
class School(models.Model):
|
||||||
uai = models.CharField(max_length=10)
|
uai = models.CharField(max_length=10)
|
||||||
libelle = models.CharField(max_length=100)
|
description = models.CharField(max_length=100)
|
||||||
vacances = models.CharField(max_length=1)
|
vacation = models.CharField(max_length=1)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return self.libelle
|
return self.description
|
||||||
|
|
||||||
|
|
||||||
class Classe(models.Model):
|
class Class(models.Model):
|
||||||
lycee = models.ForeignKey(Lycee, on_delete=models.CASCADE)
|
school = models.ForeignKey(School, on_delete=models.CASCADE)
|
||||||
libelle = models.CharField(max_length=20)
|
description = models.CharField(max_length=20)
|
||||||
annee = models.IntegerField()
|
year = models.IntegerField()
|
||||||
jour_zero = models.DateField()
|
day_zero = models.DateField()
|
||||||
|
|
||||||
def no_semaine(self, jour):
|
def week_number(self, day: date) -> int:
|
||||||
"""
|
"""
|
||||||
Entrées :
|
Entrées :
|
||||||
- self
|
- self
|
||||||
- jour (datetime.date)
|
- day (datetime.date)
|
||||||
|
|
||||||
Sortie :
|
Sortie :
|
||||||
- Le numéro de la semaine contenant jour, sans compter les vacances.
|
- Le numéro de la semaine contenant day, sans compter les vacation.
|
||||||
Renvoie un numéro non spécifiée si le jour est pendant une période de vacances
|
Renvoie un numéro non spécifiée si le day est pendant une période de vacation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
zone = self.lycee.vacances
|
zone = self.school.vacation
|
||||||
vacances = calendrier[zone]
|
vacation = calendar[zone]
|
||||||
jour0 = self.jour_zero
|
day0 = self.day_zero
|
||||||
|
|
||||||
n = 1 + ((jour - jour0).days)//7
|
n = 1 + (day - day0).days // 7
|
||||||
for debut, fin in vacances:
|
for debut, fin in vacation:
|
||||||
if jour > debut:
|
if day > debut:
|
||||||
n -= round( ( fin - debut )/timedelta(weeks=1) )
|
n -= round((fin - debut) / timedelta(weeks=1))
|
||||||
return n
|
return n
|
||||||
|
|
||||||
def no_aujourdhui(self):
|
def today_number(self) -> int:
|
||||||
"""
|
"""
|
||||||
Entrée:
|
Entrée:
|
||||||
- self
|
- self
|
||||||
|
@ -62,10 +68,9 @@ class Classe(models.Model):
|
||||||
- Le numéro de la semaine courante
|
- Le numéro de la semaine courante
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.no_semaine(date.today())
|
return self.week_number(date.today())
|
||||||
|
|
||||||
|
def week_beginning_date(self, n: int) -> date:
|
||||||
def date_debut_sem(self, n):
|
|
||||||
"""
|
"""
|
||||||
Entrée:
|
Entrée:
|
||||||
- self
|
- self
|
||||||
|
@ -75,59 +80,57 @@ class Classe(models.Model):
|
||||||
- Le date du lundi de la semaine n
|
- Le date du lundi de la semaine n
|
||||||
"""
|
"""
|
||||||
|
|
||||||
zone = self.lycee.vacances
|
zone = self.school.vacation
|
||||||
vacances = calendrier[zone]
|
vacation = calendar[zone]
|
||||||
jour0 = self.jour_zero
|
day0 = self.day_zero
|
||||||
|
|
||||||
jour = jour0 + (n-1) * timedelta(weeks=1)
|
day = day0 + (n - 1) * timedelta(weeks=1)
|
||||||
|
|
||||||
for debut, fin in vacances:
|
for begin, end in vacation:
|
||||||
if jour >= debut:
|
if day >= begin:
|
||||||
jour += round( (fin - debut)/timedelta(weeks=1) )*timedelta(weeks=1)
|
day += round((end - begin) / timedelta(weeks=1)) * timedelta(weeks=1)
|
||||||
|
|
||||||
return jour
|
return day
|
||||||
|
|
||||||
def periode(self, jour):
|
def term_of_date(self, day: date):
|
||||||
"""
|
"""
|
||||||
Entrées :
|
Entrées :
|
||||||
- self
|
- self
|
||||||
- jour (datetime.date)
|
- day (datetime.date)
|
||||||
|
|
||||||
Sortie :
|
Sortie :
|
||||||
- La période (si elle existe et est unique) contenant jour
|
- La période (si elle existe et est unique) contenant day
|
||||||
|
|
||||||
Exceptions:
|
Exceptions:
|
||||||
- Le jour n'est pas dans une période
|
- Le day n'est pas dans une période
|
||||||
- Le jour est au chevauchement de deux périodes
|
- Le day est au chevauchement de deux périodes
|
||||||
"""
|
"""
|
||||||
return Periode.objects.get(classe=self, debut__lte=jour, fin__gte=jour)
|
return Term.objects.get(cls=self, debut__lte=day, fin__gte=day)
|
||||||
|
|
||||||
def periode_actuelle(self):
|
def current_term(self):
|
||||||
#return self.periode(date.today()) // ne fonctionne pas entre les périodes
|
|
||||||
"""
|
"""
|
||||||
On prend la période non révolue la plus récente
|
On prend la période non révolue la plus récente
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return Periode.objects \
|
return (Term.objects
|
||||||
.filter(classe=self, fin__gte=date.today()) \
|
.filter(cls=self, end__gte=date.today())
|
||||||
.order_by("-debut") \
|
.order_by("-begin")
|
||||||
.first()
|
.first())
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.libelle} ({self.lycee.libelle})"
|
return f"{self.description} ({self.school.description})"
|
||||||
|
|
||||||
|
|
||||||
class Periode(models.Model):
|
class Term(models.Model):
|
||||||
classe = models.ForeignKey(Classe, on_delete=models.CASCADE)
|
cls = models.ForeignKey(Class, on_delete=models.CASCADE)
|
||||||
#critere_colle = models.ForeignKey(Critere, on_delete=models.SET_NULL, null=True)
|
description = models.CharField(max_length=100)
|
||||||
libelle = models.CharField(max_length=100)
|
begin = models.DateField()
|
||||||
debut = models.DateField()
|
end = models.DateField()
|
||||||
fin = models.DateField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["debut"]
|
ordering = ["begin"]
|
||||||
|
|
||||||
def range_semaines(self):
|
def range_weeks(self) -> range:
|
||||||
"""
|
"""
|
||||||
Entrée:
|
Entrée:
|
||||||
- self
|
- self
|
||||||
|
@ -135,243 +138,312 @@ class Periode(models.Model):
|
||||||
Sortie:
|
Sortie:
|
||||||
- Un range des numéros de semaine
|
- Un range des numéros de semaine
|
||||||
"""
|
"""
|
||||||
return range(self.classe.no_semaine(self.debut), self.classe.no_semaine(self.fin)+1)
|
return range(self.cls.week_number(self.begin), self.cls.week_number(self.end) + 1)
|
||||||
|
|
||||||
def query_rotations(self):
|
def query_colles(self) -> QuerySet:
|
||||||
return Rotation.objects.filter(creneau__periode=self)
|
return (Colle.objects
|
||||||
|
.select_related("slot", "slot__term")
|
||||||
|
.prefetch_related("swap_set")
|
||||||
|
.filter(slot__term=self)
|
||||||
|
.annotate(base_vol=Count("groups__members"))
|
||||||
|
.annotate(swap_plus=Count("swap", filter=Q(swap__enroll=1), distinct=True))
|
||||||
|
.annotate(swap_minus=Count("swap", filter=Q(swap__enroll=0), distinct=True))
|
||||||
|
.annotate(volume=F("base_vol") + F("swap_plus") - F("swap_minus"))
|
||||||
|
.order_by("datetime", "slot__time"))
|
||||||
|
|
||||||
|
def query_colles_of_student(self, student) -> QuerySet:
|
||||||
|
has_student = ((Q(groups__student=student)
|
||||||
|
& ~Q(swap__enroll=0, swap__student=student))
|
||||||
|
| Q(swap__enroll=1, swap__student=student))
|
||||||
|
|
||||||
|
return (Colle.objects
|
||||||
|
.select_related("slot", "slot__term")
|
||||||
|
.prefetch_related("swap_set")
|
||||||
|
.filter(slot__term=self)
|
||||||
|
.annotate(base_vol=Count("groups__members", distinct=True))
|
||||||
|
.annotate(swap_plus=Count("pk", filter=Q(swap__enroll=1), distinct=True))
|
||||||
|
.annotate(swap_minus=Count("pk", filter=Q(swap__enroll=0), distinct=True))
|
||||||
|
.annotate(volume=F("base_vol") + F("swap_plus") - F("swap_minus"))
|
||||||
|
.filter(has_student)
|
||||||
|
.order_by()
|
||||||
|
)
|
||||||
|
|
||||||
|
def query_colles_not_full_excluding_student(self, student) -> QuerySet:
|
||||||
|
has_student = ((Q(groups__student=student)
|
||||||
|
& ~Q(swap__enroll=0, swap__student=student))
|
||||||
|
| Q(swap__enroll=1, swap__student=student))
|
||||||
|
|
||||||
|
return (self.query_colles()
|
||||||
|
.filter(volume__lt=F("slot__capacity"), datetime__gte=date.today())
|
||||||
|
.exclude(has_student))
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.description
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
class Subject(models.Model):
|
||||||
return self.libelle
|
cls = models.ForeignKey(Class, on_delete=models.CASCADE)
|
||||||
|
description = models.CharField(max_length=100)
|
||||||
|
|
||||||
class Matiere(models.Model):
|
|
||||||
classe = models.ForeignKey(Classe, on_delete=models.CASCADE)
|
|
||||||
libelle = models.CharField(max_length=100)
|
|
||||||
code = models.CharField(max_length=20)
|
code = models.CharField(max_length=20)
|
||||||
|
|
||||||
def __str__(self):
|
class Meta:
|
||||||
return self.libelle
|
ordering = ["description"]
|
||||||
|
|
||||||
|
|
||||||
class Critere(models.Model):
|
|
||||||
periode = models.ForeignKey(Periode, on_delete=models.CASCADE)
|
|
||||||
libelle = models.CharField(max_length=100)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.libelle
|
return self.description
|
||||||
|
|
||||||
|
|
||||||
class Groupe(models.Model):
|
class GroupType(models.Model):
|
||||||
#class Meta:
|
term = models.ForeignKey(Term, on_delete=models.CASCADE)
|
||||||
# ordering=[F("periode").classe.libelle, F("periode").libelle, "libelle"]
|
description = models.CharField(max_length=100)
|
||||||
|
|
||||||
periode = models.ForeignKey(Periode, on_delete=models.CASCADE)
|
|
||||||
critere = models.ForeignKey(Critere, null=True, on_delete=models.CASCADE)
|
|
||||||
libelle = models.CharField(max_length=100)
|
|
||||||
|
|
||||||
membres = models.ManyToManyField("Etudiant", through="Appartenance")
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.libelle
|
return self.description
|
||||||
|
|
||||||
def get_colles(self):
|
|
||||||
return Rotation.objects.filter(groupes=self).order_by("date")
|
class Group(models.Model):
|
||||||
|
class Meta:
|
||||||
|
ordering = ["term__cls__description", "term__description", "description"]
|
||||||
|
|
||||||
|
term = models.ForeignKey(Term, on_delete=models.CASCADE)
|
||||||
|
type = models.ForeignKey(GroupType, null=True, on_delete=models.CASCADE)
|
||||||
|
description = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
members = models.ManyToManyField("Student", through="Member")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.description
|
||||||
|
|
||||||
|
"""def get_colles(self):
|
||||||
|
return Rotation.objects.filter(slot__term=self.term,
|
||||||
|
Q(groupes=self) || Q(a)).order_by("date")
|
||||||
|
|
||||||
def get_colles_par_sem(self):
|
def get_colles_par_sem(self):
|
||||||
semaines = ( (s, self.periode.classe.date_debut_sem(s)) for s in self.periode.range_semaines() )
|
semaines = ((s, self.term.cls.week_beginning_date(s)) for s in self.term.range_semaines())
|
||||||
colles_flat = self.get_colles()
|
colles_flat = self.get_colles()
|
||||||
|
|
||||||
return [
|
return [
|
||||||
(sem, lundi,
|
(sem, lundi,
|
||||||
colles_flat.filter(date__gte=lundi, date__lt=lundi+timedelta(weeks=1)))
|
colles_fla.filter(date__gte=lundi, date__lt=lundi + timedelta(weeks=1)))
|
||||||
for sem, lundi in semaines
|
for sem, lundi in semaines
|
||||||
]
|
]"""
|
||||||
|
|
||||||
|
|
||||||
class Etudiant(models.Model):
|
class Student(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering=["classe", "nom", "prenom"]
|
ordering = ["cls", "last_name", "first_name"]
|
||||||
|
|
||||||
classe = models.ForeignKey(Classe, on_delete=models.CASCADE)
|
cls = models.ForeignKey(Class, on_delete=models.CASCADE)
|
||||||
prenom = models.CharField(max_length=100)
|
first_name = models.CharField(max_length=100)
|
||||||
nom = models.CharField(max_length=100)
|
last_name = models.CharField(max_length=100)
|
||||||
groupes = models.ManyToManyField("Groupe", through="Appartenance")
|
groups = models.ManyToManyField("Group", through="Member")
|
||||||
|
|
||||||
def appartient(self, groupe):
|
def is_member(self, group):
|
||||||
"""
|
"""
|
||||||
Renvoie si self appartient au groupe.
|
Renvoie si self appartient au groupe.
|
||||||
"""
|
"""
|
||||||
return groupe.membres.contains(self)
|
return group.members.contains(self)
|
||||||
|
|
||||||
def groupe_du_critere(self, periode, critere):
|
def group_of_type(self, term, type_):
|
||||||
"""
|
"""
|
||||||
Renvoie le groupe du critère auquel self appartient.
|
Renvoie le groupe du critère auquel self appartient.
|
||||||
"""
|
"""
|
||||||
if isinstance(critere, str):
|
if isinstance(type_, str):
|
||||||
critere = Critere.objects.get(periode=periode, libelle=critere)
|
type_ = GroupType.objects.get(term=term, description=type_)
|
||||||
|
|
||||||
return Appartenance.objects.get(groupe__periode=periode, etudiant=self, groupe__critere=critere).groupe
|
return Member.objects.get(group__term=term, student=self, group__type=type_).group
|
||||||
|
|
||||||
def groupe_de_colle(self, periode):
|
def colle_group(self, term):
|
||||||
"""
|
"""
|
||||||
Renvoie le groupe de colle de self pendant periode.
|
Renvoie le groupe de colle de self pendant term.
|
||||||
"""
|
"""
|
||||||
return self.groupe_du_critere(periode, "colle")
|
return self.group_of_type(term, "colle")
|
||||||
|
|
||||||
|
def swap_score(self, term):
|
||||||
|
colles = term.query_colles_of_student(self)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.prenom} {self.nom}"
|
return f"{self.first_name} {self.last_name}"
|
||||||
|
|
||||||
|
|
||||||
class Appartenance(models.Model):
|
class Member(models.Model):
|
||||||
etudiant = models.ForeignKey(Etudiant, on_delete=models.CASCADE)
|
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
||||||
groupe = models.ForeignKey(Groupe, on_delete=models.CASCADE)
|
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
class Colleur(models.Model):
|
class Colleur(models.Model):
|
||||||
civilite = models.CharField(max_length=1)
|
gender = models.CharField(max_length=1)
|
||||||
nom = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.civilite == "M":
|
if self.gender == "M":
|
||||||
return f"M. {self.nom}"
|
return f"M. {self.name}"
|
||||||
else:
|
else:
|
||||||
return f"Mme {self.nom}"
|
return f"Mme {self.name}"
|
||||||
|
|
||||||
def get_classes(self):
|
def get_classes(self):
|
||||||
return (x.periode.classe for x in Creneau.objects.filter(colleur=self).select_related("periode__classe"))
|
return (x.term_of_date.cls for x in Slot.objects.filter(colleur=self).select_related("term__cls"))
|
||||||
|
|
||||||
|
|
||||||
class Creneau(models.Model):
|
class Slot(models.Model):
|
||||||
periode = models.ForeignKey(Periode, on_delete=models.CASCADE)
|
term = models.ForeignKey(Term, on_delete=models.CASCADE)
|
||||||
jour = models.IntegerField()
|
day = models.IntegerField()
|
||||||
heure = models.TimeField()
|
time = models.TimeField()
|
||||||
duree = models.DurationField()
|
duration = models.DurationField()
|
||||||
salle = models.CharField(max_length=20)
|
room = models.CharField(max_length=20)
|
||||||
matiere = models.ForeignKey(Matiere, on_delete=models.CASCADE)
|
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
|
||||||
colleur = models.ForeignKey(Colleur, on_delete=models.CASCADE)
|
colleur = models.ForeignKey(Colleur, on_delete=models.CASCADE)
|
||||||
est_colle = models.BooleanField()
|
type = models.ForeignKey(GroupType, on_delete=models.CASCADE)
|
||||||
capacite = models.IntegerField()
|
capacity = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["subject", "colleur", "day", "time"]
|
||||||
|
verbose_name_plural = "slots"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
jours = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
days = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
||||||
|
|
||||||
return f"Colle {self.matiere} avec {self.colleur} {jours[self.jour]} {self.heure}"
|
return f"Colle {self.subject} avec {self.colleur} {days[self.day]} {self.time}"
|
||||||
|
|
||||||
|
|
||||||
class Rotation(models.Model):
|
class Colle(models.Model):
|
||||||
creneau = models.ForeignKey(Creneau, on_delete=models.CASCADE)
|
class Meta:
|
||||||
groupes = models.ManyToManyField(Groupe)
|
ordering = ["slot__term__cls", "slot__subject__description", "slot__colleur__name", "datetime"]
|
||||||
date = models.DateField()
|
|
||||||
|
|
||||||
def groupe_initial(self):
|
slot = models.ForeignKey(Slot, on_delete=models.CASCADE)
|
||||||
|
groups = models.ManyToManyField(Group, blank=True)
|
||||||
|
datetime = models.DateTimeField()
|
||||||
|
|
||||||
|
def initial_group(self):
|
||||||
"""
|
"""
|
||||||
Renvoie les étudiants inscrits à la colle sans prendre en compte les amendements.
|
Renvoie les étudiants inscrits à la colle sans prendre en compte les swaps.
|
||||||
"""
|
"""
|
||||||
return Etudiant.objects.filter(id__in=Appartenance.objects.filter(groupe__in=self.groupes.all()))
|
return Student.objects.filter(id__in=Member.objects.filter(groupe__in=self.groups.all()))
|
||||||
|
|
||||||
def groupe_effectif(self):
|
def final_group(self):
|
||||||
"""
|
"""
|
||||||
Renvoie les étudiants inscrits à la colle en tenant compte des amendements.
|
Renvoie les étudiants inscrits à la colle en tenant compte des swaps.
|
||||||
"""
|
"""
|
||||||
amendements=Amendement.objects.filter(rotation=self)
|
swaps = Swap.objects.filter(colle=self)
|
||||||
|
|
||||||
return Etudiant.objects.filter(
|
return Student.objects.filter(
|
||||||
( Q(id__in=Appartenance.objects.filter(groupe__in=self.groupes.all()))
|
(Q(id__in=Member.objects.filter(group__in=self.groups.all()))
|
||||||
| Q(id__in=amendements.filter(est_positif=True).values("etudiant_id")) )
|
| Q(id__in=swaps.filter(enroll=True).values("student_id")))
|
||||||
& ~Q(id__in=amendements.filter(est_positif=False).values("etudiant_id"))
|
& ~Q(id__in=swaps.filter(enroll=False).values("student_id"))
|
||||||
)
|
)
|
||||||
|
|
||||||
def effectif(self):
|
def is_attendee(self, student):
|
||||||
"""
|
return self.final_group().contains(student)
|
||||||
Renvoie le nombre d'étudiants inscrits à la colle en tenant compte des amendements.
|
|
||||||
"""
|
|
||||||
n_base = sum(len(groupe.membres.count()) for groupe in self.groupes.all())
|
|
||||||
n_plus = len(Amendement.objects.filter(est_positif=True, rotation=self))
|
|
||||||
n_moins = len(Amendement.objects.filter(est_positif=False, rotation=self))
|
|
||||||
|
|
||||||
return n_base + n_plus - n_moins
|
def get_volume(self):
|
||||||
|
"""
|
||||||
|
Renvoie le nombre d'étudiants inscrits à la colle en tenant compte des swaps.
|
||||||
|
"""
|
||||||
|
return (Student.objects
|
||||||
|
.filter(Q(groups__colle=self)
|
||||||
|
& ~Q(swap__colle=self, swap__enroll=False)
|
||||||
|
| Q(swap__colle=self, swap__enroll=True))
|
||||||
|
.distinct()
|
||||||
|
.count())
|
||||||
|
|
||||||
def est_pleine(self):
|
def is_full(self):
|
||||||
"""
|
"""
|
||||||
Renvoie si la colle est pleine.
|
Renvoie si la colle est pleine.
|
||||||
"""
|
"""
|
||||||
eff = self.effectif()
|
return self.get_volume() >= self.slot.capacity
|
||||||
return eff>=self.creneau.capacite
|
|
||||||
|
|
||||||
def est_modifiee(self):
|
def is_edited(self):
|
||||||
"""
|
"""
|
||||||
Renvoie si la colle a été amendée.
|
Renvoie si la colle a été amendée.
|
||||||
"""
|
"""
|
||||||
return Amendement.objects.filter(rotation=self).exists()
|
return Swap.objects.filter(colle=self).exists()
|
||||||
|
|
||||||
def amender(self, etudiant, est_positif):
|
def amend(self, student, enroll, notify=False):
|
||||||
"""
|
"""
|
||||||
Amende la colle en (des)inscrivant etudiant à la colle self, selon est_positif.
|
Amende la colle en (des)inscrivant student à la colle self, selon enroll.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if Amendement.objects.filter(rotation=self, etudiant=etudiant, est_positif=est_positif).exists():
|
if Swap.objects.filter(colle=self, student=student, enroll=enroll).exists():
|
||||||
raise Exception("Duplication")
|
raise Exception("Duplication")
|
||||||
elif Amendement.objects.filter(rotation=self, etudiant=etudiant, est_positif=not est_positif).exists():
|
elif Swap.objects.filter(colle=self, student=student, enroll=not enroll).exists():
|
||||||
# les amendements complémentaires s'annulent
|
# les swaps complémentaires s'annulent
|
||||||
Amendement.objects.get(rotation=self, etudiant=etudiant, est_positif=not est_positif).delete()
|
Swap.objects.get(colle=self, student=student, enroll=not enroll).delete()
|
||||||
elif est_positif and any(etudiant.appartient(groupe) for groupe in self.groupes.all()):
|
elif enroll and any(student.is_member(group) for group in self.groups.all()):
|
||||||
# on ne peut pas s'ajouter si on est dans le groupe de base
|
# on ne peut pas s'ajouter si on est dans le groupe de base
|
||||||
raise Exception("Vous êtes déjà dans le groupe")
|
raise Exception("Vous êtes déjà dans le groupe")
|
||||||
elif not est_positif and all(not etudiant.appartient(groupe) for groupe in self.groupes.all()):
|
elif not enroll and all(not student.is_member(group) for group in self.groups.all()):
|
||||||
raise Exception("Vous n'êtes pas dans le groupe")
|
raise Exception("Vous n'êtes pas dans le groupe")
|
||||||
elif est_positif and self.est_pleine():
|
elif enroll and self.is_full():
|
||||||
raise Exception("Capacité dépassée")
|
raise Exception("Capacité dépassée")
|
||||||
else:
|
else:
|
||||||
amendement = Amendement(rotation=self, etudiant=etudiant, est_positif=est_positif)
|
swap = Swap(colle=self, student=student, enroll=enroll)
|
||||||
amendement.save()
|
swap.save()
|
||||||
|
|
||||||
|
#if notify:
|
||||||
|
# func = async_to_sync(swap.notify)
|
||||||
|
# func()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.creneau} le {self.date} avec groupes {'+'.join(str(groupe) for groupe in self.groupes.all())}"
|
return f"Colle {self.slot.subject} ({self.slot.colleur}); {self.datetime} {self.slot.time} {self.slot.room}. Groupe(s) {{{'; '.join(str(groupe) for groupe in self.groups.all())}}}"
|
||||||
|
|
||||||
|
|
||||||
def datetime(self):
|
class Swap(models.Model):
|
||||||
return datetime.combine( self.date, self.creneau.heure, tzinfo=timezone("Europe/Paris") )
|
enroll = models.BooleanField()
|
||||||
|
colle = models.ForeignKey(Colle, on_delete=models.CASCADE)
|
||||||
|
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
async def notify(self):
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
webhook = Webhook.from_url(settings.DISCORD_NOTIFY_WEBHOOK_URL, session=session)
|
||||||
|
|
||||||
|
if self.enroll:
|
||||||
|
await webhook.send(f"Colle réservée : {self.colle}", username=settings.DISCORD_NOTIFY_WEBHOOK_USERNAME)
|
||||||
|
else:
|
||||||
|
await webhook.send(f"Colle libérée : {self.colle}", username=settings.DISCORD_NOTIFY_WEBHOOK_USERNAME)
|
||||||
|
|
||||||
|
|
||||||
class Amendement(models.Model):
|
class Profile(models.Model):
|
||||||
est_positif = models.BooleanField()
|
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||||
rotation = models.ForeignKey(Rotation, on_delete=models.CASCADE)
|
student = models.ForeignKey(Student, null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
etudiant = models.ForeignKey(Etudiant, on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
|
|
||||||
class Profil(models.Model):
|
|
||||||
utilisateur = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
|
||||||
etudiant = models.ForeignKey(Etudiant, null=True, blank=True, on_delete=models.SET_NULL)
|
|
||||||
colleur = models.ForeignKey(Colleur, null=True, blank=True, on_delete=models.SET_NULL)
|
colleur = models.ForeignKey(Colleur, null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Profil {self.utilisateur} : {self.etudiant} ; {self.colleur}"
|
return f"Profil {self.user} : {self.student} ; {self.colleur}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_request(request, preprocess=lambda query: query):
|
def from_request(request, preprocess=lambda query: query):
|
||||||
user = request.user
|
user = request.user
|
||||||
session = request.session
|
session = request.session
|
||||||
|
|
||||||
match session.get("profil"):
|
match session.get("profile"):
|
||||||
case "etudiant":
|
case "student":
|
||||||
profil = preprocess(Profil.objects.filter(utilisateur=user)).get()
|
profil = preprocess(Profile.objects.filter(user=user)).get()
|
||||||
return profil.etudiant
|
return profil.student
|
||||||
case "colleur":
|
case "colleur":
|
||||||
profil = preprocess(Profil.objects.filter(utilisateur=user)).get()
|
profil = preprocess(Profile.objects.filter(user=user)).get()
|
||||||
return profil.colleur
|
return profil.colleur
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("profil non choisi")
|
raise ValueError("profil non choisi")
|
||||||
|
|
||||||
|
|
||||||
class LienCalendrier(models.Model):
|
class CalendarLink(models.Model):
|
||||||
code = models.CharField(max_length=32, unique=True)
|
key = models.CharField(max_length=32, unique=True)
|
||||||
etudiant = models.ForeignKey(Etudiant, on_delete=models.CASCADE)
|
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
||||||
periode = models.ForeignKey(Periode, on_delete=models.CASCADE)
|
term = models.ForeignKey(Term, on_delete=models.CASCADE)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
fields=['etudiant', 'periode'], name='unique_etudiant_periode_combination'
|
fields=['student', 'term'], name='unique_student_term_combination'
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
valentin = Student.objects.get(pk=25)
|
||||||
|
term = Term.objects.get(pk=3)
|
||||||
|
colles = term.query_colles_of_student(valentin).order_by("-volume")
|
||||||
|
|
||||||
|
for c in colles:
|
||||||
|
print(f"* {c.slot} {c.volume} : {c.base_vol} + {c.swap_plus} - {c.swap_minus}")
|
|
@ -1,14 +1,16 @@
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from fpdf import FPDF
|
||||||
|
|
||||||
from colloscope.models import *
|
from colloscope.models import *
|
||||||
|
|
||||||
from fpdf import FPDF
|
|
||||||
|
|
||||||
|
|
||||||
class PDF(FPDF):
|
class PDF(FPDF):
|
||||||
def liste_eleves(self, periode):
|
def liste_eleves(self, term):
|
||||||
classe = periode.classe
|
cls = term.cls
|
||||||
etudiants = Etudiant.objects.filter(classe=classe)
|
students = Student.objects.filter(cls=cls)
|
||||||
|
|
||||||
with self.table(
|
with self.table(
|
||||||
align="RIGHT",
|
align="RIGHT",
|
||||||
|
@ -19,27 +21,27 @@ class PDF(FPDF):
|
||||||
for th in ("Nom", "Prénom", "Grp.", "TD",): #"LV1", "LV2"):
|
for th in ("Nom", "Prénom", "Grp.", "TD",): #"LV1", "LV2"):
|
||||||
header.cell(th)
|
header.cell(th)
|
||||||
|
|
||||||
for etu in etudiants:
|
for etu in students:
|
||||||
row = table.row()
|
row = table.row()
|
||||||
row.cell(etu.nom.upper()) # Nom
|
row.cell(etu.last_name.upper()) # Nom
|
||||||
row.cell(etu.prenom) # Prénom
|
row.cell(etu.first_name) # Prénom
|
||||||
row.cell(etu.groupe_de_colle(periode).libelle) # Groupe
|
row.cell(etu.colle_group(term).description) # Groupe
|
||||||
row.cell(etu.groupe_du_critere(periode, "td").libelle)
|
row.cell(etu.group_of_type(term, "td").description)
|
||||||
#row.cell("??") # LV1
|
#row.cell("??") # LV1
|
||||||
#row.cell("??") # LV2
|
#row.cell("??") # LV2
|
||||||
|
|
||||||
|
|
||||||
def table_colloscope(self, periode, heading=True, est_colle=True):
|
def table_colloscope(self, term, heading=True, type="colle"):
|
||||||
semaines = periode.range_semaines()
|
weeks = term.range_weeks()
|
||||||
lundis = [ periode.classe.date_debut_sem(n) for n in semaines ]
|
lundis = [term.cls.week_beginning_date(n) for n in weeks]
|
||||||
creneaux = Creneau.objects.filter(periode=periode, est_colle=est_colle)
|
slots = Slot.objects.filter(term=term, type__description=type)
|
||||||
jours = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
weekdays = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
||||||
|
|
||||||
with self.table(
|
with self.table(
|
||||||
align="LEFT",
|
align="LEFT",
|
||||||
width=190,
|
width=190,
|
||||||
line_height=3,
|
line_height=3,
|
||||||
col_widths=(2, 1, 1, 3, 1, *(1,)*len(semaines)),
|
col_widths=(2, 1, 1, 3, 1, *(1,)*len(weeks)),
|
||||||
num_heading_rows=2 if heading else 0,
|
num_heading_rows=2 if heading else 0,
|
||||||
first_row_as_headings=heading) as table:
|
first_row_as_headings=heading) as table:
|
||||||
|
|
||||||
|
@ -48,7 +50,7 @@ class PDF(FPDF):
|
||||||
for th in ("Matière", "Jour", "Heure", "Colleur", "Salle"):
|
for th in ("Matière", "Jour", "Heure", "Colleur", "Salle"):
|
||||||
header.cell(th, align="CENTER", rowspan=2)
|
header.cell(th, align="CENTER", rowspan=2)
|
||||||
|
|
||||||
for sem in semaines:
|
for sem in weeks:
|
||||||
header.cell(str(sem), align="CENTER")
|
header.cell(str(sem), align="CENTER")
|
||||||
|
|
||||||
header2 = table.row()
|
header2 = table.row()
|
||||||
|
@ -56,38 +58,38 @@ class PDF(FPDF):
|
||||||
header2.cell(lundi.strftime("%d/%m/%y"), align="CENTER")
|
header2.cell(lundi.strftime("%d/%m/%y"), align="CENTER")
|
||||||
|
|
||||||
|
|
||||||
for i, c in enumerate(creneaux):
|
for i, c in enumerate(slots):
|
||||||
matiere = c.matiere
|
subject = c.subject
|
||||||
jour = c.jour
|
day = c.day
|
||||||
heure = c.heure
|
time = c.time
|
||||||
colleur = c.colleur
|
colleur = c.colleur
|
||||||
salle = c.salle
|
room = c.room
|
||||||
|
|
||||||
row = table.row()
|
row = table.row()
|
||||||
row.cell(matiere.libelle)
|
row.cell(subject.description)
|
||||||
row.cell(jours[jour])
|
row.cell(weekdays[day])
|
||||||
row.cell(heure.strftime("%H:%M"))
|
row.cell(time.strftime("%H:%M"))
|
||||||
row.cell("{} {}".format("M." if colleur.civilite=="M" else "Mme", colleur.nom.upper()))
|
row.cell("{} {}".format("M." if colleur.gender=="M" else "Mme", colleur.name.upper()))
|
||||||
row.cell(salle)
|
row.cell(room)
|
||||||
|
|
||||||
for s in semaines:
|
for s in weeks:
|
||||||
lundi = periode.classe.date_debut_sem(s)
|
lundi = term.cls.week_beginning_date(s)
|
||||||
|
|
||||||
if Rotation.objects.filter(creneau=c, date__gte=lundi, date__lt=lundi+timedelta(weeks=1)).exists():
|
if Colle.objects.filter(slot=c, datetime__gte=lundi, datetime__lt=lundi + timedelta(weeks=1)).exists():
|
||||||
r = Rotation.objects.get(creneau=c, date__gte=lundi, date__lt=lundi+timedelta(weeks=1))
|
r = Colle.objects.get(slot=c, datetime__gte=lundi, datetime__lt=lundi + timedelta(weeks=1))
|
||||||
groupes = r.groupes
|
groups = r.groups
|
||||||
content = ", ".join(g.libelle for g in groupes.all())
|
content = ", ".join(g.description for g in groups.all())
|
||||||
|
|
||||||
with self.local_context(fill_color=(255, 100, 100) if r.est_modifiee() else None):
|
with self.local_context(fill_color=(255, 100, 100) if r.is_edited() else None):
|
||||||
row.cell(content, align="CENTER")
|
row.cell(content, align="CENTER")
|
||||||
else:
|
else:
|
||||||
row.cell()
|
row.cell()
|
||||||
|
|
||||||
def generate(periode):
|
def generate(term):
|
||||||
pdf = PDF(orientation="landscape", format="a4")
|
pdf = PDF(orientation="landscape", format="a4")
|
||||||
pdf.set_font("helvetica", size=6)
|
pdf.set_font("helvetica", size=6)
|
||||||
|
|
||||||
titre = f"Colloscope {periode.classe.libelle} {periode.libelle}"
|
titre = f"Colloscope {term.cls.description} {term.description}"
|
||||||
|
|
||||||
pdf.set_title(titre)
|
pdf.set_title(titre)
|
||||||
pdf.set_author("colles.mp2i-vms.fr")
|
pdf.set_author("colles.mp2i-vms.fr")
|
||||||
|
@ -97,39 +99,39 @@ def generate(periode):
|
||||||
pdf.set_line_width(0.1)
|
pdf.set_line_width(0.1)
|
||||||
base_y = pdf.t_margin + 10
|
base_y = pdf.t_margin + 10
|
||||||
pdf.set_y(base_y)
|
pdf.set_y(base_y)
|
||||||
pdf.liste_eleves(periode)
|
pdf.liste_eleves(term)
|
||||||
pdf.set_y(base_y)
|
pdf.set_y(base_y)
|
||||||
pdf.table_colloscope(periode)
|
pdf.table_colloscope(term)
|
||||||
pdf.y += 3
|
pdf.y += 3
|
||||||
pdf.table_colloscope(periode, heading=False, est_colle=False)
|
pdf.table_colloscope(term, heading=False, type="td")
|
||||||
|
|
||||||
return pdf
|
return pdf
|
||||||
|
|
||||||
|
|
||||||
def handle(request):
|
def handle(request):
|
||||||
try:
|
try:
|
||||||
etudiant = Profil.from_request(
|
student = Profile.from_request(
|
||||||
request,
|
request,
|
||||||
preprocess=lambda query: query \
|
preprocess=lambda query: query \
|
||||||
.select_related("etudiant__classe") \
|
.select_related("student__cls") \
|
||||||
.prefetch_related("etudiant__classe__periode_set")
|
.prefetch_related("student__cls__term_set")
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return redirect("colloscope.choix_profil")
|
return redirect("colloscope.select_profile")
|
||||||
|
|
||||||
if not isinstance(etudiant, Etudiant):
|
if not isinstance(student, Student):
|
||||||
return HttpResponse("pas encore supporté")
|
return HttpResponse("pas encore supporté")
|
||||||
|
|
||||||
|
|
||||||
periode_str = request.GET.get("periode")
|
term_str = request.GET.get("term")
|
||||||
if periode_str is None:
|
if term_str is None:
|
||||||
periode = etudiant.classe.periode_actuelle()
|
term = student.cls.current_term()
|
||||||
else:
|
else:
|
||||||
periode = Periode.objects.get(id=int(periode_str), classe=etudiant.classe)
|
term = Term.objects.get(id=int(term_str), cls=student.cls)
|
||||||
|
|
||||||
return generate(periode)
|
return generate(term)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
periode = Periode.objects.get(id=3)
|
term = Term.objects.get(id=3)
|
||||||
return generate(periode)
|
return generate(term)
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.serializers import ModelSerializer
|
||||||
|
|
||||||
|
from colloscope.models import *
|
||||||
|
|
||||||
|
|
||||||
|
class SchoolSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = School
|
||||||
|
fields = ["id", "uai", "description", "vacation"]
|
||||||
|
|
||||||
|
|
||||||
|
class StudentSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Student
|
||||||
|
fields = ["id", "cls", "first_name", "last_name", "groups"]
|
||||||
|
|
||||||
|
|
||||||
|
class ClassSerializer(ModelSerializer):
|
||||||
|
students = StudentSerializer(source="student_set", many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Class
|
||||||
|
fields = ["id", "school", "description", "year", "day_zero", "students"]
|
||||||
|
|
||||||
|
|
||||||
|
class TermSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Term
|
||||||
|
fields = ["id", "cls", "description", "begin", "end"]
|
||||||
|
|
||||||
|
|
||||||
|
class SubjectSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Subject
|
||||||
|
fields = ["id", "cls", "description", "code"]
|
||||||
|
|
||||||
|
|
||||||
|
class GroupTypeSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = GroupType
|
||||||
|
fields = ["id", "term", "description"]
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Group
|
||||||
|
fields = ["id", "term", "description", "members"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ColleurSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Colleur
|
||||||
|
fields = ["id", "gender", "name"]
|
||||||
|
|
||||||
|
|
||||||
|
class SlotSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Slot
|
||||||
|
fields = ["id", "term", "day", "time", "duration", "room", "subject", "colleur", "type", "capacity"]
|
||||||
|
|
||||||
|
|
||||||
|
class SwapSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Swap
|
||||||
|
fields = ["enroll", "colle", "student"]
|
||||||
|
|
||||||
|
|
||||||
|
class ColleSerializer(ModelSerializer):
|
||||||
|
base_vol = serializers.IntegerField()
|
||||||
|
volume = serializers.IntegerField()
|
||||||
|
slot = SlotSerializer()
|
||||||
|
swaps = SwapSerializer(source="swap_set", many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Colle
|
||||||
|
fields = ["id", "slot", "groups", "datetime", "base_vol", "volume", "swaps"]
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarLinkSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = CalendarLink
|
||||||
|
fields = ["id", "key", "student", "term"]
|
|
@ -2,8 +2,8 @@ from colloscope.models import *
|
||||||
|
|
||||||
def table_colloscope(periode, heading=True, est_colle=True):
|
def table_colloscope(periode, heading=True, est_colle=True):
|
||||||
semaines = periode.range_semaines()
|
semaines = periode.range_semaines()
|
||||||
lundis = [ periode.classe.date_debut_sem(n) for n in semaines ]
|
lundis = [periode.classe.week_beginning_date(n) for n in semaines]
|
||||||
creneaux = Creneau.objects.filter(periode=periode, est_colle=est_colle)
|
creneaux = Slot.objects.filter(periode=periode, est_colle=est_colle)
|
||||||
jours = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
jours = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
||||||
|
|
||||||
s = ""
|
s = ""
|
||||||
|
@ -28,26 +28,26 @@ def table_colloscope(periode, heading=True, est_colle=True):
|
||||||
s += "</tr>\n"
|
s += "</tr>\n"
|
||||||
|
|
||||||
for i, c in enumerate(creneaux):
|
for i, c in enumerate(creneaux):
|
||||||
matiere = c.matiere
|
matiere = c.subject
|
||||||
jour = c.jour
|
jour = c.jour
|
||||||
heure = c.heure
|
heure = c.time
|
||||||
colleur = c.colleur
|
colleur = c.colleur
|
||||||
salle = c.salle
|
salle = c.room
|
||||||
|
|
||||||
s += "<tr>\n"
|
s += "<tr>\n"
|
||||||
s += f"<td>{matiere.libelle}</td>\n"
|
s += f"<td>{matiere.description}</td>\n"
|
||||||
s += f"<td>{jours[jour]}</td>\n"
|
s += f"<td>{jours[jour]}</td>\n"
|
||||||
s += f"<td>{heure.strftime('%H:%M')}</td>\n"
|
s += f"<td>{heure.strftime('%H:%M')}</td>\n"
|
||||||
s += "<td>{} {}</td>\n".format("M." if colleur.civilite=="M" else "Mme", colleur.nom.upper())
|
s += "<td>{} {}</td>\n".format("M." if colleur.civilite=="M" else "Mme", colleur.nom.upper())
|
||||||
s += f"<td>salle</td>\n"
|
s += f"<td>salle</td>\n"
|
||||||
|
|
||||||
for sem in semaines:
|
for sem in semaines:
|
||||||
if Rotation.objects.filter(creneau=c, semaine=sem).exists():
|
if Colle.objects.filter(creneau=c, semaine=sem).exists():
|
||||||
r = Rotation.objects.get(creneau=c, semaine=sem)
|
r = Colle.objects.get(creneau=c, semaine=sem)
|
||||||
groupes = r.groupes
|
groupes = r.groupes
|
||||||
content = ", ".join(g.libelle for g in groupes.all())
|
content = ", ".join(g.description for g in groupes.all())
|
||||||
|
|
||||||
if r.est_modifiee():
|
if r.is_edited():
|
||||||
s += f"<td class='modif'>{content}</td>\n"
|
s += f"<td class='modif'>{content}</td>\n"
|
||||||
else:
|
else:
|
||||||
s += f"<td>{content}</td>\n"
|
s += f"<td>{content}</td>\n"
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% load extras %}
|
||||||
|
|
||||||
{% block title %}Tableau de bord{% endblock title %}
|
{% block title %}Tableau de bord{% endblock title %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
@ -7,23 +10,45 @@
|
||||||
<h1>Tableau de bord</h1>
|
<h1>Tableau de bord</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Bienvenue {{ etudiant }}. Votre lycée est {{ periode.classe.lycee.libelle }}, et votre classe est {{ periode.classe.libelle }}.
|
Bienvenue {{ student }}. Votre lycée est {{ term.cls.school.description }}, et votre classe est {{ term.cls.description }}.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>Période actuelle : {{ periode }}. Votre groupe de colle est {{ groupe }}. <a href="table.html">Consulter le colloscope</a></p>
|
<p>Période actuelle : {{ term }}. Votre groupe de colle est {{ group }}. <a href="table.html">Consulter le colloscope</a></p>
|
||||||
|
|
||||||
<h2>Mes colles</h2>
|
<h2>Mes colles</h2>
|
||||||
<a href="{{ lien_calendrier }}">Exporter en .ics (ceci est un permalien public)</a>
|
|
||||||
|
|
||||||
<ul>
|
<p><a href="{{ calendar_link }}"><i class="fa-regular fa-calendar"></i> Exporter en .ics (ceci est un permalien public)</a></p>
|
||||||
{% for n, lundi, colles in rotations %}
|
|
||||||
<li>Semaine {{n}} ({{lundi}})</li>
|
<p><a href="{% url "colloscope.marketplace" %}">Accéder au marketplace</a></p>
|
||||||
<ul>
|
|
||||||
|
|
||||||
|
{% for n, lundi, colles in colles_per_sem %}
|
||||||
|
|
||||||
|
{% if colles %}
|
||||||
|
<h3 class="week">Semaine {{n}} ({{lundi}})</h3>
|
||||||
|
<div class="colle-wrapper">
|
||||||
{% for colle in colles %}
|
{% for colle in colles %}
|
||||||
<li>{{colle}}</li>
|
<div class="colle">
|
||||||
{% endfor %}
|
<span class="summary">{{ colle.slot.subject }} ({{ colle.slot.colleur }})</span>
|
||||||
|
<ul>
|
||||||
|
<li><i class="fa-solid fa-clock"></i> Le {{ colle.datetime|date:"l" }} {{ colle.datetime|date:"DATETIME_FORMAT" }}</li>
|
||||||
|
<li><i class="fa-solid fa-users"></i> {{ colle.groups.all | print_manager | safe }} ({{ colle.volume }} / {{ colle.slot.capacity }})</li>
|
||||||
|
<li><i class="fa-solid fa-earth-americas"></i> {{ colle.slot.room }}</li>
|
||||||
|
<li><i class="fa-solid fa-circle-exclamation"></i>
|
||||||
|
<form
|
||||||
|
action="{% url "colloscope.withdraw" %}"
|
||||||
|
method="POST"
|
||||||
|
onsubmit="return confirm('Êtes-vous sûr de vouloir vous désinscrire de la colle {{ colle }} ');">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="colle_id" value="{{ colle.id }}">
|
||||||
|
<button type="submit">Rendre disponible</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
|
||||||
|
|
||||||
{% endblock main %}
|
{% endblock main %}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% load extras %}
|
||||||
|
|
||||||
|
{% block title %}Marketplace{% endblock title %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>Marketplace</h1>
|
||||||
|
|
||||||
|
Bienvenue sur le marketplace.
|
||||||
|
|
||||||
|
{% if colles %}
|
||||||
|
Les colles libres sont :
|
||||||
|
<div class="colle-wrapper">
|
||||||
|
{% for colle in colles %}
|
||||||
|
<div class="colle">
|
||||||
|
<span class="summary">{{ colle.slot.subject }} ({{ colle.slot.colleur }})</span>
|
||||||
|
<ul>
|
||||||
|
<li><i class="fa-solid fa-clock"></i> Le {{ colle.datetime|date:"l" }} {{ colle.datetime|date:"DATETIME_FORMAT" }}</li>
|
||||||
|
<li><i class="fa-solid fa-users"></i> {{ colle.groups.all | print_manager | safe }} ({{ colle.volume }} / {{ colle.slot.capacity }})</li>
|
||||||
|
<li><i class="fa-solid fa-earth-americas"></i> {{ colle.slot.room }}</li>
|
||||||
|
<li><i class="fa-solid fa-circle-exclamation"></i>
|
||||||
|
<form action="{% url "colloscope.enroll" %}"
|
||||||
|
method="POST"
|
||||||
|
onsubmit="return confirm('Êtes-vous sûr de vouloir vous inscrire à la colle {{ colle }} ');">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="colle_id" value="{{ colle.id }}">
|
||||||
|
<button type="submit">Réserver</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
Aucune colle n'est disponible
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock main %}
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
{% block title %}Sélection du profil{% endblock title %}
|
{% block title %}Sélection du profil{% endblock title %}
|
||||||
|
|
||||||
{% block header %}
|
|
||||||
<h1>Sélection du profil</h1>
|
|
||||||
{% endblock header %}
|
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
|
<h1>Sélection du profil</h1>
|
||||||
|
|
||||||
Vous êtes connecté. Votre compte correspond à deux profils :
|
Vous êtes connecté. Votre compte correspond à deux profils :
|
||||||
<ul>
|
<ul>
|
||||||
<li>en tant que colleur : {{ profil.colleur }} ; Classes : {{ profil.colleur.get_classes|join:"; " }}</li>
|
<li>en tant que colleur : {{ profil.colleur }} ; Classes : {{ profil.colleur.get_classes|join:"; " }}</li>
|
|
@ -9,24 +9,20 @@
|
||||||
<link href="{% static 'table.css' %}" rel="stylesheet" type="text/css">
|
<link href="{% static 'table.css' %}" rel="stylesheet" type="text/css">
|
||||||
{% endblock head %}
|
{% endblock head %}
|
||||||
|
|
||||||
{% block header %}
|
|
||||||
<h1>Colloscope</h1>
|
|
||||||
{% endblock header %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
<h1>Colloscope</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Lycée : {{ periode.classe.lycee.libelle }}. Classe : {{ periode.classe.libelle }}. <a href="dashboard.html">Retour au tableau de bord</a>
|
Lycée : {{ term.cls.school.description }}. Classe : {{ term.cls.description }}. <a href="dashboard.html">Retour au tableau de bord</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Colloscope : {{ periode.libelle }}</h2>
|
<h2>Colloscope : {{ term.description }}</h2>
|
||||||
|
|
||||||
<form method="get" action="{% url "colloscope.table" %}">
|
<form method="get" action="{% url "colloscope.table" %}">
|
||||||
Changer de période :
|
Changer de période :
|
||||||
<select name="periode" id="periode">
|
<select name="term" id="term">
|
||||||
{% for p in periode.classe.periode_set.all %}
|
{% for p in term.cls.term_set.all %}
|
||||||
{% if p.id == periode.id %}
|
{% if p.id == term.id %}
|
||||||
<option value="{{ p.id }}" selected>{{ p }}</option>
|
<option value="{{ p.id }}" selected>{{ p }}</option>
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value="{{ p.id }}" selected>{{ p }}</option>
|
<option value="{{ p.id }}" selected>{{ p }}</option>
|
||||||
|
@ -36,8 +32,8 @@
|
||||||
<button type="submit">Valider</button>
|
<button type="submit">Valider</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% if request.GET.periode %}
|
{% if request.GET.term %}
|
||||||
<a href="export.pdf?periode={{ request.GET.periode }}" target="_blank">Exporter le colloscope</a>
|
<a href="export.pdf?term={{ request.GET.term }}" target="_blank">Exporter le colloscope</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="export.pdf" target="_blank">Exporter le colloscope</a>
|
<a href="export.pdf" target="_blank">Exporter le colloscope</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -53,7 +49,7 @@
|
||||||
<col>
|
<col>
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
{% for _ in semaines %}
|
{% for _ in weeks %}
|
||||||
<col>
|
<col>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</colgroup>
|
</colgroup>
|
||||||
|
@ -64,32 +60,32 @@
|
||||||
<th rowspan=2>Heure</th>
|
<th rowspan=2>Heure</th>
|
||||||
<th rowspan=2>Colleur</th>
|
<th rowspan=2>Colleur</th>
|
||||||
<th rowspan=2>Salle</th>
|
<th rowspan=2>Salle</th>
|
||||||
{% for n in semaines %}
|
{% for n in weeks %}
|
||||||
<th>{{ n }}</th>
|
<th>{{ n }}</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
{% for lundi in lundis %}
|
{% for monday in mondays %}
|
||||||
<th>{{ lundi | strftime:"%d/%m/%y" }}</th>
|
<th>{{ monday | strftime:"%d/%m/%y" }}</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
{% for c, rs in rotations %}
|
{% for c, rs in colles %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ c.matiere.libelle }}</td>
|
<td>{{ c.subject.description }}</td>
|
||||||
<td>{{ jours | getitem:c.jour }}</td>
|
<td>{{ days | getitem:c.day }}</td>
|
||||||
<td>{{ c.heure | strftime:"%H:%M" }}</td>
|
<td>{{ c.time | strftime:"%H:%M" }}</td>
|
||||||
<td>{{ c.colleur }}</td>
|
<td>{{ c.colleur }}</td>
|
||||||
<td>{{ c.salle }}</td>
|
<td>{{ c.room }}</td>
|
||||||
|
|
||||||
{% for sem, exists, r, est_modifiee, groupes in rs %}
|
{% for sem, exists, r, is_edited, groups in rs %}
|
||||||
{% if exists %}
|
{% if exists %}
|
||||||
{% if est_modifiee %}
|
{% if is_edited %}
|
||||||
<td class="modif">{{ groupes | join:"," }}</td>
|
<td class="modif">{{ groups | join:"," }}</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td>{{ groupes | join:"," }}</td>
|
<td>{{ groups | join:"," }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<td></td>
|
<td></td>
|
||||||
|
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
{% block title %}Sélection du profil{% endblock title %}
|
{% block title %}Sélection du profil{% endblock title %}
|
||||||
|
|
||||||
{% block header %}
|
|
||||||
<h1>Sélection du profil</h1>
|
|
||||||
{% endblock header %}
|
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
|
<h1>Sélection du profil</h1>
|
||||||
|
|
||||||
Vous êtes connecté, mais votre compte n'est associé à aucun profil. Veuillez contacter le webmestre à l'adresse <a href="mailto:valentin@mp2i-vms.fr">valentin@mp2i-vms.fr</a>.
|
Vous êtes connecté, mais votre compte n'est associé à aucun profil. Veuillez contacter le webmestre à l'adresse <a href="mailto:valentin@mp2i-vms.fr">valentin@mp2i-vms.fr</a>.
|
||||||
|
|
||||||
{% endblock main %}
|
{% endblock main %}
|
|
@ -4,14 +4,25 @@ from colloscope.models import *
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name="strftime")
|
@register.filter(name="strftime")
|
||||||
def strftime(value, arg):
|
def strftime(value, arg):
|
||||||
return value.strftime(arg)
|
return value.strftime(arg)
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name="getitem")
|
@register.filter(name="getitem")
|
||||||
def getitem(indexable, i):
|
def getitem(indexable, i):
|
||||||
return indexable[i]
|
return indexable[i]
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="print_manager")
|
||||||
|
def print_manager(value):
|
||||||
|
if value.exists():
|
||||||
|
return "+".join(str(v) for v in value)
|
||||||
|
else:
|
||||||
|
return "∅"
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@register.filter(name="exists")
|
@register.filter(name="exists")
|
||||||
def exists(queryset):
|
def exists(queryset):
|
||||||
|
|
|
@ -6,6 +6,9 @@ urlpatterns = [
|
||||||
path("table.html", views.colloscope, name="colloscope.table"),
|
path("table.html", views.colloscope, name="colloscope.table"),
|
||||||
path("dashboard.html", views.dashboard, name="colloscope.dashboard"),
|
path("dashboard.html", views.dashboard, name="colloscope.dashboard"),
|
||||||
path("export.pdf", views.export, name="colloscope.export"),
|
path("export.pdf", views.export, name="colloscope.export"),
|
||||||
path("calendrier.ics", views.icalendar, name="colloscope.calendrier"),
|
path("export/calendar/<str:key>/calendar.ics", views.icalendar, name="colloscope.calendar.ics"),
|
||||||
path("choix_profil", views.choix_profil, name="colloscope.choix_profil"),
|
path("select_profile", views.select_profile, name="colloscope.select_profile"),
|
||||||
|
path("marketplace.html", views.marketplace, name="colloscope.marketplace"),
|
||||||
|
path("action/enroll", views.enroll, name="colloscope.enroll"),
|
||||||
|
path("action/withdraw", views.withdraw, name="colloscope.withdraw"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
from datetime import date, timedelta
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.shortcuts import redirect
|
from django import forms
|
||||||
from django.http import HttpResponse
|
from django.shortcuts import redirect, render
|
||||||
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
from colloscope.models import *
|
from colloscope.models import *
|
||||||
from colloscope.table import table_colloscope
|
|
||||||
from colloscope.pdfexport import handle
|
from colloscope.pdfexport import handle
|
||||||
from colloscope.icalexport import to_calendar
|
from colloscope.icalexport import to_calendar
|
||||||
|
|
||||||
|
|
||||||
def handler404(request):
|
def handler404(request):
|
||||||
template = loader.get_template("404.html")
|
template = loader.get_template("404.html")
|
||||||
#response.status_code = 404
|
|
||||||
context = {}
|
context = {}
|
||||||
return HttpResponse(template.render(context))
|
return HttpResponse(template.render(context), status=404)
|
||||||
|
|
||||||
|
|
||||||
def home_redirect(request):
|
def home_redirect(request):
|
||||||
|
@ -24,143 +23,182 @@ def home_redirect(request):
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def choix_profil(request):
|
def select_profile(request):
|
||||||
user = request.user
|
user = request.user
|
||||||
session = request.session
|
session = request.session
|
||||||
|
|
||||||
if not Profil.objects.filter(utilisateur=user).exists():
|
if not Profile.objects.filter(user=user).exists():
|
||||||
profil = Profil(utilisateur=user)
|
profile = Profile(user=user)
|
||||||
profil.save()
|
profile.save()
|
||||||
else:
|
else:
|
||||||
profil = Profil.objects.get(utilisateur=user)
|
profile = Profile.objects.get(user=user)
|
||||||
|
|
||||||
|
if profile.student is not None and profile.colleur is None:
|
||||||
if profil.etudiant is not None and profil.colleur is None:
|
session["profile"] = "student"
|
||||||
session["profil"] = "etudiant"
|
|
||||||
return redirect("/colloscope/")
|
return redirect("/colloscope/")
|
||||||
elif profil.colleur is not None and profil.etudiant is None:
|
elif profile.colleur is not None and profile.student is None:
|
||||||
session["profil"] = "colleur"
|
session["profile"] = "colleur"
|
||||||
return redirect("/colloscope/")
|
return redirect("/colloscope/")
|
||||||
else:
|
else:
|
||||||
if profil.etudiant is not None:
|
if profile.student is not None:
|
||||||
template = loader.get_template("choix_profil.html")
|
template = loader.get_template("select_profile.html")
|
||||||
else:
|
else:
|
||||||
template = loader.get_template("profil_non_associe.html")
|
template = loader.get_template("unbound_profile.html")
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"profil": profil,
|
"profile": profile,
|
||||||
}
|
}
|
||||||
return HttpResponse(template.render(context))
|
return HttpResponse(template.render(context))
|
||||||
|
|
||||||
|
|
||||||
def get_lien_calendrier(etudiant, periode):
|
def get_lien_calendrier(student, term):
|
||||||
try:
|
try:
|
||||||
lien = LienCalendrier.objects.get(etudiant=etudiant, periode=periode)
|
lien = CalendarLink.objects.get(student=student, term=term)
|
||||||
except LienCalendrier.DoesNotExist:
|
except CalendarLink.DoesNotExist:
|
||||||
code = uuid4().hex
|
key = uuid4().hex
|
||||||
lien = LienCalendrier(code=code, etudiant=etudiant, periode=periode)
|
lien = CalendarLink(key=key, student=student, term=term)
|
||||||
lien.save()
|
lien.save()
|
||||||
|
|
||||||
return f"calendrier.ics?key={lien.code}"
|
return f"calendrier.ics?key={lien.key}"
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def dashboard(request):
|
def dashboard(request):
|
||||||
user = request.user
|
|
||||||
session = request.session
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
etudiant = Profil.from_request(
|
student = Profile.from_request(
|
||||||
request,
|
request,
|
||||||
preprocess=lambda query: query \
|
preprocess=lambda query: (query
|
||||||
.select_related("etudiant__classe") \
|
.select_related("student__cls")
|
||||||
.prefetch_related("etudiant__classe__periode_set")
|
.prefetch_related("student__cls__term_set"))
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return redirect("colloscope.choix_profil")
|
return redirect("colloscope.select_profile")
|
||||||
|
|
||||||
if not isinstance(etudiant, Etudiant):
|
if not isinstance(student, Student):
|
||||||
return HttpResponse("pas encore supporté")
|
return HttpResponse("pas encore supporté")
|
||||||
|
|
||||||
periode = etudiant.classe.periode_actuelle()
|
term = student.cls.current_term()
|
||||||
groupe = etudiant.groupe_de_colle(periode)
|
group = student.colle_group(term)
|
||||||
rotations = groupe.get_colles_par_sem()
|
|
||||||
|
colles = term.query_colles_of_student(student)
|
||||||
|
|
||||||
|
colles_per_sem = [None] * len(term.range_weeks())
|
||||||
|
for k, n in enumerate(term.range_weeks()):
|
||||||
|
lundi = term.cls.week_beginning_date(n)
|
||||||
|
colles_per_sem[k] = n, lundi, colles.filter(datetime__gte=max(lundi, date.today()),
|
||||||
|
datetime__lt=lundi + timedelta(weeks=1))
|
||||||
|
|
||||||
template = loader.get_template("dashboard.html")
|
template = loader.get_template("dashboard.html")
|
||||||
lien_calendrier = get_lien_calendrier(etudiant, periode)
|
calendar_link = get_calendar_link(student, term)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"etudiant": etudiant,
|
"student": student,
|
||||||
"periode": periode,
|
"term": term,
|
||||||
"groupe": groupe,
|
"group": group,
|
||||||
"rotations" : rotations,
|
"colles_per_sem": colles_per_sem,
|
||||||
"lien_calendrier": lien_calendrier,
|
"calendar_link": calendar_link,
|
||||||
}
|
}
|
||||||
|
|
||||||
return HttpResponse(template.render(context, request))
|
return HttpResponse(template.render(context, request))
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
class AmendForm(forms.Form):
|
||||||
def colloscope(request):
|
colle_id = forms.IntegerField(widget=forms.HiddenInput(), required=True)
|
||||||
user = request.user
|
|
||||||
session = request.session
|
|
||||||
|
|
||||||
|
|
||||||
|
class EnrollForm(AmendForm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WithdrawForm(AmendForm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def marketplace(request):
|
||||||
try:
|
try:
|
||||||
etudiant = Profil.from_request(
|
student = Profile.from_request(
|
||||||
request,
|
request,
|
||||||
preprocess=lambda query: query \
|
preprocess=lambda query: (query
|
||||||
.select_related("etudiant__classe") \
|
.select_related("student__cls")
|
||||||
.prefetch_related("etudiant__classe__periode_set")
|
.prefetch_related("student__cls__term_set"))
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return redirect("colloscope.choix_profil")
|
return redirect("colloscope.select_profile")
|
||||||
|
|
||||||
if not isinstance(etudiant, Etudiant):
|
if not isinstance(student, Student):
|
||||||
return HttpResponse("pas encore supporté")
|
return HttpResponse("pas encore supporté")
|
||||||
|
|
||||||
|
term = student.cls.current_term()
|
||||||
|
colles = term.query_colles_not_full_excluding_student(student)
|
||||||
|
|
||||||
periode_str = request.GET.get("periode")
|
context = {
|
||||||
if periode_str is None:
|
"colles": colles,
|
||||||
periode = etudiant.classe.periode_actuelle()
|
}
|
||||||
|
|
||||||
|
return render(request, "marketplace.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def colloscope(request):
|
||||||
|
try:
|
||||||
|
student = Profile.from_request(
|
||||||
|
request,
|
||||||
|
preprocess=lambda query: (query
|
||||||
|
.select_related("student__cls")
|
||||||
|
.prefetch_related("student__cls__term_set"))
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
return redirect("colloscope.select_profile")
|
||||||
|
|
||||||
|
if not isinstance(student, Student):
|
||||||
|
return HttpResponse("pas encore supporté")
|
||||||
|
|
||||||
|
term_str = request.GET.get("term")
|
||||||
|
if term_str is None:
|
||||||
|
term = student.cls.current_term()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
periode = Periode.objects.get(id=int(periode_str), classe=etudiant.classe)
|
term = Term.objects.get(id=int(term_str), cls=student.cls)
|
||||||
except Periode.DoesNotExist:
|
except Term.DoesNotExist:
|
||||||
template = loader.get_template("404.html")
|
template = loader.get_template("404.html")
|
||||||
context = {}
|
context = {}
|
||||||
response = HttpResponse(template.render(context, request))
|
response = HttpResponse(template.render(context, request))
|
||||||
response.status_code = 404
|
response.status_code = 404
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
creneaux = Creneau.objects \
|
slots = (Slot.objects
|
||||||
.filter(periode=periode, est_colle=True) \
|
.filter(term=term, type__description="colle")
|
||||||
.prefetch_related("rotation_set")
|
.order_by()
|
||||||
|
.prefetch_related("colle_set"))
|
||||||
|
|
||||||
semaines = periode.range_semaines()
|
weeks = term.range_weeks()
|
||||||
rotations = [ (c, []) for c in creneaux ]
|
colles = [(c, []) for c in slots]
|
||||||
for c, l in rotations:
|
for c, l in colles:
|
||||||
for sem in semaines:
|
for sem in weeks:
|
||||||
lundi = periode.classe.date_debut_sem(sem)
|
lundi = term.cls.week_beginning_date(sem)
|
||||||
|
|
||||||
rot = Rotation.objects.filter(creneau=c, date__gte=lundi, date__lt=lundi+timedelta(weeks=1))
|
rot = Colle.objects.filter(slot=c, datetime__gte=lundi, datetime__lt=lundi + timedelta(weeks=1))
|
||||||
exists = rot.exists()
|
exists = rot.exists()
|
||||||
|
|
||||||
if exists:
|
if exists:
|
||||||
r = rot.first()
|
r = rot.first()
|
||||||
est_modifiee = r.est_modifiee()
|
is_edited = r.is_edited()
|
||||||
groupes = (g.libelle for g in r.groupes.all())
|
groups = (g.description for g in r.groups.all())
|
||||||
else:
|
else:
|
||||||
r = est_modifiee = groupes = None
|
r = is_edited = groups = None
|
||||||
|
|
||||||
l.append((sem, exists, r, est_modifiee, groupes))
|
l.append((sem, exists, r, is_edited, groups))
|
||||||
|
|
||||||
template = loader.get_template("table.html")
|
template = loader.get_template("table.html")
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"periode": periode,
|
"term": term,
|
||||||
"semaines": semaines,
|
"weeks": weeks,
|
||||||
"lundis": [periode.classe.date_debut_sem(n) for n in semaines],
|
"mondays": [term.cls.week_beginning_date(n) for n in weeks],
|
||||||
"jours" : ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
|
"days": ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
|
||||||
"rotations" : rotations,
|
"colles": colles,
|
||||||
}
|
}
|
||||||
|
|
||||||
return HttpResponse(template.render(context, request))
|
return HttpResponse(template.render(context, request))
|
||||||
|
@ -171,27 +209,82 @@ def export(request):
|
||||||
return HttpResponse(bytes(handle(request).output()), content_type="application/pdf")
|
return HttpResponse(bytes(handle(request).output()), content_type="application/pdf")
|
||||||
|
|
||||||
|
|
||||||
def get_lien_calendrier(etudiant, periode):
|
def get_calendar_link(student, term):
|
||||||
try:
|
try:
|
||||||
lien = LienCalendrier.objects.get(etudiant=etudiant, periode=periode)
|
lien = CalendarLink.objects.get(student=student, term=term)
|
||||||
except LienCalendrier.DoesNotExist:
|
except CalendarLink.DoesNotExist:
|
||||||
code = uuid4().hex
|
key = uuid4().hex
|
||||||
lien = LienCalendrier(code=code, etudiant=etudiant, periode=periode)
|
lien = CalendarLink(key=key, student=student, term=term)
|
||||||
lien.save()
|
lien.save()
|
||||||
|
|
||||||
return f"calendrier.ics?key={lien.code}"
|
return f"export/calendar/{lien.key}/calendar.ics"
|
||||||
|
|
||||||
def icalendar(request):
|
|
||||||
if request.GET.get("key") is not None:
|
def icalendar(request, key):
|
||||||
try:
|
try:
|
||||||
lien = LienCalendrier.objects.get(code=request.GET.get("key"))
|
link = CalendarLink.objects.get(key=key)
|
||||||
|
|
||||||
return HttpResponse(to_calendar(lien.etudiant, lien.periode).to_ical(), content_type="text/calendar")
|
if not request.GET.get("edt"):
|
||||||
except LienCalendrier.DoesNotExist:
|
return HttpResponse(to_calendar(link.student, link.term, include_EDT=True).to_ical(),
|
||||||
|
content_type="text/calendar")
|
||||||
|
|
||||||
|
return HttpResponse(to_calendar(link.student, link.term, include_EDT=True).to_ical(),
|
||||||
|
content_type="text/calendar")
|
||||||
|
|
||||||
|
except CalendarLink.DoesNotExist:
|
||||||
return HttpResponse("Invalid key", status=404)
|
return HttpResponse("Invalid key", status=404)
|
||||||
else:
|
|
||||||
return HttpResponse("Unspecified key", status=404)
|
|
||||||
|
|
||||||
def data_dump(request):
|
|
||||||
template = loader.get_template("data_dump.html")
|
def amend(request, colle_id, do_enroll):
|
||||||
return HttpResponse(template.render())
|
try:
|
||||||
|
student = Profile.from_request(
|
||||||
|
request,
|
||||||
|
preprocess=lambda query: (query
|
||||||
|
.select_related("student__cls")
|
||||||
|
.prefetch_related("student__cls__term_set"))
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
return redirect("colloscope.choix_profil")
|
||||||
|
|
||||||
|
if not isinstance(student, Student):
|
||||||
|
return HttpResponse("pas encore supporté")
|
||||||
|
|
||||||
|
if do_enroll:
|
||||||
|
(Colle.objects
|
||||||
|
.get(id=colle_id, slot__term__cls=student.cls)
|
||||||
|
.amend(enroll=True, student=student, notify=True))
|
||||||
|
else:
|
||||||
|
colle = Colle.objects.get(id=colle_id)
|
||||||
|
|
||||||
|
if colle.is_attendee(student):
|
||||||
|
colle.amend(enroll=False, student=student, notify=True)
|
||||||
|
else:
|
||||||
|
raise Exception("vous n'êtes pas dans la colle...")
|
||||||
|
|
||||||
|
|
||||||
|
@require_POST
|
||||||
|
@login_required
|
||||||
|
def enroll(request):
|
||||||
|
form = WithdrawForm(request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
colle_id = form.cleaned_data["colle_id"]
|
||||||
|
amend(request, colle_id, True)
|
||||||
|
else:
|
||||||
|
print("!!!! invalide")
|
||||||
|
|
||||||
|
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
|
||||||
|
|
||||||
|
|
||||||
|
@require_POST
|
||||||
|
@login_required
|
||||||
|
def withdraw(request):
|
||||||
|
form = WithdrawForm(request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
colle_id = form.cleaned_data["colle_id"]
|
||||||
|
amend(request, colle_id, False)
|
||||||
|
else:
|
||||||
|
print("!!!! invalide")
|
||||||
|
|
||||||
|
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||||
|
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
|
from colloscope.models import *
|
||||||
|
from colloscope.serializers import *
|
||||||
|
|
||||||
|
|
||||||
|
class SchoolViewset(ReadOnlyModelViewSet):
|
||||||
|
serializer_class = SchoolSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return School.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ClassViewset(ReadOnlyModelViewSet):
|
||||||
|
serializer_class = ClassSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Class.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class TermViewset(ReadOnlyModelViewSet):
|
||||||
|
serializer_class = TermSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Term.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class SubjectViewset(ReadOnlyModelViewSet):
|
||||||
|
serializer_class = SubjectSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Subject.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class GroupTypeViewset(ReadOnlyModelViewSet):
|
||||||
|
serializer_class = GroupTypeSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return GroupType.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class GroupViewset(ReadOnlyModelViewSet):
|
||||||
|
serializer_class = GroupSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Group.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class StudentViewset(ReadOnlyModelViewSet):
|
||||||
|
serializer_class = StudentSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Student.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ColleurViewset(ReadOnlyModelViewSet):
|
||||||
|
serializer_class = ColleurSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Colleur.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class SlotViewset(ReadOnlyModelViewSet):
|
||||||
|
serializer_class = SlotSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Slot.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class ColleViewset(ModelViewSet):
|
||||||
|
serializer_class = ColleSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return (Colle.objects
|
||||||
|
.select_related("slot", "slot__term")
|
||||||
|
.prefetch_related("swap_set")
|
||||||
|
.annotate(base_vol=Count("groups__members"))
|
||||||
|
.annotate(swap_plus=Count("swap", filter=Q(swap__enroll=1), distinct=True))
|
||||||
|
.annotate(swap_minus=Count("swap", filter=Q(swap__enroll=0), distinct=True))
|
||||||
|
.annotate(volume=F("base_vol") + F("swap_plus") - F("swap_minus"))
|
||||||
|
.order_by("datetime", "slot__time"))
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarLinkViewset(ReadOnlyModelViewSet):
|
||||||
|
serializer_class = CalendarLinkSerializer
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return CalendarLink.objects.all()
|
|
@ -1,147 +0,0 @@
|
||||||
"""
|
|
||||||
Django settings for kholles_web project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 5.0.4.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/5.0/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/5.0/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = 'django-insecure-$)@!wj+$^y1@^tr78ay&)cna10da_k^vncrbo+4ja-qth$8bhz'
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["127.0.0.1", "colles.mp2i-vms.fr"]
|
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [
|
|
||||||
"http://127.0.0.1:8000",
|
|
||||||
"https://colles.mp2i-vms.fr"
|
|
||||||
]
|
|
||||||
|
|
||||||
CORS_ORIGIN_WHITELIST = [
|
|
||||||
"http://localhost:8000",
|
|
||||||
"https://colles.mp2i-vms.fr"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'colloscope',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'kholles_web.urls'
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [ BASE_DIR / "templates" ],
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'kholles_web.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': BASE_DIR / 'db.sqlite3',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'fr-fr'
|
|
||||||
|
|
||||||
TIME_ZONE = 'Europe/Paris'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
|
||||||
|
|
||||||
STATICFILES_DIRS = [
|
|
||||||
BASE_DIR / "static",
|
|
||||||
]
|
|
||||||
|
|
||||||
STATICFILES_FINDERS = [
|
|
||||||
"django.contrib.staticfiles.finders.FileSystemFinder",
|
|
||||||
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Default primary key field type
|
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
||||||
|
|
||||||
LOGIN_URL = "/comptes/login"
|
|
||||||
LOGIN_REDIRECT_URL = "home"
|
|
||||||
LOGOUT_REDIRECT_URL = "home"
|
|
|
@ -16,12 +16,46 @@ Including another URLconf
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin, auth
|
from django.contrib import admin, auth
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.contrib.staticfiles import views as vstatic
|
||||||
|
from rest_framework import routers
|
||||||
|
from rest_framework_simplejwt.views import (
|
||||||
|
TokenObtainPairView,
|
||||||
|
TokenRefreshView,
|
||||||
|
)
|
||||||
|
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
||||||
|
|
||||||
from colloscope.views import home_redirect
|
from colloscope.views import home_redirect
|
||||||
|
from colloscope.viewsets import *
|
||||||
|
|
||||||
|
router = routers.SimpleRouter()
|
||||||
|
router.register('school', SchoolViewset, basename='school')
|
||||||
|
router.register('class', ClassViewset, basename='class')
|
||||||
|
router.register('term', TermViewset, basename='term')
|
||||||
|
router.register("subject", SubjectViewset, basename='subject')
|
||||||
|
router.register('grouptype', GroupTypeViewset, basename='grouptype')
|
||||||
|
router.register("group", GroupViewset, basename='group')
|
||||||
|
router.register("student", StudentViewset, basename='student')
|
||||||
|
router.register("colleur", ColleurViewset, basename='colleur')
|
||||||
|
router.register("slot", SlotViewset, basename='slot')
|
||||||
|
router.register("colle", ColleViewset, basename='colle')
|
||||||
|
router.register("calendarlink", CalendarLinkViewset, basename='calendarlink')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', home_redirect, name="home"),
|
path('', home_redirect, name="home"),
|
||||||
|
path('api-auth/', include('rest_framework.urls')),
|
||||||
|
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
|
||||||
|
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
|
||||||
|
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||||
|
path('api/documentation/', SpectacularSwaggerView.as_view(url_name='schema'), name='api-doc'),
|
||||||
|
path("api/", lambda request: redirect("api-doc")),
|
||||||
|
path("api/doc/", lambda request: redirect("api-doc")),
|
||||||
|
path("api/", include(router.urls)),
|
||||||
|
|
||||||
|
path("oauth2/", include('oauth2_provider.urls', namespace='oauth2_provider')),
|
||||||
|
path("favicon.ico", lambda req: vstatic.serve(req, "favicon.ico")),
|
||||||
path('colloscope/', include('colloscope.urls')),
|
path('colloscope/', include('colloscope.urls')),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('comptes/', include("django.contrib.auth.urls")),
|
path('accounts/', include("django.contrib.auth.urls")),
|
||||||
]
|
]
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,60 @@
|
||||||
|
aiohttp==3.9.5
|
||||||
|
aiosignal==1.3.1
|
||||||
|
asgiref==3.8.1
|
||||||
|
attrs==23.2.0
|
||||||
|
autobahn==23.6.2
|
||||||
|
Automat==22.10.0
|
||||||
|
certifi==2024.2.2
|
||||||
|
cffi==1.16.0
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
constantly==23.10.4
|
||||||
|
cryptography==42.0.5
|
||||||
|
daphne==4.1.2
|
||||||
|
defusedxml==0.7.1
|
||||||
|
discord.py==2.3.2
|
||||||
|
Django==5.0.4
|
||||||
|
django-cors-headers==4.3.1
|
||||||
|
django-oauth-toolkit==2.3.0
|
||||||
|
django-smtp-ssl==1.0
|
||||||
|
djangorestframework==3.15.1
|
||||||
|
djangorestframework-simplejwt==5.3.1
|
||||||
|
drf-spectacular==0.27.2
|
||||||
|
fonttools==4.51.0
|
||||||
|
fpdf2==2.7.8
|
||||||
|
frozenlist==1.4.1
|
||||||
|
hyperlink==21.0.0
|
||||||
|
icalendar==5.0.12
|
||||||
|
idna==3.7
|
||||||
|
incremental==22.10.0
|
||||||
|
inflection==0.5.1
|
||||||
|
jsonschema==4.22.0
|
||||||
|
jsonschema-specifications==2023.12.1
|
||||||
|
jwcrypto==1.5.6
|
||||||
|
multidict==6.0.5
|
||||||
|
numpy==1.26.4
|
||||||
|
oauthlib==3.2.2
|
||||||
|
pandas==2.2.2
|
||||||
|
pillow==10.3.0
|
||||||
|
pyasn1==0.6.0
|
||||||
|
pyasn1_modules==0.4.0
|
||||||
|
pycparser==2.22
|
||||||
|
PyJWT==2.8.0
|
||||||
|
pyOpenSSL==24.1.0
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
pytz==2024.1
|
||||||
|
PyYAML==6.0.1
|
||||||
|
referencing==0.35.1
|
||||||
|
requests==2.31.0
|
||||||
|
rpds-py==0.18.0
|
||||||
|
service-identity==24.1.0
|
||||||
|
setuptools==69.5.1
|
||||||
|
six==1.16.0
|
||||||
|
sqlparse==0.4.4
|
||||||
|
Twisted==24.3.0
|
||||||
|
txaio==23.1.1
|
||||||
|
typing_extensions==4.11.0
|
||||||
|
tzdata==2024.1
|
||||||
|
uritemplate==4.1.1
|
||||||
|
urllib3==2.2.1
|
||||||
|
yarl==1.9.4
|
||||||
|
zope.interface==6.3
|
|
@ -20,11 +20,11 @@ def scrape(periode, chemin):
|
||||||
else:
|
else:
|
||||||
c = Colleur.objects.get(nom=nom_colleur, civilite=civilite)
|
c = Colleur.objects.get(nom=nom_colleur, civilite=civilite)
|
||||||
|
|
||||||
if not Matiere.objects.filter(classe=periode.classe, libelle=matiere).exists():
|
if not Subject.objects.filter(classe=periode.classe, libelle=matiere).exists():
|
||||||
m = Matiere(classe=periode.classe, libelle=matiere, code=matiere.upper())
|
m = Subject(classe=periode.classe, libelle=matiere, code=matiere.upper())
|
||||||
m.save()
|
m.save()
|
||||||
else:
|
else:
|
||||||
m = Matiere.objects.get(classe=periode.classe, libelle=matiere)
|
m = Subject.objects.get(classe=periode.classe, libelle=matiere)
|
||||||
|
|
||||||
jours_dict = {"dimanche": 0, "lundi": 1, "mardi": 2, "mercredi": 3, "jeudi": 4, "vendredi": 5, "samedi": 6}
|
jours_dict = {"dimanche": 0, "lundi": 1, "mardi": 2, "mercredi": 3, "jeudi": 4, "vendredi": 5, "samedi": 6}
|
||||||
j = jours_dict[jour]
|
j = jours_dict[jour]
|
||||||
|
@ -40,11 +40,11 @@ def scrape(periode, chemin):
|
||||||
|
|
||||||
print(f"--> Traitement de {c=}, {m=}, {j=}, {h=}, {d=}, {c2=}")
|
print(f"--> Traitement de {c=}, {m=}, {j=}, {h=}, {d=}, {c2=}")
|
||||||
|
|
||||||
if not Creneau.objects.filter(periode=periode, jour=j, heure=h, duree=d, matiere=m, colleur=c, est_colle=True, capacite=c2).exists():
|
if not Slot.objects.filter(periode=periode, jour=j, heure=h, duree=d, matiere=m, colleur=c, est_colle=True, capacite=c2).exists():
|
||||||
creneau = Creneau(periode=periode, jour=j, heure=h, duree=d, salle="nc", matiere=m, colleur=c, est_colle=True, capacite=c2)
|
creneau = Slot(periode=periode, jour=j, heure=h, duree=d, salle="nc", matiere=m, colleur=c, est_colle=True, capacite=c2)
|
||||||
creneau.save()
|
creneau.save()
|
||||||
else:
|
else:
|
||||||
creneau = Creneau.objects.get(periode=periode, jour=j, heure=h, duree=d, matiere=m, colleur=c, est_colle=True, capacite=c2)
|
creneau = Slot.objects.get(periode=periode, jour=j, heure=h, duree=d, matiere=m, colleur=c, est_colle=True, capacite=c2)
|
||||||
|
|
||||||
for i, r in enumerate(rotations):
|
for i, r in enumerate(rotations):
|
||||||
sem = headers[4+i].split("/")
|
sem = headers[4+i].split("/")
|
||||||
|
@ -53,16 +53,16 @@ def scrape(periode, chemin):
|
||||||
|
|
||||||
s = date.fromisoformat("-".join(sem)) + (j-1) * timedelta(days=1)
|
s = date.fromisoformat("-".join(sem)) + (j-1) * timedelta(days=1)
|
||||||
|
|
||||||
if not Rotation.objects.filter(creneau=creneau, date=s):
|
if not Colle.objects.filter(creneau=creneau, date=s):
|
||||||
rot = Rotation(creneau=creneau, date=s)
|
rot = Colle(creneau=creneau, date=s)
|
||||||
rot.save()
|
rot.save()
|
||||||
else:
|
else:
|
||||||
rot = Rotation.objects.get(creneau=creneau, date=s)
|
rot = Colle.objects.get(creneau=creneau, date=s)
|
||||||
|
|
||||||
rot.groupes.add(Groupe.objects.get(libelle=r))
|
rot.groupes.add(Group.objects.get(libelle=r))
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
periode = Periode.objects.get(id=3)
|
periode = Term.objects.get(id=3)
|
||||||
scrape(periode, "colloscope.csv")
|
scrape(periode, "colloscope.csv")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//mp2i-vms-[2]-(23-24)-s3//Stackity Bot Inc//EN
|
||||||
|
VERSION:2.0
|
||||||
|
CALSCALE:GREGORIAN
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-WR-CALNAME:EDT
|
||||||
|
X-WR-TIMEZONE:Europe/Paris
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Paris
|
||||||
|
X-LIC-LOCATION:Europe/Paris
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
TZNAME:CEST
|
||||||
|
DTSTART:19700329T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
TZNAME:CET
|
||||||
|
DTSTART:19701025T030000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
END:VCALENDAR
|
|
@ -0,0 +1,74 @@
|
||||||
|
;Semaine;;;;;;;;;;;;;
|
||||||
|
;;;;;;;;22/04/24;29/04/24;13/05/24;20/05/24;27/05/24;03/06/24;10/06/24
|
||||||
|
type;matière;jour;heure;durée;colleur;récurrent (semaines);salle;;;;;;;
|
||||||
|
;;;;;;;;;;;;;;
|
||||||
|
;;;;;;;;;;;;;;
|
||||||
|
colle;InfoTP;mercredi;14h;2h;M. ROUVROY;;M101;11;12;8;5;11;12;5
|
||||||
|
colle;InfoTP;mercredi;14h;2h;M. ROUVROY;;M101;13;15;9;6;13;15;7
|
||||||
|
colle;InfoTP;mercredi;14h;2h;M. ROUVROY;;M101;14;4;10;7;14;4;10
|
||||||
|
colle;Info;lundi;12h;1h;M. JOSPIN;;;10;5;11;12;4;8;11
|
||||||
|
colle;Info;lundi;13h;1h;M. JOSPIN;;;9;6;14;15;7;13;14
|
||||||
|
;;;;;;;;;;;;;;
|
||||||
|
colle;Maths;mercredi;15h;1h;M. CARPINTERO;;V152;7;3;11;10;9;6;2
|
||||||
|
colle;Maths;mercredi;14h;1h;M. CARPINTERO;;V152;15;8;14;13;1;5;4
|
||||||
|
colle;Maths;mercredi;15h;1h;M. BOULLY;;R004;6;5;13;2;3;10;14
|
||||||
|
colle;Maths;mercredi;14h;1h;M. BOULLY;;R004;8;1;12;4;15;7;11
|
||||||
|
colle;Maths;mardi;14h;1h;Mme. MULLAERT;;M070;9;11;4;15;5;12;7
|
||||||
|
colle;Maths;jeudi;18h;1h;Mme. MULLAERT;;;2;14;1;8;6;13;3
|
||||||
|
colle;Maths;jeudi;18h;1h;M. RAPIN;;C284;10;9;5;11;8;2;13
|
||||||
|
colle;Maths;vendredi;17h;1h;M. OUBAHA;;C382;4;13;6;14;7;9;15
|
||||||
|
colle;Maths;vendredi;18h;1h;M. OUBAHA;;C382;5;10;2;3;12;11;8
|
||||||
|
colle;Maths;mardi;18h;1h;M. RAPIN;;V152;3;7;15;12;4;1;6
|
||||||
|
;;;;;;;;;;;;;;
|
||||||
|
colle;Anglais;mercredi;14h;1h;Mme. LE GOURIELLEC;;C393;1;2;5;16;7;13;8
|
||||||
|
colle;Anglais;mercredi;15h;1h;Mme. LE GOURIELLEC;;C393;3;11;6;4;10;14;9
|
||||||
|
colle;Anglais;mercredi;16h;1h;M. MANN;;C380;9;14;7;12;5;11;6
|
||||||
|
colle;Anglais;mercredi;17h;1h;M. MANN;;C380;10;16;8;15;3;4;1
|
||||||
|
colle;Anglais;mardi;14h;1h;Mme. BELAGGOUNE;;C454;5;4;10;11;9;15;10
|
||||||
|
colle;Anglais;mardi;18h;1h;Mme. BELAGGOUNE;;;7;13;3;2;6;16;5
|
||||||
|
colle;Anglais;mardi;17h;1h;Mme. BELAGGOUNE;;;6;12;1;14;8;12;7
|
||||||
|
colle;Anglais;mardi;17h;1h;M. HERBAULT;;V052;8;15;9;13;1;2;3
|
||||||
|
;;;;;;;;;;;;;;
|
||||||
|
colle;Physique;jeudi;18h;1h;M. DE ROUX;;C054;14;8;12;5;13;3;2
|
||||||
|
colle;Physique;vendredi;16h;1h;Mme. CHIBANI;;C284;15;9;11;10;14;6;13
|
||||||
|
colle;Physique;lundi;12h;1h;Mme. CHEVALIER;;R105;16;10;4;8;15;7;12
|
||||||
|
colle;Physique;mercredi;17h;1h;M. POUPY;;R012;12;6;15;9;4;5;11
|
||||||
|
colle;Physique;mercredi;18h;1h;M. POUPY;;R012;13;7;14;3;2;1;16
|
||||||
|
colle;Physique;mardi;17h;1h;Mme. CHEVALIER;;R103;2;1;13;7;12;9;15
|
||||||
|
colle;Physique;mardi;17h;1h;M. COLIN;;C386;11;3;2;1;16;10;4
|
||||||
|
colle;Physique;mardi;14h;1h;Mme. CHEVALIER;;R103;4;5;16;6;11;8;14
|
||||||
|
;;;;;;;;;;;;;;
|
||||||
|
;;;;;;;;;;;;;;
|
||||||
|
cours;TP Physique;lundi;8h30;1h30;Mme CHEVALIER;1;R417;gr 9 à 15;;;;;;
|
||||||
|
cours;TP Physique;lundi;10h;1h30;Mme CHEVALIER;1;R417;gr 1 à 8;;;;;;
|
||||||
|
cours;TP Info;lundi;10h;1h30;M. HALFON;2;C154;gr 9 à 15;;;;;;
|
||||||
|
cours;TP Info;lundi;8h30;1h30;M. HALFON;2;C154;gr 4 à 8;;;;;;
|
||||||
|
cours;TD Maths;vendredi;11h;2h;Mme MULLAERT;2;M103;B+2+3;A+1;;;;;
|
||||||
|
cours;TD Maths;vendredi;14h;2h;Mme MULLAERT;2;M103;A+1;B+2+3;;;;;
|
||||||
|
cours;TP Info;vendredi;14h;2h;M. HALFON;2;;B;A;;;;;
|
||||||
|
cours;TP Info;vendredi;11h;2h;M. HALFON;2;;A;B;;;;;
|
||||||
|
cours;TD Physique;jeudi;14h;1h;Mme CHEVALIER;1;R011;gr 9 à 15;gr 9 à 15;;;;;
|
||||||
|
cours;TD Physique;jeudi;15h;1h;Mme CHEVALIER;1;R011;gr 1 à 8;gr 1 à 8;;;;;
|
||||||
|
cours;Info;jeudi;10h;3h;M. HALFON;1;M103;A+B;;;;;;
|
||||||
|
cours ;Chimie;jeudi;10h;2h;Mme CHEVALIER;1;R101;SI;;;;;;
|
||||||
|
cours;TIPE Physique;jeudi;12h;0h30;Mme CHEVALIER;1;R101;SI;;;;;;
|
||||||
|
cours;TP ITC ;jeudi;13h30;1h30;M. HALFON;1;;SI;;;;;;
|
||||||
|
cours;TP SI;lundi;8h;2h;M. DERUMAUX;1;;SI;;;;;;
|
||||||
|
cours;TIPE Physique;lundi;11h30;0h30;Mme CHEVALIER;1;;SI;;;;;;
|
||||||
|
cours;SI;lundi;12h;1h;M. DERUMAUX;1;;SI;;;;;;
|
||||||
|
cours;SI;mardi;14h;1h;M. DERUMAUX;1;;SI;;;;;;
|
||||||
|
cours ;Maths;lundi;14h;4h;Mme MULLAERT;1;M103;A+B+SI;;;;;;
|
||||||
|
cours;Maths;mardi;8h;3h;Mme MULLAERT;1;M103;A+B+SI;;;;;;
|
||||||
|
cours;Maths;mercredi;10h;1h;Mme MULLAERT;1;M103;A+B+SI;;;;;;
|
||||||
|
cours;Physique;mardi;15h;2h;Mme CHEVALIER;1;R013;A+B+SI;;;;;;
|
||||||
|
cours ;Français-Philo;mercredi;11h;2h;Mme CHAPIRO;1;M103;A+B+SI;;;;;;
|
||||||
|
cours;Physique;jeudi;8h;2h;Mme CHEVALIER;1;R101;A+B+SI;;;;;;
|
||||||
|
cours;EPS;jeudi;16h;2h;M. TORRES-LACAZE;1;Gymnase;A+B+SI;;;;;;
|
||||||
|
cours;Maths;vendredi;8h;3h;Mme MULLAERT;1;M103;A+B+SI;;;;;;
|
||||||
|
;;;;;;;;;;;;;;
|
||||||
|
cours;Anglais LV1;mardi;11h;2h;;1;;;;;;;;
|
||||||
|
cours;Anglais LV2;mardi ;11h;1h30;;1;;;;;;;;
|
||||||
|
cours;Espagnol LV1;mercredi;8h;2h;;1;;;;;;;;
|
||||||
|
cours;Espagnol LV1;mercredi;8h30;1h30;;1;;;;;;;;
|
||||||
|
cours;Allemand LV1 ;mercredi;8h;2h;;1;;;;;;;;
|
||||||
|
cours;Allemand LV2;mercredi;8h30;1h30;;1;;;;;;;;
|
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
132
static/main.css
132
static/main.css
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
background-color: #fafafa;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +35,49 @@ header .bandeau button:active {
|
||||||
background-color: darkgoldenrod;
|
background-color: darkgoldenrod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
background-color: #eee;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 400px)
|
||||||
|
{
|
||||||
|
.navbar {
|
||||||
|
padding: 0 10px;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .block {
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar a:link, .navbar a:visited {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .link {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font: inherit;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .link:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
margin: 20px auto;
|
margin: 20px auto;
|
||||||
width: clamp(350px, 60%, 1200px);
|
width: clamp(350px, 60%, 1200px);
|
||||||
|
@ -47,6 +89,34 @@ h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.login table {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.login input[type=text], form.login input[type=password] {
|
||||||
|
padding: 5px;
|
||||||
|
border: none;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.login button {
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: dodgerblue;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
display: block;
|
||||||
|
margin: 15px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.login button:hover {
|
||||||
|
background-color: #0483ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.login button:active {
|
||||||
|
background-color: #0077ea;
|
||||||
|
}
|
||||||
|
|
||||||
nav.semaine {
|
nav.semaine {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -75,3 +145,63 @@ p.programme {
|
||||||
footer {
|
footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.week {
|
||||||
|
background-color: dodgerblue;
|
||||||
|
color: white;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.week.empty {
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle-wrapper {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 400px)
|
||||||
|
{
|
||||||
|
.colle-wrapper {
|
||||||
|
grid-template-columns: repeat(3, minmax(100px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle span {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle ul {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle li {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle form {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle button {
|
||||||
|
padding: 5px;
|
||||||
|
border: none;
|
||||||
|
background-color: #c0392b;
|
||||||
|
color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle button:hover {
|
||||||
|
background-color: #a93226;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle button:active {
|
||||||
|
background-color: #922b21;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 9e2427d5ae8e7fafef9fe001394e9566e181f217
|
|
@ -7,5 +7,5 @@
|
||||||
{% endblock header %}
|
{% endblock header %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
Vous vous êtes perdu.
|
Vous vous êtes perdu. ASKMULLER
|
||||||
{% endblock main %}
|
{% endblock main %}
|
||||||
|
|
|
@ -5,27 +5,73 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}{% endblock title %}</title>
|
<title>{% block title %}{% endblock title %}</title>
|
||||||
|
<link href="{% static 'unified-navigator/navigator.css' %}" rel="stylesheet" type="text/css">
|
||||||
<link href="{% static 'main.css' %}" rel="stylesheet" type="text/css">
|
<link href="{% static 'main.css' %}" rel="stylesheet" type="text/css">
|
||||||
|
<script src="https://kit.fontawesome.com/0fd87250ec.js" crossorigin="anonymous"></script>
|
||||||
{% block head %}{% endblock head %}
|
{% block head %}{% endblock head %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
|
<script>
|
||||||
|
function navigator_toggleMenu() {
|
||||||
|
if (document.body.clientWidth > 600)
|
||||||
|
return document.location = "https://mp2i-vms.fr"
|
||||||
|
|
||||||
|
if (document.getElementById("menu-sec").classList.contains("hidden")) {
|
||||||
|
document.getElementById("menu-sec").classList.remove("hidden");
|
||||||
|
document.getElementById("navigator-dropdown-indicator").className = "fa-solid fa-caret-up";
|
||||||
|
} else {
|
||||||
|
document.getElementById("menu-sec").classList.add("hidden");
|
||||||
|
document.getElementById("navigator-dropdown-indicator").className = "fa-solid fa-caret-down";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div class="unified_navigator">
|
||||||
|
<div class="nav-container">
|
||||||
|
<div class="logo-wrapper" onclick="navigator_toggleMenu();">
|
||||||
|
<a class="logo">
|
||||||
|
<img src="{% static 'unified-navigator/icon.png' %}" alt="logo"><strong>mp2i-vms.fr</strong>
|
||||||
|
</a>
|
||||||
|
<i id="navigator-dropdown-indicator" class="fa-solid fa-caret-down"></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-wrapper hidden" id="menu-sec">
|
||||||
|
<div class="link"><a href="https://mp2i-vms.fr/pages.html"><i class="fa-solid fa-house"></i> Pages personnelles</a></div>
|
||||||
|
<div class="link"><a href="https://git.mp2i-vms.fr"><i class="fa-brands fa-git-alt"></i> Git</a></div>
|
||||||
|
<div class="link"><a href="https://play.mp2i-vms.fr"><i class="fa-solid fa-cubes"></i> Minecraft</a></div>
|
||||||
|
<div class="link"><a href="{% url "home" %}"><i class="fa-solid fa-book"></i> Colles</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="navbar">
|
||||||
|
<div class="block">
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<div class="bandeau">
|
<a href="{% url "colloscope.dashboard" %}">
|
||||||
Vous êtes connecté avec le compte <b>{{ user.username }}</b>.
|
<div class="link"><i class="fa-solid fa-rocket"></i> Tableau de bord</div>
|
||||||
{% if request.session.profil == "etudiant" %}
|
</a>
|
||||||
Profil actuel : étudiant.
|
<a href="{% url "colloscope.table" %}">
|
||||||
{% elif request.session.profil == "colleur" %}
|
<div class="link"><i class="fa-solid fa-calendar"></i> Colloscope</div>
|
||||||
Profil actuel : colleur.
|
</a>
|
||||||
{% else %}
|
<a href="{% url "colloscope.marketplace" %}">
|
||||||
Pas de profil.
|
<div class="link"><i class="fa-solid fa-shop"></i> Marketplace</div>
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
<form action="{% url 'logout' %}" method="post">
|
<form action="{% url 'logout' %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit">Se déconnecter</button>
|
<button class="link" type="submit" href="{% url "login" %}">
|
||||||
|
<i class="fa-solid fa-right-from-bracket"></i> Se déconnecter
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
{% else %}
|
||||||
|
<a href="{% url "login" %}">
|
||||||
|
<div class="link right"><i class="fa-solid fa-right-from-bracket"></i> Se connecter</div>
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% block header %}{% endblock header %}
|
{% block header %}{% endblock header %}
|
||||||
</header>
|
</header>
|
||||||
|
@ -33,6 +79,6 @@
|
||||||
{% block main %}{% endblock main %}
|
{% block main %}{% endblock main %}
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
{% block footer %}© colles.mp2i-vms.fr 2024{% endblock footer %}
|
{% block footer %}© colles.mp2i-vms.fr 2024 - <a href="https://git.mp2i-vms.fr/mp2i-vms/kholles-web" target="_blank">Code source</a> - <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank">Licence GNU AGPL v3 or later</a>{% endblock footer %}
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form method="post" action="{% url 'login' %}">
|
<form class="login" method="post" action="{% url 'login' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -31,8 +31,7 @@
|
||||||
<td>{{ form.password }}</td>
|
<td>{{ form.password }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
<button type="submit">Se connecter</button>
|
||||||
<input type="submit" value="Se connecter">
|
|
||||||
<input type="hidden" name="next" value="{{ next }}">
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue