diff --git a/colloscope/models.py b/colloscope/models.py
index 44c2230..429df07 100644
--- a/colloscope/models.py
+++ b/colloscope/models.py
@@ -1,18 +1,23 @@
from datetime import date, datetime, timedelta
from pytz import timezone
-from django.db import models
-from django.db.models import F, Q
-from django.contrib.auth.models import User
+import asyncio
+import aiohttp
+from django.db import models
+from django.db.models import F, Q, Count
+from django.contrib.auth.models import User
+from django.conf import settings
+
+from discord import Webhook
calendrier = {
- "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é
+ "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é
]
}
@@ -47,10 +52,10 @@ class Classe(models.Model):
vacances = calendrier[zone]
jour0 = self.jour_zero
- n = 1 + ((jour - jour0).days)//7
+ n = 1 + ((jour - jour0).days) // 7
for debut, fin in vacances:
if jour > debut:
- n -= round( ( fin - debut )/timedelta(weeks=1) )
+ n -= round((fin - debut) / timedelta(weeks=1))
return n
def no_aujourdhui(self):
@@ -64,7 +69,6 @@ class Classe(models.Model):
return self.no_semaine(date.today())
-
def date_debut_sem(self, n):
"""
Entrée:
@@ -74,16 +78,16 @@ class Classe(models.Model):
Sortie:
- Le date du lundi de la semaine n
"""
-
+
zone = self.lycee.vacances
vacances = calendrier[zone]
jour0 = self.jour_zero
- jour = jour0 + (n-1) * timedelta(weeks=1)
-
+ jour = jour0 + (n - 1) * timedelta(weeks=1)
+
for debut, fin in vacances:
if jour >= debut:
- jour += round( (fin - debut)/timedelta(weeks=1) )*timedelta(weeks=1)
+ jour += round((fin - debut) / timedelta(weeks=1)) * timedelta(weeks=1)
return jour
@@ -109,9 +113,9 @@ class Classe(models.Model):
"""
return Periode.objects \
- .filter(classe=self, fin__gte=date.today()) \
- .order_by("-debut") \
- .first()
+ .filter(classe=self, fin__gte=date.today()) \
+ .order_by("-debut") \
+ .first()
def __str__(self):
return f"{self.libelle} ({self.lycee.libelle})"
@@ -135,11 +139,32 @@ class Periode(models.Model):
Sortie:
- Un range des numéros de semaine
"""
- return range(self.classe.no_semaine(self.debut), self.classe.no_semaine(self.fin)+1)
+ return range(self.classe.no_semaine(self.debut), self.classe.no_semaine(self.fin) + 1)
def query_rotations(self):
- return Rotation.objects.filter(creneau__periode=self)
+ return (Rotation.objects
+ .select_related("creneau", "creneau__periode")
+ .prefetch_related("amendement_set")
+ .filter(creneau__periode=self)
+ .annotate(adt_plus=Count("amendement", filter=Q(amendement__est_positif=1)))
+ .annotate(adt_minus=Count("amendement", filter=Q(amendement__est_positif=0)))
+ .annotate(volume=F("creneau__capacite") + F("adt_plus") - F("adt_minus")))
+ def query_rotations_etudiant(self, etudiant):
+ return (Rotation.objects
+ .select_related("creneau", "creneau__periode")
+ .prefetch_related("amendement_set")
+ .filter(creneau__periode=self)
+ .filter((Q(groupes__etudiant=etudiant)
+ & ~Q(amendement__est_positif=0, amendement__etudiant=etudiant))
+ | Q(amendement__est_positif=1, amendement__etudiant=etudiant))
+ .annotate(adt_plus=Count("amendement", filter=Q(amendement__est_positif=1)))
+ .annotate(adt_minus=Count("amendement", filter=Q(amendement__est_positif=0)))
+ .annotate(volume=F("creneau__capacite") + F("adt_plus") - F("adt_minus")))
+
+ def query_rotations_not_full(self):
+ return (self.query_rotations()
+ .filter(volume__lt=F("creneau__capacite"), date__gte=date.today()))
def __str__(self):
return self.libelle
@@ -165,7 +190,7 @@ class Critere(models.Model):
class Groupe(models.Model):
#class Meta:
# ordering=[F("periode").classe.libelle, F("periode").libelle, "libelle"]
-
+
periode = models.ForeignKey(Periode, on_delete=models.CASCADE)
critere = models.ForeignKey(Critere, null=True, on_delete=models.CASCADE)
libelle = models.CharField(max_length=100)
@@ -175,23 +200,24 @@ class Groupe(models.Model):
def __str__(self):
return self.libelle
- def get_colles(self):
- return Rotation.objects.filter(groupes=self).order_by("date")
+ #def get_colles(self):
+ # return Rotation.objects.filter(creneau__periode=self.periode,
+ # Q(groupes=self) || Q(a)).order_by("date")
def get_colles_par_sem(self):
- semaines = ( (s, self.periode.classe.date_debut_sem(s)) for s in self.periode.range_semaines() )
+ semaines = ((s, self.periode.classe.date_debut_sem(s)) for s in self.periode.range_semaines())
colles_flat = self.get_colles()
return [
(sem, lundi,
- colles_flat.filter(date__gte=lundi, date__lt=lundi+timedelta(weeks=1)))
+ colles_flat.filter(date__gte=lundi, date__lt=lundi + timedelta(weeks=1)))
for sem, lundi in semaines
]
class Etudiant(models.Model):
class Meta:
- ordering=["classe", "nom", "prenom"]
+ ordering = ["classe", "nom", "prenom"]
classe = models.ForeignKey(Classe, on_delete=models.CASCADE)
prenom = models.CharField(max_length=100)
@@ -255,11 +281,15 @@ class Creneau(models.Model):
def __str__(self):
jours = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
-
+
return f"Colle {self.matiere} avec {self.colleur} {jours[self.jour]} {self.heure}"
class Rotation(models.Model):
+ class Meta:
+ ordering = ["creneau__periode__classe", "creneau__matiere__libelle", "creneau__colleur__nom", "date",
+ "creneau__heure"]
+
creneau = models.ForeignKey(Creneau, on_delete=models.CASCADE)
groupes = models.ManyToManyField(Groupe)
date = models.DateField()
@@ -274,11 +304,11 @@ class Rotation(models.Model):
"""
Renvoie les étudiants inscrits à la colle en tenant compte des amendements.
"""
- amendements=Amendement.objects.filter(rotation=self)
+ amendements = Amendement.objects.filter(rotation=self)
return Etudiant.objects.filter(
- ( Q(id__in=Appartenance.objects.filter(groupe__in=self.groupes.all()))
- | Q(id__in=amendements.filter(est_positif=True).values("etudiant_id")) )
+ (Q(id__in=Appartenance.objects.filter(groupe__in=self.groupes.all()))
+ | Q(id__in=amendements.filter(est_positif=True).values("etudiant_id")))
& ~Q(id__in=amendements.filter(est_positif=False).values("etudiant_id"))
)
@@ -297,7 +327,7 @@ class Rotation(models.Model):
Renvoie si la colle est pleine.
"""
eff = self.effectif()
- return eff>=self.creneau.capacite
+ return eff >= self.creneau.capacite
def est_modifiee(self):
"""
@@ -305,7 +335,7 @@ class Rotation(models.Model):
"""
return Amendement.objects.filter(rotation=self).exists()
- def amender(self, etudiant, est_positif):
+ def amender(self, etudiant, est_positif, notifier=False):
"""
Amende la colle en (des)inscrivant etudiant à la colle self, selon est_positif.
"""
@@ -326,12 +356,15 @@ class Rotation(models.Model):
amendement = Amendement(rotation=self, etudiant=etudiant, est_positif=est_positif)
amendement.save()
+ if notifier:
+ loop =
+ asyncio.run_until_complete(amendement.notifier())
+
def __str__(self):
return f"{self.creneau} le {self.date} avec groupes {'+'.join(str(groupe) for groupe in self.groupes.all())}"
-
def datetime(self):
- return datetime.combine( self.date, self.creneau.heure, tzinfo=timezone("Europe/Paris") )
+ return datetime.combine(self.date, self.creneau.heure, tzinfo=timezone("Europe/Paris"))
class Amendement(models.Model):
@@ -339,6 +372,15 @@ class Amendement(models.Model):
rotation = models.ForeignKey(Rotation, on_delete=models.CASCADE)
etudiant = models.ForeignKey(Etudiant, on_delete=models.CASCADE)
+ async def notifier(self):
+ async with aiohttp.ClientSession() as session:
+ webhook = Webhook.from_url(settings.DISCORD_NOTIFY_WEBHOOK_URL, session=session)
+
+ if self.est_positif:
+ await webhook.send(f"Colle réservée : {self.rotation}", username=settings.DISCORD_NOTIFY_WEBHOOK_USERNAME)
+ else:
+ await webhook.send(f"Colle libérée : {self.rotation}", username=settings.DISCORD_NOTIFY_WEBHOOK_USERNAME)
+
class Profil(models.Model):
utilisateur = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
diff --git a/colloscope/templates/dashboard.html b/colloscope/templates/dashboard.html
index d5ecb3c..959b644 100644
--- a/colloscope/templates/dashboard.html
+++ b/colloscope/templates/dashboard.html
@@ -20,7 +20,13 @@ Bienvenue {{ etudiant }}. Votre lycée est {{ periode.classe.lycee.libelle }}, e
Semaine {{n}} ({{lundi}})
{% for colle in colles %}
- - {{colle}}
+ - {{ colle.creneau.matiere }} ({{ colle.creneau.colleur }}) Absent ?:
+
+ - Le {{ colle.date }} à {{ colle.creneau.heure }}
+ - Groupes : {{ colle.groupes.all | join:"+" }}
+ - Salle : {{ colle.creneau.salle }}
+ - Capacité : {{ colle.volume }} / {{ colle.creneau.capacite }}
+
{% endfor %}
{% endfor %}
diff --git a/colloscope/templates/marketplace.html b/colloscope/templates/marketplace.html
new file mode 100644
index 0000000..5cddef6
--- /dev/null
+++ b/colloscope/templates/marketplace.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block title %}Marketplace{% endblock title %}
+
+{% block main %}
+ Marketplace
+
+ Bienvenue sur le marketplace.
+
+ Les colles libres sont :
+
+
+ {% for colle in colles %}
+ - Colle !
+
+ - {{ colle }}
+ - Places occupées : {{ colle.volume }} / {{ colle.creneau.capacite }}
+ - Réserver
+
+ {% endfor %}
+
+
+{% endblock main %}
\ No newline at end of file
diff --git a/colloscope/urls.py b/colloscope/urls.py
index aa72ca1..06c7916 100644
--- a/colloscope/urls.py
+++ b/colloscope/urls.py
@@ -8,4 +8,7 @@ urlpatterns = [
path("export.pdf", views.export, name="colloscope.export"),
path("calendrier.ics", views.icalendar, name="colloscope.calendrier"),
path("choix_profil", views.choix_profil, name="colloscope.choix_profil"),
+ path("marketplace.html", views.marketplace, name="colloscope.marketplace"),
+ path("action/inscription/", views.inscription, name="colloscope.inscription"),
+ path("action/desinscription/", views.desinscription, name="colloscope.desinscription"),
]
diff --git a/colloscope/views.py b/colloscope/views.py
index fdeac66..c756782 100644
--- a/colloscope/views.py
+++ b/colloscope/views.py
@@ -1,6 +1,7 @@
from datetime import date, timedelta
from uuid import uuid4
+from django.contrib.auth.models import User
from django.shortcuts import redirect
from django.http import HttpResponse
from django.template import loader
@@ -34,7 +35,6 @@ def choix_profil(request):
else:
profil = Profil.objects.get(utilisateur=user)
-
if profil.etudiant is not None and profil.colleur is None:
session["profil"] = "etudiant"
return redirect("/colloscope/")
@@ -60,22 +60,19 @@ def get_lien_calendrier(etudiant, periode):
code = uuid4().hex
lien = LienCalendrier(code=code, etudiant=etudiant, periode=periode)
lien.save()
-
+
return f"calendrier.ics?key={lien.code}"
@login_required
def dashboard(request):
- user = request.user
- session = request.session
-
try:
etudiant = Profil.from_request(
- request,
- preprocess=lambda query: query \
- .select_related("etudiant__classe") \
- .prefetch_related("etudiant__classe__periode_set")
- )
+ request,
+ preprocess=lambda query: query \
+ .select_related("etudiant__classe") \
+ .prefetch_related("etudiant__classe__periode_set")
+ )
except ValueError:
return redirect("colloscope.choix_profil")
@@ -84,7 +81,15 @@ def dashboard(request):
periode = etudiant.classe.periode_actuelle()
groupe = etudiant.groupe_de_colle(periode)
- rotations = groupe.get_colles_par_sem()
+
+ rotations = periode.query_rotations_etudiant(etudiant)
+
+ colles_par_sem = [None] * len(periode.range_semaines())
+ for i, n in enumerate(periode.range_semaines()):
+ lundi = periode.classe.date_debut_sem(n)
+ colles = rotations.filter(date__gte=lundi, date__lt=lundi+timedelta(weeks=1))
+ colles_par_sem[i] = n, lundi, colles
+
template = loader.get_template("dashboard.html")
lien_calendrier = get_lien_calendrier(etudiant, periode)
@@ -92,7 +97,7 @@ def dashboard(request):
"etudiant": etudiant,
"periode": periode,
"groupe": groupe,
- "rotations" : rotations,
+ "rotations": colles_par_sem,
"lien_calendrier": lien_calendrier,
}
@@ -100,17 +105,14 @@ def dashboard(request):
@login_required
-def colloscope(request):
- user = request.user
- session = request.session
-
+def marketplace(request):
try:
etudiant = Profil.from_request(
- request,
- preprocess=lambda query: query \
- .select_related("etudiant__classe") \
- .prefetch_related("etudiant__classe__periode_set")
- )
+ request,
+ preprocess=lambda query: query \
+ .select_related("etudiant__classe") \
+ .prefetch_related("etudiant__classe__periode_set")
+ )
except ValueError:
return redirect("colloscope.choix_profil")
@@ -118,6 +120,33 @@ def colloscope(request):
return HttpResponse("pas encore supporté")
+ periode = etudiant.classe.periode_actuelle()
+ colles = periode.query_rotations_not_full()
+
+ template = loader.get_template("marketplace.html")
+
+ context = {
+ "colles" : colles
+ }
+
+ return HttpResponse(template.render(context, request))
+
+
+@login_required
+def colloscope(request):
+ try:
+ etudiant = Profil.from_request(
+ request,
+ preprocess=lambda query: query \
+ .select_related("etudiant__classe") \
+ .prefetch_related("etudiant__classe__periode_set")
+ )
+ except ValueError:
+ return redirect("colloscope.choix_profil")
+
+ if not isinstance(etudiant, Etudiant):
+ return HttpResponse("pas encore supporté")
+
periode_str = request.GET.get("periode")
if periode_str is None:
periode = etudiant.classe.periode_actuelle()
@@ -130,18 +159,17 @@ def colloscope(request):
response = HttpResponse(template.render(context, request))
response.status_code = 404
-
creneaux = Creneau.objects \
- .filter(periode=periode, est_colle=True) \
- .prefetch_related("rotation_set")
+ .filter(periode=periode, est_colle=True) \
+ .prefetch_related("rotation_set")
semaines = periode.range_semaines()
- rotations = [ (c, []) for c in creneaux ]
+ rotations = [(c, []) for c in creneaux]
for c, l in rotations:
for sem in semaines:
lundi = periode.classe.date_debut_sem(sem)
- rot = Rotation.objects.filter(creneau=c, date__gte=lundi, date__lt=lundi+timedelta(weeks=1))
+ rot = Rotation.objects.filter(creneau=c, date__gte=lundi, date__lt=lundi + timedelta(weeks=1))
exists = rot.exists()
if exists:
@@ -152,15 +180,15 @@ def colloscope(request):
r = est_modifiee = groupes = None
l.append((sem, exists, r, est_modifiee, groupes))
-
+
template = loader.get_template("table.html")
context = {
"periode": periode,
"semaines": semaines,
"lundis": [periode.classe.date_debut_sem(n) for n in semaines],
- "jours" : ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
- "rotations" : rotations,
+ "jours": ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
+ "rotations": rotations,
}
return HttpResponse(template.render(context, request))
@@ -178,24 +206,67 @@ def get_lien_calendrier(etudiant, periode):
code = uuid4().hex
lien = LienCalendrier(code=code, etudiant=etudiant, periode=periode)
lien.save()
-
+
return f"calendrier.ics?key={lien.code}"
+
def icalendar(request):
if request.GET.get("key") is not None:
try:
lien = LienCalendrier.objects.get(code=request.GET.get("key"))
if not request.GET.get("edt"):
- return HttpResponse(to_calendar(lien.etudiant, lien.periode, include_EDT=True).to_ical(), content_type="text/calendar")
+ return HttpResponse(to_calendar(lien.etudiant, lien.periode, include_EDT=True).to_ical(),
+ content_type="text/calendar")
- return HttpResponse(to_calendar(lien.etudiant, lien.periode, include_EDT=True).to_ical(), content_type="text/calendar")
+ return HttpResponse(to_calendar(lien.etudiant, lien.periode, include_EDT=True).to_ical(),
+ content_type="text/calendar")
except LienCalendrier.DoesNotExist:
return HttpResponse("Invalid key", status=404)
else:
return HttpResponse("Unspecified key", status=404)
+
+@login_required
+def amender(request, colle_id, est_positif):
+ try:
+ etudiant = Profil.from_request(
+ request,
+ preprocess=lambda query: query \
+ .select_related("etudiant__classe") \
+ .prefetch_related("etudiant__classe__periode_set")
+ )
+ except ValueError:
+ return redirect("colloscope.choix_profil")
+
+ if not isinstance(etudiant, Etudiant):
+ return HttpResponse("pas encore supporté")
+
+ #try:
+ if est_positif:
+ (Rotation.objects
+ .get(id=colle_id, creneau__periode__classe=etudiant.classe)
+ .amender(est_positif=True, etudiant=etudiant, notifier=True))
+ else:
+ (Rotation.objects
+ .get(id=colle_id, groupes__etudiant=etudiant)
+ .amender(est_positif=False, etudiant=etudiant, notifier=True))
+
+ return HttpResponse("ok")
+ #except Exception as e:
+ # return HttpResponse(f"aïe : {e}")
+
+
+@login_required
+def inscription(request, colle_id):
+ return amender(request, colle_id, True)
+
+@login_required
+def desinscription(request, colle_id):
+ return amender(request, colle_id, False)
+
+
def data_dump(request):
template = loader.get_template("data_dump.html")
return HttpResponse(template.render())