Marketplace, CSS, iCal and a lot of things... #4
|
@ -158,5 +158,6 @@ cython_debug/
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
# 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
|
|
@ -0,0 +1,32 @@
|
||||||
|
,,,Semaine,22/04/24,29/04/24,13/05/24,20/05/24,27/05/24,03/06/24,10/06/24
|
||||||
|
M. ROUVROY,InfoTP,mercredi,14h,11,12,8,5,11,12,5
|
||||||
|
M. ROUVROY,InfoTP,mercredi,14h,13,15,9,6,13,15,7
|
||||||
|
M. ROUVROY,InfoTP,mercredi,14h,14,4,10,7,14,4,10
|
||||||
|
M. JOSPIN,Info,lundi,12h,10,5,11,12,4,8,11
|
||||||
|
M. JOSPIN,Info,lundi,13h,9,6,14,15,7,13,14
|
||||||
|
M. CARPINTERO,Maths,mercredi,15h,7,3,11,10,9,6,2
|
||||||
|
M. CARPINTERO,Maths,mercredi,14h,15,8,14,13,1,5,4
|
||||||
|
M. BOULLY,Maths,mercredi,15h,6,5,13,2,3,10,14
|
||||||
|
M. BOULLY,Maths,mercredi,14h,8,1,12,4,15,7,11
|
||||||
|
Mme. MULLAERT,Maths,mardi,14h,9,11,4,15,5,12,7
|
||||||
|
Mme. MULLAERT,Maths,jeudi,18h,2,14,1,8,6,13,3
|
||||||
|
M. RAPIN,Maths,jeudi,18h,10,9,5,11,8,2,13
|
||||||
|
M. OUBAHA,Maths,vendredi,17h,4,13,6,14,7,9,15
|
||||||
|
M. OUBAHA,Maths,vendredi,18h,5,10,2,3,12,11,8
|
||||||
|
M. RAPIN,Maths,mardi,18h,3,7,15,12,4,1,6
|
||||||
|
Mme. LE GOURIELLEC,Anglais,mercredi,14h,1,2,5,16,7,13,8
|
||||||
|
Mme. LE GOURIELLEC,Anglais,mercredi,15h,3,11,6,4,10,14,9
|
||||||
|
M. MANN,Anglais,mercredi,16h,9,14,7,12,5,11,6
|
||||||
|
M. MANN,Anglais,mercredi,17h,10,16,8,15,3,4,1
|
||||||
|
Mme. BELAGOUNE,Anglais,mardi,14h,5,4,10,11,9,15,10
|
||||||
|
Mme. BELAGOUNE,Anglais,mardi,18h,7,13,3,2,6,16,5
|
||||||
|
Mme. BELAGOUNE,Anglais,mardi,17h,6,12,1,14,8,12,7
|
||||||
|
M. HERBAULT,Anglais,mardi,17h,8,15,9,13,1,2,3
|
||||||
|
M. DE ROUX,Physique,jeudi,18h,14,8,12,5,13,3,2
|
||||||
|
Mme. CHIBANI,Physique,vendredi,16h,15,9,11,10,14,6,13
|
||||||
|
Mme. CHEVALIER,Physique,lundi,12h,16,10,4,8,15,7,12
|
||||||
|
M. POUPY,Physique,mercredi,17h,12,6,15,9,4,5,11
|
||||||
|
M. POUPY,Physique,mercredi,18h,13,7,14,3,2,1,16
|
||||||
|
Mme. CHEVALIER,Physique,mardi,17h,2,1,13,7,12,9,15
|
||||||
|
M. COLIN,Physique,mardi,17h,11,3,2,1,16,10,4
|
||||||
|
Mme. CHEVALIER,Physique,mardi,14h,4,5,16,6,11,8,14
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from colloscope.models import *
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(School)
|
||||||
|
class LyceeAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('uai', 'description', 'vacation')
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Class)
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class ColleInline(admin.StackedInline):
|
||||||
|
model = Colle
|
||||||
|
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,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ColloscopeConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'colloscope'
|
|
@ -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()
|
|
@ -0,0 +1,76 @@
|
||||||
|
from datetime import date, time, datetime, timedelta
|
||||||
|
import pytz
|
||||||
|
from uuid import uuid4
|
||||||
|
from os import path
|
||||||
|
from icalendar import Calendar, Event, vCalAddress, vText
|
||||||
|
|
||||||
|
from colloscope.models import *
|
||||||
|
from .create_calendar import get_calendar
|
||||||
|
|
||||||
|
LOCAL_TZ = "Europe/Paris"
|
||||||
|
|
||||||
|
|
||||||
|
def emailize(nom, first_name=None):
|
||||||
|
if first_name is not None:
|
||||||
|
return "{}.{}@example.com" \
|
||||||
|
.format(
|
||||||
|
first_name.replace(" ", "_").lower(),
|
||||||
|
nom.replace(" ", "_").lower()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return "{}@example.com" \
|
||||||
|
.format(nom.replace(" ", "_").lower())
|
||||||
|
|
||||||
|
|
||||||
|
def to_calendar(student, term, include_EDT: bool = True):
|
||||||
|
p = path.abspath('./static/Base_Calendar.ics')
|
||||||
|
|
||||||
|
with open(p) as f:
|
||||||
|
cal = Calendar.from_ical(f.read())
|
||||||
|
|
||||||
|
colles = term.query_colles_of_student(student)
|
||||||
|
|
||||||
|
for colle in colles:
|
||||||
|
event = Event()
|
||||||
|
|
||||||
|
summary = f"Colle {colle.slot.subject} ({colle.slot.colleur})"
|
||||||
|
event.add("summary", summary)
|
||||||
|
|
||||||
|
start = colle.datetime.astimezone(pytz.timezone(LOCAL_TZ))
|
||||||
|
fin = start + colle.slot.duration
|
||||||
|
|
||||||
|
event.add("dtstart", start)
|
||||||
|
event.add("dtend", fin)
|
||||||
|
event.add("dtstamp", datetime.now())
|
||||||
|
event.add("uid", str(uuid4()))
|
||||||
|
|
||||||
|
event.add("location", f"{colle.slot.room} ({colle.slot.term.cls.school})")
|
||||||
|
event.add("categories", "COLLE-" + str(colle.slot.subject))
|
||||||
|
|
||||||
|
description = f"Groupes: {','.join(str(group) for group in colle.groups.all())}"
|
||||||
|
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")
|
||||||
|
event.add("organizer", organizer)
|
||||||
|
|
||||||
|
for e in colle.final_group():
|
||||||
|
attendee = vCalAddress(f"mailto:{emailize(e.last_name, first_name=e.first_name)}")
|
||||||
|
attendee.params["role"] = vText("Etudiant")
|
||||||
|
attendee.params["cn"] = vText(str(e))
|
||||||
|
|
||||||
|
event.add("attendee", attendee, encode=0)
|
||||||
|
|
||||||
|
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
|
|
@ -0,0 +1,151 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-04-14 20:04
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Classe',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('libelle', models.CharField(max_length=20)),
|
||||||
|
('annee', models.IntegerField()),
|
||||||
|
('jour_zero', models.DateField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Colleur',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('civilite', models.CharField(max_length=1)),
|
||||||
|
('nom', models.CharField(max_length=100)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Critere',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('libelle', models.CharField(max_length=100)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Lycee',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('uai', models.CharField(max_length=10)),
|
||||||
|
('libelle', models.CharField(max_length=100)),
|
||||||
|
('vacances', models.CharField(max_length=1)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Matiere',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('libelle', models.CharField(max_length=100)),
|
||||||
|
('code', models.CharField(max_length=20)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Etudiant',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('prenom', models.CharField(max_length=100)),
|
||||||
|
('nom', models.CharField(max_length=100)),
|
||||||
|
('classe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.classe')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['classe', 'nom', 'prenom'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='classe',
|
||||||
|
name='lycee',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.lycee'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Periode',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('libelle', models.CharField(max_length=100)),
|
||||||
|
('debut', models.DateField()),
|
||||||
|
('fin', models.DateField()),
|
||||||
|
('classe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.classe')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Groupe',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('libelle', models.CharField(max_length=100)),
|
||||||
|
('critere', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='colloscope.critere')),
|
||||||
|
('periode', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.periode')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='critere',
|
||||||
|
name='periode',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.periode'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Creneau',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('jour', models.IntegerField()),
|
||||||
|
('heure', models.TimeField()),
|
||||||
|
('duree', models.DurationField()),
|
||||||
|
('salle', models.CharField(max_length=20)),
|
||||||
|
('est_colle', models.BooleanField()),
|
||||||
|
('capacite', models.IntegerField()),
|
||||||
|
('classe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.classe')),
|
||||||
|
('colleur', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.colleur')),
|
||||||
|
('matiere', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.matiere')),
|
||||||
|
('periode', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.periode')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Appartenance',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('etudiant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.etudiant')),
|
||||||
|
('groupe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.groupe')),
|
||||||
|
('periode', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.periode')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Rotation',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('semaine', models.IntegerField()),
|
||||||
|
('creneau', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.creneau')),
|
||||||
|
('groupes', models.ManyToManyField(to='colloscope.groupe')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Amendement',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('est_positif', models.BooleanField()),
|
||||||
|
('etudiant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.etudiant')),
|
||||||
|
('rotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.rotation')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Utilisateur',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('username', models.CharField(max_length=100)),
|
||||||
|
('password', models.CharField(max_length=300)),
|
||||||
|
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('colleur', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='colloscope.colleur')),
|
||||||
|
('etudiant', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='colloscope.etudiant')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-04-15 00:12
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='periode',
|
||||||
|
options={'ordering': ['debut']},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='appartenance',
|
||||||
|
name='periode',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-04-15 00:14
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0002_alter_periode_options_remove_appartenance_periode'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='creneau',
|
||||||
|
name='classe',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-04-15 00:17
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0003_remove_creneau_classe'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='matiere',
|
||||||
|
name='classe',
|
||||||
|
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='colloscope.classe'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-04-18 23:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0004_matiere_classe'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='etudiant',
|
||||||
|
name='groupes',
|
||||||
|
field=models.ManyToManyField(through='colloscope.Appartenance', to='colloscope.groupe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='groupe',
|
||||||
|
name='membres',
|
||||||
|
field=models.ManyToManyField(through='colloscope.Appartenance', to='colloscope.etudiant'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-04-19 00:08
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0005_etudiant_groupes_groupe_membres'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='rotation',
|
||||||
|
name='semaine',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rotation',
|
||||||
|
name='date',
|
||||||
|
field=models.DateField(default=datetime.date(2024, 4, 22)),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-04-19 17:07
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0006_remove_rotation_semaine_rotation_date'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Profil',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('colleur', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='colloscope.colleur')),
|
||||||
|
('etudiant', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='colloscope.etudiant')),
|
||||||
|
('utilisateur', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, unique=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Utilisateur',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-04-20 00:53
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0007_profil_delete_utilisateur'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='profil',
|
||||||
|
name='id',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='profil',
|
||||||
|
name='colleur',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='colloscope.colleur'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='profil',
|
||||||
|
name='etudiant',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='colloscope.etudiant'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='profil',
|
||||||
|
name='utilisateur',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LienCalendrier',
|
||||||
|
fields=[
|
||||||
|
('code', models.CharField(max_length=50, primary_key=True, serialize=False)),
|
||||||
|
('etudiant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.etudiant')),
|
||||||
|
('periode', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='colloscope.periode')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='liencalendrier',
|
||||||
|
constraint=models.UniqueConstraint(fields=('etudiant', 'periode'), name='unique_etudiant_periode_combination'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-04-20 01:13
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0008_remove_profil_id_alter_profil_colleur_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='liencalendrier',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(auto_created=True, default=1, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='liencalendrier',
|
||||||
|
name='code',
|
||||||
|
field=models.CharField(max_length=50),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-04-20 01:14
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0009_liencalendrier_id_alter_liencalendrier_code'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='liencalendrier',
|
||||||
|
name='code',
|
||||||
|
field=models.CharField(max_length=50, unique=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.0.4 on 2024-04-20 01:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('colloscope', '0010_alter_liencalendrier_code'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='liencalendrier',
|
||||||
|
name='code',
|
||||||
|
field=models.CharField(max_length=32, unique=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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',
|
||||||
|
)
|
||||||
|
]
|
|
@ -0,0 +1,449 @@
|
||||||
|
from datetime import date, datetime, timedelta
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
from pytz import timezone
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import F, Q, Count, QuerySet, Subquery, OuterRef, Sum
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from discord import Webhook
|
||||||
|
|
||||||
|
calendar = {
|
||||||
|
"C": [
|
||||||
|
(date(2023, 10, 21), date(2023, 11, 6)),
|
||||||
|
(date(2023, 12, 23), date(2024, 1, 8)),
|
||||||
|
(date(2024, 2, 10), date(2024, 2, 26)),
|
||||||
|
(date(2024, 4, 6), date(2024, 4, 22)),
|
||||||
|
(date(2024, 5, 6), date(2024, 5, 11)), # pont un peu gratté
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class School(models.Model):
|
||||||
|
uai = models.CharField(max_length=10)
|
||||||
|
description = models.CharField(max_length=100)
|
||||||
|
vacation = models.CharField(max_length=1)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.description
|
||||||
|
|
||||||
|
|
||||||
|
class Class(models.Model):
|
||||||
|
school = models.ForeignKey(School, on_delete=models.CASCADE)
|
||||||
|
description = models.CharField(max_length=20)
|
||||||
|
year = models.IntegerField()
|
||||||
|
day_zero = models.DateField()
|
||||||
|
|
||||||
|
def week_number(self, day: date) -> int:
|
||||||
|
"""
|
||||||
|
Entrées :
|
||||||
|
- self
|
||||||
|
- day (datetime.date)
|
||||||
|
|
||||||
|
Sortie :
|
||||||
|
- Le numéro de la semaine contenant day, sans compter les vacation.
|
||||||
|
Renvoie un numéro non spécifiée si le day est pendant une période de vacation
|
||||||
|
"""
|
||||||
|
|
||||||
|
zone = self.school.vacation
|
||||||
|
vacation = calendar[zone]
|
||||||
|
day0 = self.day_zero
|
||||||
|
|
||||||
|
n = 1 + (day - day0).days // 7
|
||||||
|
for debut, fin in vacation:
|
||||||
|
if day > debut:
|
||||||
|
n -= round((fin - debut) / timedelta(weeks=1))
|
||||||
|
return n
|
||||||
|
|
||||||
|
def today_number(self) -> int:
|
||||||
|
"""
|
||||||
|
Entrée:
|
||||||
|
- self
|
||||||
|
|
||||||
|
Sortie:
|
||||||
|
- Le numéro de la semaine courante
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.week_number(date.today())
|
||||||
|
|
||||||
|
def week_beginning_date(self, n: int) -> date:
|
||||||
|
"""
|
||||||
|
Entrée:
|
||||||
|
- self
|
||||||
|
- n (int) <-> numéro de semaine
|
||||||
|
|
||||||
|
Sortie:
|
||||||
|
- Le date du lundi de la semaine n
|
||||||
|
"""
|
||||||
|
|
||||||
|
zone = self.school.vacation
|
||||||
|
vacation = calendar[zone]
|
||||||
|
day0 = self.day_zero
|
||||||
|
|
||||||
|
day = day0 + (n - 1) * timedelta(weeks=1)
|
||||||
|
|
||||||
|
for begin, end in vacation:
|
||||||
|
if day >= begin:
|
||||||
|
day += round((end - begin) / timedelta(weeks=1)) * timedelta(weeks=1)
|
||||||
|
|
||||||
|
return day
|
||||||
|
|
||||||
|
def term_of_date(self, day: date):
|
||||||
|
"""
|
||||||
|
Entrées :
|
||||||
|
- self
|
||||||
|
- day (datetime.date)
|
||||||
|
|
||||||
|
Sortie :
|
||||||
|
- La période (si elle existe et est unique) contenant day
|
||||||
|
|
||||||
|
Exceptions:
|
||||||
|
- Le day n'est pas dans une période
|
||||||
|
- Le day est au chevauchement de deux périodes
|
||||||
|
"""
|
||||||
|
return Term.objects.get(cls=self, debut__lte=day, fin__gte=day)
|
||||||
|
|
||||||
|
def current_term(self):
|
||||||
|
"""
|
||||||
|
On prend la période non révolue la plus récente
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (Term.objects
|
||||||
|
.filter(cls=self, end__gte=date.today())
|
||||||
|
.order_by("-begin")
|
||||||
|
.first())
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.description} ({self.school.description})"
|
||||||
|
|
||||||
|
|
||||||
|
class Term(models.Model):
|
||||||
|
cls = models.ForeignKey(Class, on_delete=models.CASCADE)
|
||||||
|
description = models.CharField(max_length=100)
|
||||||
|
begin = models.DateField()
|
||||||
|
end = models.DateField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["begin"]
|
||||||
|
|
||||||
|
def range_weeks(self) -> range:
|
||||||
|
"""
|
||||||
|
Entrée:
|
||||||
|
- self
|
||||||
|
|
||||||
|
Sortie:
|
||||||
|
- Un range des numéros de semaine
|
||||||
|
"""
|
||||||
|
return range(self.cls.week_number(self.begin), self.cls.week_number(self.end) + 1)
|
||||||
|
|
||||||
|
def query_colles(self) -> QuerySet:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class Subject(models.Model):
|
||||||
|
cls = models.ForeignKey(Class, on_delete=models.CASCADE)
|
||||||
|
description = models.CharField(max_length=100)
|
||||||
|
code = models.CharField(max_length=20)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["description"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.description
|
||||||
|
|
||||||
|
|
||||||
|
class GroupType(models.Model):
|
||||||
|
term = models.ForeignKey(Term, on_delete=models.CASCADE)
|
||||||
|
description = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.description
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
semaines = ((s, self.term.cls.week_beginning_date(s)) for s in self.term.range_semaines())
|
||||||
|
colles_flat = self.get_colles()
|
||||||
|
|
||||||
|
return [
|
||||||
|
(sem, lundi,
|
||||||
|
colles_fla.filter(date__gte=lundi, date__lt=lundi + timedelta(weeks=1)))
|
||||||
|
for sem, lundi in semaines
|
||||||
|
]"""
|
||||||
|
|
||||||
|
|
||||||
|
class Student(models.Model):
|
||||||
|
class Meta:
|
||||||
|
ordering = ["cls", "last_name", "first_name"]
|
||||||
|
|
||||||
|
cls = models.ForeignKey(Class, on_delete=models.CASCADE)
|
||||||
|
first_name = models.CharField(max_length=100)
|
||||||
|
last_name = models.CharField(max_length=100)
|
||||||
|
groups = models.ManyToManyField("Group", through="Member")
|
||||||
|
|
||||||
|
def is_member(self, group):
|
||||||
|
"""
|
||||||
|
Renvoie si self appartient au groupe.
|
||||||
|
"""
|
||||||
|
return group.members.contains(self)
|
||||||
|
|
||||||
|
def group_of_type(self, term, type_):
|
||||||
|
"""
|
||||||
|
Renvoie le groupe du critère auquel self appartient.
|
||||||
|
"""
|
||||||
|
if isinstance(type_, str):
|
||||||
|
type_ = GroupType.objects.get(term=term, description=type_)
|
||||||
|
|
||||||
|
return Member.objects.get(group__term=term, student=self, group__type=type_).group
|
||||||
|
|
||||||
|
def colle_group(self, term):
|
||||||
|
"""
|
||||||
|
Renvoie le groupe de colle de self pendant term.
|
||||||
|
"""
|
||||||
|
return self.group_of_type(term, "colle")
|
||||||
|
|
||||||
|
def swap_score(self, term):
|
||||||
|
colles = term.query_colles_of_student(self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.first_name} {self.last_name}"
|
||||||
|
|
||||||
|
|
||||||
|
class Member(models.Model):
|
||||||
|
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
||||||
|
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
|
class Colleur(models.Model):
|
||||||
|
gender = models.CharField(max_length=1)
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.gender == "M":
|
||||||
|
return f"M. {self.name}"
|
||||||
|
else:
|
||||||
|
return f"Mme {self.name}"
|
||||||
|
|
||||||
|
def get_classes(self):
|
||||||
|
return (x.term_of_date.cls for x in Slot.objects.filter(colleur=self).select_related("term__cls"))
|
||||||
|
|
||||||
|
|
||||||
|
class Slot(models.Model):
|
||||||
|
term = models.ForeignKey(Term, on_delete=models.CASCADE)
|
||||||
|
day = models.IntegerField()
|
||||||
|
time = models.TimeField()
|
||||||
|
duration = models.DurationField()
|
||||||
|
room = models.CharField(max_length=20)
|
||||||
|
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
|
||||||
|
colleur = models.ForeignKey(Colleur, on_delete=models.CASCADE)
|
||||||
|
type = models.ForeignKey(GroupType, on_delete=models.CASCADE)
|
||||||
|
capacity = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["subject", "colleur", "day", "time"]
|
||||||
|
verbose_name_plural = "slots"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
days = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
||||||
|
|
||||||
|
return f"Colle {self.subject} avec {self.colleur} {days[self.day]} {self.time}"
|
||||||
|
|
||||||
|
|
||||||
|
class Colle(models.Model):
|
||||||
|
class Meta:
|
||||||
|
ordering = ["slot__term__cls", "slot__subject__description", "slot__colleur__name", "datetime"]
|
||||||
|
|
||||||
|
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 swaps.
|
||||||
|
"""
|
||||||
|
return Student.objects.filter(id__in=Member.objects.filter(groupe__in=self.groups.all()))
|
||||||
|
|
||||||
|
def final_group(self):
|
||||||
|
"""
|
||||||
|
Renvoie les étudiants inscrits à la colle en tenant compte des swaps.
|
||||||
|
"""
|
||||||
|
swaps = Swap.objects.filter(colle=self)
|
||||||
|
|
||||||
|
return Student.objects.filter(
|
||||||
|
(Q(id__in=Member.objects.filter(group__in=self.groups.all()))
|
||||||
|
| Q(id__in=swaps.filter(enroll=True).values("student_id")))
|
||||||
|
& ~Q(id__in=swaps.filter(enroll=False).values("student_id"))
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_attendee(self, student):
|
||||||
|
return self.final_group().contains(student)
|
||||||
|
|
||||||
|
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 is_full(self):
|
||||||
|
"""
|
||||||
|
Renvoie si la colle est pleine.
|
||||||
|
"""
|
||||||
|
return self.get_volume() >= self.slot.capacity
|
||||||
|
|
||||||
|
def is_edited(self):
|
||||||
|
"""
|
||||||
|
Renvoie si la colle a été amendée.
|
||||||
|
"""
|
||||||
|
return Swap.objects.filter(colle=self).exists()
|
||||||
|
|
||||||
|
def amend(self, student, enroll, notify=False):
|
||||||
|
"""
|
||||||
|
Amende la colle en (des)inscrivant student à la colle self, selon enroll.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if Swap.objects.filter(colle=self, student=student, enroll=enroll).exists():
|
||||||
|
raise Exception("Duplication")
|
||||||
|
elif Swap.objects.filter(colle=self, student=student, enroll=not enroll).exists():
|
||||||
|
# les swaps complémentaires s'annulent
|
||||||
|
Swap.objects.get(colle=self, student=student, enroll=not enroll).delete()
|
||||||
|
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
|
||||||
|
raise Exception("Vous êtes déjà dans le groupe")
|
||||||
|
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")
|
||||||
|
elif enroll and self.is_full():
|
||||||
|
raise Exception("Capacité dépassée")
|
||||||
|
else:
|
||||||
|
swap = Swap(colle=self, student=student, enroll=enroll)
|
||||||
|
swap.save()
|
||||||
|
|
||||||
|
#if notify:
|
||||||
|
# func = async_to_sync(swap.notify)
|
||||||
|
# func()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
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())}}}"
|
||||||
|
|
||||||
|
|
||||||
|
class Swap(models.Model):
|
||||||
|
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 Profile(models.Model):
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||||
|
student = models.ForeignKey(Student, 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):
|
||||||
|
return f"Profil {self.user} : {self.student} ; {self.colleur}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_request(request, preprocess=lambda query: query):
|
||||||
|
user = request.user
|
||||||
|
session = request.session
|
||||||
|
|
||||||
|
match session.get("profile"):
|
||||||
|
case "student":
|
||||||
|
profil = preprocess(Profile.objects.filter(user=user)).get()
|
||||||
|
return profil.student
|
||||||
|
case "colleur":
|
||||||
|
profil = preprocess(Profile.objects.filter(user=user)).get()
|
||||||
|
return profil.colleur
|
||||||
|
case _:
|
||||||
|
raise ValueError("profil non choisi")
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarLink(models.Model):
|
||||||
|
key = models.CharField(max_length=32, unique=True)
|
||||||
|
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
||||||
|
term = models.ForeignKey(Term, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
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}")
|
|
@ -0,0 +1,137 @@
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from fpdf import FPDF
|
||||||
|
|
||||||
|
from colloscope.models import *
|
||||||
|
|
||||||
|
|
||||||
|
class PDF(FPDF):
|
||||||
|
def liste_eleves(self, term):
|
||||||
|
cls = term.cls
|
||||||
|
students = Student.objects.filter(cls=cls)
|
||||||
|
|
||||||
|
with self.table(
|
||||||
|
align="RIGHT",
|
||||||
|
col_widths=(4, 3, 1, 1, 1, 1),
|
||||||
|
width=80,
|
||||||
|
line_height=3) as table:
|
||||||
|
header = table.row()
|
||||||
|
for th in ("Nom", "Prénom", "Grp.", "TD",): #"LV1", "LV2"):
|
||||||
|
header.cell(th)
|
||||||
|
|
||||||
|
for etu in students:
|
||||||
|
row = table.row()
|
||||||
|
row.cell(etu.last_name.upper()) # Nom
|
||||||
|
row.cell(etu.first_name) # Prénom
|
||||||
|
row.cell(etu.colle_group(term).description) # Groupe
|
||||||
|
row.cell(etu.group_of_type(term, "td").description)
|
||||||
|
#row.cell("??") # LV1
|
||||||
|
#row.cell("??") # LV2
|
||||||
|
|
||||||
|
|
||||||
|
def table_colloscope(self, term, heading=True, type="colle"):
|
||||||
|
weeks = term.range_weeks()
|
||||||
|
lundis = [term.cls.week_beginning_date(n) for n in weeks]
|
||||||
|
slots = Slot.objects.filter(term=term, type__description=type)
|
||||||
|
weekdays = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
||||||
|
|
||||||
|
with self.table(
|
||||||
|
align="LEFT",
|
||||||
|
width=190,
|
||||||
|
line_height=3,
|
||||||
|
col_widths=(2, 1, 1, 3, 1, *(1,)*len(weeks)),
|
||||||
|
num_heading_rows=2 if heading else 0,
|
||||||
|
first_row_as_headings=heading) as table:
|
||||||
|
|
||||||
|
if heading:
|
||||||
|
header = table.row()
|
||||||
|
for th in ("Matière", "Jour", "Heure", "Colleur", "Salle"):
|
||||||
|
header.cell(th, align="CENTER", rowspan=2)
|
||||||
|
|
||||||
|
for sem in weeks:
|
||||||
|
header.cell(str(sem), align="CENTER")
|
||||||
|
|
||||||
|
header2 = table.row()
|
||||||
|
for lundi in lundis:
|
||||||
|
header2.cell(lundi.strftime("%d/%m/%y"), align="CENTER")
|
||||||
|
|
||||||
|
|
||||||
|
for i, c in enumerate(slots):
|
||||||
|
subject = c.subject
|
||||||
|
day = c.day
|
||||||
|
time = c.time
|
||||||
|
colleur = c.colleur
|
||||||
|
room = c.room
|
||||||
|
|
||||||
|
row = table.row()
|
||||||
|
row.cell(subject.description)
|
||||||
|
row.cell(weekdays[day])
|
||||||
|
row.cell(time.strftime("%H:%M"))
|
||||||
|
row.cell("{} {}".format("M." if colleur.gender=="M" else "Mme", colleur.name.upper()))
|
||||||
|
row.cell(room)
|
||||||
|
|
||||||
|
for s in weeks:
|
||||||
|
lundi = term.cls.week_beginning_date(s)
|
||||||
|
|
||||||
|
if Colle.objects.filter(slot=c, datetime__gte=lundi, datetime__lt=lundi + timedelta(weeks=1)).exists():
|
||||||
|
r = Colle.objects.get(slot=c, datetime__gte=lundi, datetime__lt=lundi + timedelta(weeks=1))
|
||||||
|
groups = r.groups
|
||||||
|
content = ", ".join(g.description for g in groups.all())
|
||||||
|
|
||||||
|
with self.local_context(fill_color=(255, 100, 100) if r.is_edited() else None):
|
||||||
|
row.cell(content, align="CENTER")
|
||||||
|
else:
|
||||||
|
row.cell()
|
||||||
|
|
||||||
|
def generate(term):
|
||||||
|
pdf = PDF(orientation="landscape", format="a4")
|
||||||
|
pdf.set_font("helvetica", size=6)
|
||||||
|
|
||||||
|
titre = f"Colloscope {term.cls.description} {term.description}"
|
||||||
|
|
||||||
|
pdf.set_title(titre)
|
||||||
|
pdf.set_author("colles.mp2i-vms.fr")
|
||||||
|
|
||||||
|
pdf.add_page()
|
||||||
|
pdf.cell(text=titre, center=True, border=1, h=5)
|
||||||
|
pdf.set_line_width(0.1)
|
||||||
|
base_y = pdf.t_margin + 10
|
||||||
|
pdf.set_y(base_y)
|
||||||
|
pdf.liste_eleves(term)
|
||||||
|
pdf.set_y(base_y)
|
||||||
|
pdf.table_colloscope(term)
|
||||||
|
pdf.y += 3
|
||||||
|
pdf.table_colloscope(term, heading=False, type="td")
|
||||||
|
|
||||||
|
return pdf
|
||||||
|
|
||||||
|
|
||||||
|
def handle(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:
|
||||||
|
term = Term.objects.get(id=int(term_str), cls=student.cls)
|
||||||
|
|
||||||
|
return generate(term)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
term = Term.objects.get(id=3)
|
||||||
|
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"]
|
|
@ -0,0 +1,59 @@
|
||||||
|
from colloscope.models import *
|
||||||
|
|
||||||
|
def table_colloscope(periode, heading=True, est_colle=True):
|
||||||
|
semaines = periode.range_semaines()
|
||||||
|
lundis = [periode.classe.week_beginning_date(n) for n in semaines]
|
||||||
|
creneaux = Slot.objects.filter(periode=periode, est_colle=est_colle)
|
||||||
|
jours = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
|
||||||
|
|
||||||
|
s = ""
|
||||||
|
|
||||||
|
s += "<table>\n"
|
||||||
|
|
||||||
|
|
||||||
|
if heading:
|
||||||
|
s += "<tr>\n"
|
||||||
|
|
||||||
|
for th in ("Matière", "Jour", "Heure", "Colleur", "Salle"):
|
||||||
|
s += f"<th rowspan=2>{th}</th>\n"
|
||||||
|
|
||||||
|
for sem in semaines:
|
||||||
|
s +=f"<th>{sem}</th>\n"
|
||||||
|
|
||||||
|
s += "</tr>\n<tr>\n"
|
||||||
|
|
||||||
|
for lundi in lundis:
|
||||||
|
s += f"<th>{lundi.strftime('%d/%m/%y')}</th>\n"
|
||||||
|
|
||||||
|
s += "</tr>\n"
|
||||||
|
|
||||||
|
for i, c in enumerate(creneaux):
|
||||||
|
matiere = c.subject
|
||||||
|
jour = c.jour
|
||||||
|
heure = c.time
|
||||||
|
colleur = c.colleur
|
||||||
|
salle = c.room
|
||||||
|
|
||||||
|
s += "<tr>\n"
|
||||||
|
s += f"<td>{matiere.description}</td>\n"
|
||||||
|
s += f"<td>{jours[jour]}</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 += f"<td>salle</td>\n"
|
||||||
|
|
||||||
|
for sem in semaines:
|
||||||
|
if Colle.objects.filter(creneau=c, semaine=sem).exists():
|
||||||
|
r = Colle.objects.get(creneau=c, semaine=sem)
|
||||||
|
groupes = r.groupes
|
||||||
|
content = ", ".join(g.description for g in groupes.all())
|
||||||
|
|
||||||
|
if r.is_edited():
|
||||||
|
s += f"<td class='modif'>{content}</td>\n"
|
||||||
|
else:
|
||||||
|
s += f"<td>{content}</td>\n"
|
||||||
|
else:
|
||||||
|
s += "<td></td>\n"
|
||||||
|
|
||||||
|
s += "</table>\n"
|
||||||
|
|
||||||
|
return s
|
|
@ -0,0 +1,54 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% load extras %}
|
||||||
|
|
||||||
|
{% block title %}Tableau de bord{% endblock title %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<h1>Tableau de bord</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Bienvenue {{ student }}. Votre lycée est {{ term.cls.school.description }}, et votre classe est {{ term.cls.description }}.
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<p><a href="{{ calendar_link }}"><i class="fa-regular fa-calendar"></i> Exporter en .ics (ceci est un permalien public)</a></p>
|
||||||
|
|
||||||
|
<p><a href="{% url "colloscope.marketplace" %}">Accéder au marketplace</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
{% 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 %}
|
||||||
|
<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.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>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% 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 %}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Sélection du profil{% endblock title %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<h1>Sélection du profil</h1>
|
||||||
|
|
||||||
|
Vous êtes connecté. Votre compte correspond à deux profils :
|
||||||
|
<ul>
|
||||||
|
<li>en tant que colleur : {{ profil.colleur }} ; Classes : {{ profil.colleur.get_classes|join:"; " }}</li>
|
||||||
|
<li>en tant qu'étudiant : {{ profil.etudiant }} classe : {{ profil.etudiant.classe }}.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock main %}
|
|
@ -0,0 +1,99 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% load extras %}
|
||||||
|
|
||||||
|
{% block title %}Colloscope{% endblock title %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<link href="{% static 'table.css' %}" rel="stylesheet" type="text/css">
|
||||||
|
{% endblock head %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<h1>Colloscope</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Lycée : {{ term.cls.school.description }}. Classe : {{ term.cls.description }}. <a href="dashboard.html">Retour au tableau de bord</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Colloscope : {{ term.description }}</h2>
|
||||||
|
|
||||||
|
<form method="get" action="{% url "colloscope.table" %}">
|
||||||
|
Changer de période :
|
||||||
|
<select name="term" id="term">
|
||||||
|
{% for p in term.cls.term_set.all %}
|
||||||
|
{% if p.id == term.id %}
|
||||||
|
<option value="{{ p.id }}" selected>{{ p }}</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="{{ p.id }}" selected>{{ p }}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<button type="submit">Valider</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% if request.GET.term %}
|
||||||
|
<a href="export.pdf?term={{ request.GET.term }}" target="_blank">Exporter le colloscope</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="export.pdf" target="_blank">Exporter le colloscope</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<table>
|
||||||
|
|
||||||
|
<colgroup>
|
||||||
|
<col>
|
||||||
|
<col>
|
||||||
|
<col>
|
||||||
|
<col>
|
||||||
|
<col>
|
||||||
|
</colgroup>
|
||||||
|
<colgroup>
|
||||||
|
{% for _ in weeks %}
|
||||||
|
<col>
|
||||||
|
{% endfor %}
|
||||||
|
</colgroup>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th rowspan=2>Matière</th>
|
||||||
|
<th rowspan=2>Jour</th>
|
||||||
|
<th rowspan=2>Heure</th>
|
||||||
|
<th rowspan=2>Colleur</th>
|
||||||
|
<th rowspan=2>Salle</th>
|
||||||
|
{% for n in weeks %}
|
||||||
|
<th>{{ n }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
{% for monday in mondays %}
|
||||||
|
<th>{{ monday | strftime:"%d/%m/%y" }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
{% for c, rs in colles %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ c.subject.description }}</td>
|
||||||
|
<td>{{ days | getitem:c.day }}</td>
|
||||||
|
<td>{{ c.time | strftime:"%H:%M" }}</td>
|
||||||
|
<td>{{ c.colleur }}</td>
|
||||||
|
<td>{{ c.room }}</td>
|
||||||
|
|
||||||
|
{% for sem, exists, r, is_edited, groups in rs %}
|
||||||
|
{% if exists %}
|
||||||
|
{% if is_edited %}
|
||||||
|
<td class="modif">{{ groups | join:"," }}</td>
|
||||||
|
{% else %}
|
||||||
|
<td>{{ groups | join:"," }}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock main %}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Sélection du profil{% endblock title %}
|
||||||
|
|
||||||
|
{% 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>.
|
||||||
|
|
||||||
|
{% endblock main %}
|
|
@ -0,0 +1,35 @@
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
from colloscope.models import *
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="strftime")
|
||||||
|
def strftime(value, arg):
|
||||||
|
return value.strftime(arg)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="getitem")
|
||||||
|
def getitem(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")
|
||||||
|
def exists(queryset):
|
||||||
|
return queryset.exists()
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
@register.filter(name="est_modifiee")
|
||||||
|
def est_modifiee(rotation):
|
||||||
|
return rotation.est_modifiee()
|
||||||
|
"""
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
|
@ -0,0 +1,14 @@
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", views.home_redirect, name="colloscope.home"),
|
||||||
|
path("table.html", views.colloscope, name="colloscope.table"),
|
||||||
|
path("dashboard.html", views.dashboard, name="colloscope.dashboard"),
|
||||||
|
path("export.pdf", views.export, name="colloscope.export"),
|
||||||
|
path("export/calendar/<str:key>/calendar.ics", views.icalendar, name="colloscope.calendar.ics"),
|
||||||
|
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"),
|
||||||
|
]
|
|
@ -0,0 +1,290 @@
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
|
from django.template import loader
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
from colloscope.models import *
|
||||||
|
from colloscope.pdfexport import handle
|
||||||
|
from colloscope.icalexport import to_calendar
|
||||||
|
|
||||||
|
|
||||||
|
def handler404(request):
|
||||||
|
template = loader.get_template("404.html")
|
||||||
|
context = {}
|
||||||
|
return HttpResponse(template.render(context), status=404)
|
||||||
|
|
||||||
|
|
||||||
|
def home_redirect(request):
|
||||||
|
return redirect("/colloscope/dashboard.html")
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def select_profile(request):
|
||||||
|
user = request.user
|
||||||
|
session = request.session
|
||||||
|
|
||||||
|
if not Profile.objects.filter(user=user).exists():
|
||||||
|
profile = Profile(user=user)
|
||||||
|
profile.save()
|
||||||
|
else:
|
||||||
|
profile = Profile.objects.get(user=user)
|
||||||
|
|
||||||
|
if profile.student is not None and profile.colleur is None:
|
||||||
|
session["profile"] = "student"
|
||||||
|
return redirect("/colloscope/")
|
||||||
|
elif profile.colleur is not None and profile.student is None:
|
||||||
|
session["profile"] = "colleur"
|
||||||
|
return redirect("/colloscope/")
|
||||||
|
else:
|
||||||
|
if profile.student is not None:
|
||||||
|
template = loader.get_template("select_profile.html")
|
||||||
|
else:
|
||||||
|
template = loader.get_template("unbound_profile.html")
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"profile": profile,
|
||||||
|
}
|
||||||
|
return HttpResponse(template.render(context))
|
||||||
|
|
||||||
|
|
||||||
|
def get_lien_calendrier(student, term):
|
||||||
|
try:
|
||||||
|
lien = CalendarLink.objects.get(student=student, term=term)
|
||||||
|
except CalendarLink.DoesNotExist:
|
||||||
|
key = uuid4().hex
|
||||||
|
lien = CalendarLink(key=key, student=student, term=term)
|
||||||
|
lien.save()
|
||||||
|
|
||||||
|
return f"calendrier.ics?key={lien.key}"
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def dashboard(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 = student.cls.current_term()
|
||||||
|
group = student.colle_group(term)
|
||||||
|
|
||||||
|
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")
|
||||||
|
calendar_link = get_calendar_link(student, term)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"student": student,
|
||||||
|
"term": term,
|
||||||
|
"group": group,
|
||||||
|
"colles_per_sem": colles_per_sem,
|
||||||
|
"calendar_link": calendar_link,
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse(template.render(context, request))
|
||||||
|
|
||||||
|
|
||||||
|
class AmendForm(forms.Form):
|
||||||
|
colle_id = forms.IntegerField(widget=forms.HiddenInput(), required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class EnrollForm(AmendForm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WithdrawForm(AmendForm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def marketplace(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 = student.cls.current_term()
|
||||||
|
colles = term.query_colles_not_full_excluding_student(student)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"colles": colles,
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
term = Term.objects.get(id=int(term_str), cls=student.cls)
|
||||||
|
except Term.DoesNotExist:
|
||||||
|
template = loader.get_template("404.html")
|
||||||
|
context = {}
|
||||||
|
response = HttpResponse(template.render(context, request))
|
||||||
|
response.status_code = 404
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
slots = (Slot.objects
|
||||||
|
.filter(term=term, type__description="colle")
|
||||||
|
.order_by()
|
||||||
|
.prefetch_related("colle_set"))
|
||||||
|
|
||||||
|
weeks = term.range_weeks()
|
||||||
|
colles = [(c, []) for c in slots]
|
||||||
|
for c, l in colles:
|
||||||
|
for sem in weeks:
|
||||||
|
lundi = term.cls.week_beginning_date(sem)
|
||||||
|
|
||||||
|
rot = Colle.objects.filter(slot=c, datetime__gte=lundi, datetime__lt=lundi + timedelta(weeks=1))
|
||||||
|
exists = rot.exists()
|
||||||
|
|
||||||
|
if exists:
|
||||||
|
r = rot.first()
|
||||||
|
is_edited = r.is_edited()
|
||||||
|
groups = (g.description for g in r.groups.all())
|
||||||
|
else:
|
||||||
|
r = is_edited = groups = None
|
||||||
|
|
||||||
|
l.append((sem, exists, r, is_edited, groups))
|
||||||
|
|
||||||
|
template = loader.get_template("table.html")
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"term": term,
|
||||||
|
"weeks": weeks,
|
||||||
|
"mondays": [term.cls.week_beginning_date(n) for n in weeks],
|
||||||
|
"days": ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
|
||||||
|
"colles": colles,
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse(template.render(context, request))
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def export(request):
|
||||||
|
return HttpResponse(bytes(handle(request).output()), content_type="application/pdf")
|
||||||
|
|
||||||
|
|
||||||
|
def get_calendar_link(student, term):
|
||||||
|
try:
|
||||||
|
lien = CalendarLink.objects.get(student=student, term=term)
|
||||||
|
except CalendarLink.DoesNotExist:
|
||||||
|
key = uuid4().hex
|
||||||
|
lien = CalendarLink(key=key, student=student, term=term)
|
||||||
|
lien.save()
|
||||||
|
|
||||||
|
return f"export/calendar/{lien.key}/calendar.ics"
|
||||||
|
|
||||||
|
|
||||||
|
def icalendar(request, key):
|
||||||
|
try:
|
||||||
|
link = CalendarLink.objects.get(key=key)
|
||||||
|
|
||||||
|
if not request.GET.get("edt"):
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def amend(request, colle_id, do_enroll):
|
||||||
|
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()
|
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
ASGI config for kholles_web project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kholles_web.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
|
@ -0,0 +1,61 @@
|
||||||
|
"""
|
||||||
|
URL configuration for kholles_web project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin, auth
|
||||||
|
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.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 = [
|
||||||
|
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('admin/', admin.site.urls),
|
||||||
|
path('accounts/', include("django.contrib.auth.urls")),
|
||||||
|
]
|
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
WSGI config for kholles_web project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kholles_web.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kholles_web.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -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
|
|
@ -0,0 +1,69 @@
|
||||||
|
import csv
|
||||||
|
from datetime import date, time, timedelta
|
||||||
|
from colloscope.models import *
|
||||||
|
|
||||||
|
def scrape(periode, chemin):
|
||||||
|
with open(chemin, "r") as file:
|
||||||
|
reader = csv.reader(file)
|
||||||
|
headers, *colloscope = list(reader)
|
||||||
|
|
||||||
|
for l in colloscope:
|
||||||
|
print(l)
|
||||||
|
|
||||||
|
for colleur, matiere, jour, heure, *(rotations) in colloscope:
|
||||||
|
nom_colleur = colleur.lstrip("Mme.").title()
|
||||||
|
civilite = "M" if colleur.startswith("M.") else "F"
|
||||||
|
|
||||||
|
if not Colleur.objects.filter(nom=nom_colleur, civilite=civilite).exists():
|
||||||
|
c = Colleur(civilite=civilite, nom=nom_colleur)
|
||||||
|
c.save()
|
||||||
|
else:
|
||||||
|
c = Colleur.objects.get(nom=nom_colleur, civilite=civilite)
|
||||||
|
|
||||||
|
if not Subject.objects.filter(classe=periode.classe, libelle=matiere).exists():
|
||||||
|
m = Subject(classe=periode.classe, libelle=matiere, code=matiere.upper())
|
||||||
|
m.save()
|
||||||
|
else:
|
||||||
|
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}
|
||||||
|
j = jours_dict[jour]
|
||||||
|
|
||||||
|
h = time(int(heure[:-1]), 0)
|
||||||
|
|
||||||
|
if matiere=="InfoTP":
|
||||||
|
d = timedelta(hours=2)
|
||||||
|
c2 = 9
|
||||||
|
else:
|
||||||
|
d = timedelta(hours=1)
|
||||||
|
c2 = 3
|
||||||
|
|
||||||
|
print(f"--> Traitement de {c=}, {m=}, {j=}, {h=}, {d=}, {c2=}")
|
||||||
|
|
||||||
|
if not Slot.objects.filter(periode=periode, jour=j, heure=h, duree=d, matiere=m, colleur=c, est_colle=True, capacite=c2).exists():
|
||||||
|
creneau = Slot(periode=periode, jour=j, heure=h, duree=d, salle="nc", matiere=m, colleur=c, est_colle=True, capacite=c2)
|
||||||
|
creneau.save()
|
||||||
|
else:
|
||||||
|
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):
|
||||||
|
sem = headers[4+i].split("/")
|
||||||
|
sem[2] = "20"+sem[2]
|
||||||
|
sem.reverse()
|
||||||
|
|
||||||
|
s = date.fromisoformat("-".join(sem)) + (j-1) * timedelta(days=1)
|
||||||
|
|
||||||
|
if not Colle.objects.filter(creneau=creneau, date=s):
|
||||||
|
rot = Colle(creneau=creneau, date=s)
|
||||||
|
rot.save()
|
||||||
|
else:
|
||||||
|
rot = Colle.objects.get(creneau=creneau, date=s)
|
||||||
|
|
||||||
|
rot.groupes.add(Group.objects.get(libelle=r))
|
||||||
|
|
||||||
|
def main():
|
||||||
|
periode = Term.objects.get(id=3)
|
||||||
|
scrape(periode, "colloscope.csv")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
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 |
|
@ -0,0 +1,207 @@
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .bandeau {
|
||||||
|
display: block;
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .bandeau form {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .bandeau button {
|
||||||
|
border: none;
|
||||||
|
background-color: gold;
|
||||||
|
color: black;
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin: 0 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .bandeau button:hover {
|
||||||
|
background-color: goldenrod;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .bandeau button:active {
|
||||||
|
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 {
|
||||||
|
margin: 20px auto;
|
||||||
|
width: clamp(350px, 60%, 1200px);
|
||||||
|
background-color: white;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
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 {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
background-color: #ddd;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.semaine .select, nav.semaine .label {
|
||||||
|
padding: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.semaine .select { background-color: dodgerblue; color: white; width: 1em; }
|
||||||
|
nav.semaine .select:hover { background-color: #0077ea; }
|
||||||
|
|
||||||
|
nav.semaine .label:hover {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.programme {
|
||||||
|
border-left: 5px solid gold;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #ffffdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
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,33 @@
|
||||||
|
.table-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
border-bottom: 0;
|
||||||
|
border-top: 0;
|
||||||
|
border-left: 1px solid #eee;
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover, col:hover {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:hover {
|
||||||
|
background-color: gold;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 9e2427d5ae8e7fafef9fe001394e9566e181f217
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Erreur 404{% endblock title %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>404 : GAME OVER</h1>
|
||||||
|
{% endblock header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
Vous vous êtes perdu. ASKMULLER
|
||||||
|
{% endblock main %}
|
|
@ -0,0 +1,84 @@
|
||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr-fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<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">
|
||||||
|
<script src="https://kit.fontawesome.com/0fd87250ec.js" crossorigin="anonymous"></script>
|
||||||
|
{% block head %}{% endblock head %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<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 %}
|
||||||
|
<a href="{% url "colloscope.dashboard" %}">
|
||||||
|
<div class="link"><i class="fa-solid fa-rocket"></i> Tableau de bord</div>
|
||||||
|
</a>
|
||||||
|
<a href="{% url "colloscope.table" %}">
|
||||||
|
<div class="link"><i class="fa-solid fa-calendar"></i> Colloscope</div>
|
||||||
|
</a>
|
||||||
|
<a href="{% url "colloscope.marketplace" %}">
|
||||||
|
<div class="link"><i class="fa-solid fa-shop"></i> Marketplace</div>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<form action="{% url 'logout' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button class="link" type="submit" href="{% url "login" %}">
|
||||||
|
<i class="fa-solid fa-right-from-bracket"></i> Se déconnecter
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url "login" %}">
|
||||||
|
<div class="link right"><i class="fa-solid fa-right-from-bracket"></i> Se connecter</div>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% block header %}{% endblock header %}
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
{% block main %}{% endblock main %}
|
||||||
|
</main>
|
||||||
|
<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>
|
||||||
|
</body>
|
|
@ -0,0 +1,43 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Se connecter{% endblock title %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<h1>Se connecter</h1>
|
||||||
|
|
||||||
|
{% if form.errors %}
|
||||||
|
<p>Your username and password didn't match. Please try again.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if next %}
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<p>Your account doesn't have access to this page. To proceed,
|
||||||
|
please login with an account that has access.</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Please login to see this page.</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form class="login" method="post" action="{% url 'login' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>{{ form.username.label_tag }}</td>
|
||||||
|
<td>{{ form.username }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ form.password.label_tag }}</td>
|
||||||
|
<td>{{ form.password }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<button type="submit">Se connecter</button>
|
||||||
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{# Assumes you set up the password_reset view in your URLconf #}
|
||||||
|
<p><a href="{% url 'password_reset' %}">Mot de passe oublié ?</a></p>
|
||||||
|
|
||||||
|
{% endblock main %}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue