Compare commits
53 Commits
fcbd9dd395
...
1354bdc449
Author | SHA1 | Date |
---|---|---|
|
1354bdc449 | |
|
c374a07d5f | |
|
bfb75adc46 | |
|
9f4d1c755c | |
|
1cde62b74d | |
|
4d4b83f4d4 | |
|
78d1ab0392 | |
|
a78aff9f9d | |
|
0afc683c5b | |
|
a5651da3e8 | |
|
6617c5f7a3 | |
|
a71042335a | |
|
b02a330ad1 | |
|
d6fde1f846 | |
|
e0f1462001 | |
|
c3035bf01d | |
|
bd9fe4e735 | |
|
4710dbe7ce | |
|
6b3d4fecc5 | |
|
1f149d8252 | |
|
0e6eda5fa8 | |
|
7b593eec08 | |
|
62989bdae9 | |
|
44f9f45112 | |
|
3dd379f735 | |
|
f8f7120af4 | |
|
0fb1806f76 | |
|
941c5c69b6 | |
|
b48c601e7b | |
|
c51bd0a376 | |
|
252b145cad | |
|
42638a082b | |
|
14f1ff841e | |
|
018b4c0c3d | |
|
df8ce2bbf9 | |
|
756332d3b9 | |
|
4a9cd61405 | |
|
7e4a955215 | |
|
2191a4cf0b | |
|
468b77bb73 | |
|
a531ae0f21 | |
|
b5aaac242e | |
|
b859ca47ec | |
|
443c95daa0 | |
|
3483ce62c8 | |
|
e7dbcafa7f | |
|
ec9619742f | |
|
5f23f0dc8f | |
|
b46a739430 | |
|
f716040bcd | |
|
42ef683224 | |
|
80d41c0561 | |
|
9c539785a8 |
|
@ -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 = https://git.mp2i-vms.fr/mp2i-vms/unified-navigator
|
||||||
|
branch = main
|
|
@ -1,3 +1,48 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from colloscope.models import *
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
|
@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,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
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.0.4 on 2024-04-12 22:38
|
# Generated by Django 5.0.4 on 2024-04-14 20:04
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -12,29 +12,140 @@ class Migration(migrations.Migration):
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
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(
|
migrations.CreateModel(
|
||||||
name='Colleur',
|
name='Colleur',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('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)),
|
('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(
|
migrations.CreateModel(
|
||||||
name='Etudiant',
|
name='Etudiant',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('prenom', models.CharField(max_length=100)),
|
('prenom', models.CharField(max_length=100)),
|
||||||
('nom', models.CharField(max_length=100)),
|
('nom', models.CharField(max_length=100)),
|
||||||
('groupe', models.IntegerField()),
|
('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(
|
migrations.CreateModel(
|
||||||
name='Colle',
|
name='Groupe',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('groupe', models.IntegerField()),
|
('libelle', models.CharField(max_length=100)),
|
||||||
('date', models.DateTimeField()),
|
('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')),
|
('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',
|
||||||
|
)
|
||||||
|
]
|
|
@ -1,15 +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 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):
|
class Colleur(models.Model):
|
||||||
nom=models.CharField(max_length=100)
|
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 Etudiant(models.Model):
|
|
||||||
prenom=models.CharField(max_length=100)
|
|
||||||
nom=models.CharField(max_length=100)
|
|
||||||
groupe=models.IntegerField()
|
|
||||||
|
|
||||||
class Colle(models.Model):
|
class Colle(models.Model):
|
||||||
colleur=models.ForeignKey(Colleur, on_delete=models.CASCADE)
|
class Meta:
|
||||||
groupe=models.IntegerField()
|
ordering = ["slot__term__cls", "slot__subject__description", "slot__colleur__name", "datetime"]
|
||||||
date=models.DateTimeField()
|
|
||||||
|
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}")
|
|
@ -1,172 +1,137 @@
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.http import HttpResponse
|
||||||
from fpdf import FPDF
|
from fpdf import FPDF
|
||||||
from fpdf.fonts import FontFace
|
|
||||||
from fpdf.enums import TableCellFillMode
|
|
||||||
|
|
||||||
sem_1 = date(2023, 9, 18)
|
from colloscope.models import *
|
||||||
|
|
||||||
vacances_zoneA = [
|
|
||||||
( 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) ),
|
|
||||||
]
|
|
||||||
|
|
||||||
def jour_of_sem(n, vac):
|
class PDF(FPDF):
|
||||||
jour = sem_1 + (n-1) * timedelta(weeks=1)
|
def liste_eleves(self, term):
|
||||||
|
cls = term.cls
|
||||||
|
students = Student.objects.filter(cls=cls)
|
||||||
|
|
||||||
for (debut, fin) in vac:
|
with self.table(
|
||||||
if jour >= debut:
|
|
||||||
jour += 2*timedelta(weeks=1)
|
|
||||||
|
|
||||||
return jour
|
|
||||||
|
|
||||||
def generate():
|
|
||||||
pdf = FPDF(orientation="landscape", format="A4")
|
|
||||||
pdf.set_font("helvetica", size=6)
|
|
||||||
|
|
||||||
pdf.set_title("Colloscope MP2I Semestre 5/2")
|
|
||||||
pdf.set_author("Projet colloscope")
|
|
||||||
pdf.set_author("Projet colloscope")
|
|
||||||
|
|
||||||
etudiants = [
|
|
||||||
["Aboujaib", "Alexandre", 4, "A", "Angl.", "All."],
|
|
||||||
["Ajan", "George", 4, "A", "Angl.", ""],
|
|
||||||
["Akrad", "Lina", 1, "SI", "Angl.", ""],
|
|
||||||
["Aubert", "Nicolas", 1, "SI", "Angl.", ""],
|
|
||||||
["Badr", "Roman", 4, "A", "Angl.", ""],
|
|
||||||
["Bazire", "Aurélien", 5, "A", "Angl.", ""],
|
|
||||||
["Boit", "Arthur", 1, "SI", "Angl.", ""],
|
|
||||||
["Boubker", "Youssef", 1, "SI", "Angl.", ""],
|
|
||||||
["Boudjema", "Dylan", 1, "SI", "Angl.", ""],
|
|
||||||
["Chiriac", "Mihnea", 1, "SI", "Angl.", ""],
|
|
||||||
["Courier", "Marine", 1, "SI", "Angl.", ""],
|
|
||||||
["Daguin", "Joseph", 1, "SI", "Angl.", ""],
|
|
||||||
["De Weer", "Matthias", 1, "SI", "Angl.", ""],
|
|
||||||
["Desbouis", "Katell", 1, "SI", "Angl.", ""],
|
|
||||||
["Dupouy", "Jérémie", 1, "SI", "Angl.", ""],
|
|
||||||
["Hariri--Gautier-Picard", "Grégoire", 1, "SI", "Angl.", ""],
|
|
||||||
["Juricevic", "Matteo", 1, "SI", "Angl.", ""],
|
|
||||||
["Knanoua", "Anas", 1, "SI", "Angl.", ""],
|
|
||||||
["Lesenne", "Pierrick", 1, "SI", "Angl.", ""],
|
|
||||||
["Lin", "Hao", 1, "SI", "Angl.", ""],
|
|
||||||
["Masbatin", "Lucas", 1, "SI", "Angl.", ""],
|
|
||||||
["Mayuran", "Mithushan", 1, "SI", "Angl.", ""],
|
|
||||||
["Messahli", "Yassine", 1, "SI", "Angl.", ""],
|
|
||||||
["Moguérou", "Valentin", 10, "B", "Angl.", "All."],
|
|
||||||
["Mohellebi", "Mathéo", 10, "B", "Angl.", "All."],
|
|
||||||
["Mouisset--Ferrara", "Maël", 10, "B", "Angl.", "All."],
|
|
||||||
["Ottavi", "Corentin", 10, "B", "Angl.", "All."],
|
|
||||||
["Ponce", "Alexian", 10, "B", "Angl.", "All."],
|
|
||||||
["Pujol", "Raphaël", 10, "B", "Angl.", "All."],
|
|
||||||
["Pustetto", "Mathis", 10, "B", "Angl.", "All."],
|
|
||||||
["Radice", "Roman", 10, "B", "Angl.", "All."],
|
|
||||||
["Rat", "Evelyn", 10, "B", "Angl.", "All."],
|
|
||||||
["Rousse", "Louis", 10, "B", "Angl.", "All."],
|
|
||||||
["Roux", "Gaëtan", 10, "B", "Angl.", "All."],
|
|
||||||
["Rouyre--Cros", "Célian", 10, "B", "Angl.", "All."],
|
|
||||||
["Sourbé", "François-Gabriel", 10, "B", "Angl.", "All."],
|
|
||||||
["Stourbe", "Simon", 10, "B", "Angl.", "All."],
|
|
||||||
["Thai", "Dany", 10, "B", "Angl.", "All."],
|
|
||||||
["Théodore", "Jonathan", 10, "B", "Angl.", "All."],
|
|
||||||
["Vandroux", "Benoît", 10, "B", "Angl.", "All."],
|
|
||||||
["Veyssière", "Thibaud", 10, "B", "Angl.", "All."],
|
|
||||||
["Vié", "Adrien", 10, "B", "Angl.", "All."],
|
|
||||||
["Ye", "Luan", 10, "B", "Angl.", "All."],
|
|
||||||
["Zarka", "Amélie", 10, "B", "Angl.", "All."],
|
|
||||||
]
|
|
||||||
|
|
||||||
creneaux = [
|
|
||||||
["Mathématiques", "vendredi", "17:00", "M. OUBAHA", "C382"],
|
|
||||||
["Anglais", "mercredi", "14:00", "Mme LE GOURIELLEC", "C393"],
|
|
||||||
["Mathématiques", "mercredi", "15:00", "M. BOULLY", "R004"],
|
|
||||||
["Physique", "mardi", "14:00", "Mme CHEVALIER", "R103"],
|
|
||||||
["Mathématiques", "mardi", "18:00", "M. RAPIN", "V152"],
|
|
||||||
["Anglais", "mardi", "14:00", "Mme BELAGGOUNE", "C4??"],
|
|
||||||
["pas de colle", "", "", "", ""],
|
|
||||||
["Physique", "mardi", "17:00", "M. COLIN", "C386"],
|
|
||||||
["Mathématiques", "mercredi", "13:30", "M. BOUVEROT", "??"],
|
|
||||||
["Anglais", "lundi", "13:00", "M. HERBAUT", "V052"],
|
|
||||||
]
|
|
||||||
|
|
||||||
semaines = list(range(24, 34))
|
|
||||||
|
|
||||||
rotations = [
|
|
||||||
# [semaine, groupe, creneau]
|
|
||||||
(24, 1, 1),
|
|
||||||
(24, 2, 2),
|
|
||||||
(24, 3, 3),
|
|
||||||
(27, 3, 3),
|
|
||||||
(28, 3, 3),
|
|
||||||
(31, 3, 3),
|
|
||||||
]
|
|
||||||
|
|
||||||
pdf.add_page()
|
|
||||||
|
|
||||||
pdf.cell(text="Colloscope MP2I Semestre 5/2", center=True, border=1, h=5)
|
|
||||||
|
|
||||||
base_y = pdf.t_margin + 10
|
|
||||||
|
|
||||||
pdf.set_y(base_y)
|
|
||||||
|
|
||||||
with pdf.table(
|
|
||||||
align="RIGHT",
|
align="RIGHT",
|
||||||
col_widths=(50, 35, 12, 12, 12, 12),
|
col_widths=(4, 3, 1, 1, 1, 1),
|
||||||
width=80,
|
width=80,
|
||||||
line_height=3) as table:
|
line_height=3) as table:
|
||||||
header = table.row()
|
header = table.row()
|
||||||
for th in ("Nom", "Prénom", "Grp.", "TD", "LV1", "LV2"):
|
for th in ("Nom", "Prénom", "Grp.", "TD",): #"LV1", "LV2"):
|
||||||
header.cell(th)
|
header.cell(th)
|
||||||
|
|
||||||
for etu in etudiants:
|
for etu in students:
|
||||||
row = table.row()
|
row = table.row()
|
||||||
row.cell(etu[0].upper()) # Nom
|
row.cell(etu.last_name.upper()) # Nom
|
||||||
row.cell(etu[1]) # Prénom
|
row.cell(etu.first_name) # Prénom
|
||||||
row.cell(str(etu[2])) # Groupe
|
row.cell(etu.colle_group(term).description) # Groupe
|
||||||
row.cell(etu[3]) # TD
|
row.cell(etu.group_of_type(term, "td").description)
|
||||||
row.cell(etu[4]) # LV1
|
#row.cell("??") # LV1
|
||||||
row.cell(etu[5]) # LV2
|
#row.cell("??") # LV2
|
||||||
|
|
||||||
pdf.set_y(base_y)
|
|
||||||
|
|
||||||
with pdf.table(
|
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",
|
align="LEFT",
|
||||||
width=190,
|
width=190,
|
||||||
line_height=3,
|
line_height=3,
|
||||||
col_widths=(25, 12, 10, 25, 12, *(10,)*len(semaines)),
|
col_widths=(2, 1, 1, 3, 1, *(1,)*len(weeks)),
|
||||||
num_heading_rows=2) as table:
|
num_heading_rows=2 if heading else 0,
|
||||||
|
first_row_as_headings=heading) as table:
|
||||||
|
|
||||||
header = table.row()
|
if heading:
|
||||||
for th in ("Matière", "Jour", "Heure", "Colleur", "Salle"):
|
header = table.row()
|
||||||
header.cell(th, align="CENTER", rowspan=2)
|
for th in ("Matière", "Jour", "Heure", "Colleur", "Salle"):
|
||||||
|
header.cell(th, align="CENTER", rowspan=2)
|
||||||
|
|
||||||
for sem in semaines:
|
for sem in weeks:
|
||||||
header.cell(str(sem), align="CENTER")
|
header.cell(str(sem), align="CENTER")
|
||||||
|
|
||||||
header2 = table.row()
|
header2 = table.row()
|
||||||
for sem in semaines:
|
for lundi in lundis:
|
||||||
header2.cell(jour_of_sem(sem, vacances_zoneA).strftime("%d/%m/%y"), align="CENTER")
|
header2.cell(lundi.strftime("%d/%m/%y"), align="CENTER")
|
||||||
|
|
||||||
for i, tr in enumerate(creneaux):
|
|
||||||
matiere, jour, heure, colleur, salle = tr
|
|
||||||
|
|
||||||
row = table.row()
|
for i, c in enumerate(slots):
|
||||||
row.cell(matiere)
|
subject = c.subject
|
||||||
row.cell(jour)
|
day = c.day
|
||||||
row.cell(heure)
|
time = c.time
|
||||||
row.cell(colleur)
|
colleur = c.colleur
|
||||||
row.cell(salle)
|
room = c.room
|
||||||
|
|
||||||
for s in semaines:
|
row = table.row()
|
||||||
for rot in rotations:
|
row.cell(subject.description)
|
||||||
if rot[2] == i and rot[0] == s:
|
row.cell(weekdays[day])
|
||||||
row.cell(str(rot[1]), align="CENTER")
|
row.cell(time.strftime("%H:%M"))
|
||||||
break
|
row.cell("{} {}".format("M." if colleur.gender=="M" else "Mme", colleur.name.upper()))
|
||||||
else:
|
row.cell(room)
|
||||||
row.cell()
|
|
||||||
|
|
||||||
pdf.output("test.pdf")
|
for s in weeks:
|
||||||
|
lundi = term.cls.week_beginning_date(s)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if Colle.objects.filter(slot=c, datetime__gte=lundi, datetime__lt=lundi + timedelta(weeks=1)).exists():
|
||||||
generate()
|
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
|
|
@ -1,52 +0,0 @@
|
||||||
{% load static %}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Colloscope</title>
|
|
||||||
<link href="{% static 'main.css' %}" rel="stylesheet" type="text/css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>Universal Kholloscope Systems™</h1>
|
|
||||||
|
|
||||||
<nav class="semaine">
|
|
||||||
<span class="select"><</span>
|
|
||||||
<span class="label">Semaine : du 22 au 26 avril 2024.</span>
|
|
||||||
<span class="select">></span>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
|
|
||||||
<h2>Programme de colles</h2>
|
|
||||||
<p class="programme">
|
|
||||||
Mathématiques : Dimension finie et matrices
|
|
||||||
</p>
|
|
||||||
<p class="programme">
|
|
||||||
Physique : Mécanique du solide
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>
|
|
||||||
Colles cette semaine
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Groupe 1 : Physique dimanche 23h Newton</li>
|
|
||||||
<li>Groupe 2 : Maths dimanche 23h Euler</li>
|
|
||||||
<li>Groupe 3 : Anglais dimanche 23h Shakespeare</li>
|
|
||||||
<li>Groupe 4 : Informatique dimanche 23h Turing</li>
|
|
||||||
<li>Groupe 5 : Histoire dimanche 23h Tite-Live</li>
|
|
||||||
<li>Groupe 6 : Philosophie dimanche 23h Descartes</li>
|
|
||||||
<li>Groupe 7 : EPS dimanche 23h Bolt</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
© UKS 2024
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -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()
|
||||||
|
"""
|
Binary file not shown.
|
@ -2,5 +2,13 @@ from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("colloscope/", views.colloscope, name="colloscope"),
|
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"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,290 @@
|
||||||
from django.http import HttpResponse
|
from uuid import uuid4
|
||||||
from django.template import loader
|
|
||||||
|
|
||||||
|
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):
|
def colloscope(request):
|
||||||
template = loader.get_template("colloscope.html")
|
try:
|
||||||
return HttpResponse(template.render())
|
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,191 @@
|
||||||
|
"""
|
||||||
|
Django settings for kholles_web project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.0.4.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'SECRET'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
|
"http://localhost:8000",
|
||||||
|
"http://127.0.0.1:8000",
|
||||||
|
]
|
||||||
|
|
||||||
|
CORS_ORIGIN_WHITELIST = [
|
||||||
|
"http://localhost:8000",
|
||||||
|
"http://127.0.0.1:8000",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"daphne",
|
||||||
|
'django.contrib.admin',
|
||||||
|
'oauth2_provider',
|
||||||
|
'corsheaders',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
"rest_framework",
|
||||||
|
'rest_framework_simplejwt',
|
||||||
|
'colloscope',
|
||||||
|
"drf_spectacular",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'corsheaders.middleware.CorsMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'kholles_web.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [BASE_DIR / "templates"],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
ASGI_APPLICATION = "kholles_web.asgi.application"
|
||||||
|
WSGI_APPLICATION = 'kholles_web.wsgi.application'
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'fr'
|
||||||
|
|
||||||
|
TIME_ZONE = 'Europe/Paris'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
LOCALE_PATHS = [
|
||||||
|
BASE_DIR / 'locale',
|
||||||
|
]
|
||||||
|
|
||||||
|
LANGUAGES = (
|
||||||
|
('en', _('English')),
|
||||||
|
('fr', _('French')),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
BASE_DIR / "static",
|
||||||
|
]
|
||||||
|
|
||||||
|
STATICFILES_FINDERS = [
|
||||||
|
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||||
|
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
LOGIN_URL = "/accounts/login"
|
||||||
|
LOGIN_REDIRECT_URL = "home"
|
||||||
|
LOGOUT_REDIRECT_URL = "home"
|
||||||
|
|
||||||
|
DISCORD_NOTIFY_WEBHOOK_URL = "https://discord.com/api/webhooks/YOUR_URL"
|
||||||
|
DISCORD_NOTIFY_WEBHOOK_USERNAME = "Watchdog"
|
||||||
|
|
||||||
|
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||||
|
EMAIL_HOST = "HOST"
|
||||||
|
EMAIL_PORT = 587
|
||||||
|
EMAIL_HOST_USER = "EMAIL"
|
||||||
|
EMAIL_HOST_PASSWORD = "PASSWORD"
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
|
||||||
|
'PAGE_SIZE': 100,
|
||||||
|
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
|
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||||
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SPECTACULAR_SETTINGS = {
|
||||||
|
'TITLE': 'Colloscope API',
|
||||||
|
'DESCRIPTION': 'Gérer les colles de prépa devient facile',
|
||||||
|
'VERSION': '1.0.0',
|
||||||
|
'SERVE_INCLUDE_SCHEMA': False,
|
||||||
|
# OTHER SETTINGS
|
||||||
|
}
|
|
@ -14,10 +14,48 @@ Including another URLconf
|
||||||
1. Import the include() function: from django.urls import include, path
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin, auth
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.contrib.staticfiles import views as vstatic
|
||||||
|
from rest_framework import routers
|
||||||
|
from rest_framework_simplejwt.views import (
|
||||||
|
TokenObtainPairView,
|
||||||
|
TokenRefreshView,
|
||||||
|
)
|
||||||
|
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
||||||
|
|
||||||
|
from colloscope.views import home_redirect
|
||||||
|
from colloscope.viewsets import *
|
||||||
|
|
||||||
|
router = routers.SimpleRouter()
|
||||||
|
router.register('school', SchoolViewset, basename='school')
|
||||||
|
router.register('class', ClassViewset, basename='class')
|
||||||
|
router.register('term', TermViewset, basename='term')
|
||||||
|
router.register("subject", SubjectViewset, basename='subject')
|
||||||
|
router.register('grouptype', GroupTypeViewset, basename='grouptype')
|
||||||
|
router.register("group", GroupViewset, basename='group')
|
||||||
|
router.register("student", StudentViewset, basename='student')
|
||||||
|
router.register("colleur", ColleurViewset, basename='colleur')
|
||||||
|
router.register("slot", SlotViewset, basename='slot')
|
||||||
|
router.register("colle", ColleViewset, basename='colle')
|
||||||
|
router.register("calendarlink", CalendarLinkViewset, basename='calendarlink')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include('colloscope.urls')),
|
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('admin/', admin.site.urls),
|
||||||
|
path('accounts/', include("django.contrib.auth.urls")),
|
||||||
]
|
]
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,60 @@
|
||||||
|
aiohttp==3.9.5
|
||||||
|
aiosignal==1.3.1
|
||||||
|
asgiref==3.8.1
|
||||||
|
attrs==23.2.0
|
||||||
|
autobahn==23.6.2
|
||||||
|
Automat==22.10.0
|
||||||
|
certifi==2024.2.2
|
||||||
|
cffi==1.16.0
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
constantly==23.10.4
|
||||||
|
cryptography==42.0.5
|
||||||
|
daphne==4.1.2
|
||||||
|
defusedxml==0.7.1
|
||||||
|
discord.py==2.3.2
|
||||||
|
Django==5.0.4
|
||||||
|
django-cors-headers==4.3.1
|
||||||
|
django-oauth-toolkit==2.3.0
|
||||||
|
django-smtp-ssl==1.0
|
||||||
|
djangorestframework==3.15.1
|
||||||
|
djangorestframework-simplejwt==5.3.1
|
||||||
|
drf-spectacular==0.27.2
|
||||||
|
fonttools==4.51.0
|
||||||
|
fpdf2==2.7.8
|
||||||
|
frozenlist==1.4.1
|
||||||
|
hyperlink==21.0.0
|
||||||
|
icalendar==5.0.12
|
||||||
|
idna==3.7
|
||||||
|
incremental==22.10.0
|
||||||
|
inflection==0.5.1
|
||||||
|
jsonschema==4.22.0
|
||||||
|
jsonschema-specifications==2023.12.1
|
||||||
|
jwcrypto==1.5.6
|
||||||
|
multidict==6.0.5
|
||||||
|
numpy==1.26.4
|
||||||
|
oauthlib==3.2.2
|
||||||
|
pandas==2.2.2
|
||||||
|
pillow==10.3.0
|
||||||
|
pyasn1==0.6.0
|
||||||
|
pyasn1_modules==0.4.0
|
||||||
|
pycparser==2.22
|
||||||
|
PyJWT==2.8.0
|
||||||
|
pyOpenSSL==24.1.0
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
pytz==2024.1
|
||||||
|
PyYAML==6.0.1
|
||||||
|
referencing==0.35.1
|
||||||
|
requests==2.31.0
|
||||||
|
rpds-py==0.18.0
|
||||||
|
service-identity==24.1.0
|
||||||
|
setuptools==69.5.1
|
||||||
|
six==1.16.0
|
||||||
|
sqlparse==0.4.4
|
||||||
|
Twisted==24.3.0
|
||||||
|
txaio==23.1.1
|
||||||
|
typing_extensions==4.11.0
|
||||||
|
tzdata==2024.1
|
||||||
|
uritemplate==4.1.1
|
||||||
|
urllib3==2.2.1
|
||||||
|
yarl==1.9.4
|
||||||
|
zope.interface==6.3
|
|
@ -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
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
168
static/main.css
168
static/main.css
|
@ -1,16 +1,122 @@
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
margin: 0;
|
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 {
|
main {
|
||||||
margin: 5px;
|
margin: 20px auto;
|
||||||
|
width: clamp(350px, 60%, 1200px);
|
||||||
|
background-color: white;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.login table {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.login input[type=text], form.login input[type=password] {
|
||||||
|
padding: 5px;
|
||||||
|
border: none;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.login button {
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: dodgerblue;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
display: block;
|
||||||
|
margin: 15px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.login button:hover {
|
||||||
|
background-color: #0483ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.login button:active {
|
||||||
|
background-color: #0077ea;
|
||||||
|
}
|
||||||
|
|
||||||
nav.semaine {
|
nav.semaine {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -39,3 +145,63 @@ p.programme {
|
||||||
footer {
|
footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.week {
|
||||||
|
background-color: dodgerblue;
|
||||||
|
color: white;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.week.empty {
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle-wrapper {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 400px)
|
||||||
|
{
|
||||||
|
.colle-wrapper {
|
||||||
|
grid-template-columns: repeat(3, minmax(100px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle span {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle ul {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle li {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle form {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle button {
|
||||||
|
padding: 5px;
|
||||||
|
border: none;
|
||||||
|
background-color: #c0392b;
|
||||||
|
color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle button:hover {
|
||||||
|
background-color: #a93226;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colle button:active {
|
||||||
|
background-color: #922b21;
|
||||||
|
}
|
|
@ -0,0 +1,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