add marketplace

This commit is contained in:
Valentin Moguérou 2024-04-30 19:39:00 +02:00
parent 3dd379f735
commit 44f9f45112
5 changed files with 213 additions and 68 deletions

View File

@ -1,10 +1,15 @@
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from pytz import timezone from pytz import timezone
from django.db import models import asyncio
from django.db.models import F, Q import aiohttp
from django.contrib.auth.models import User
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 = { calendrier = {
"C": [ "C": [
@ -64,7 +69,6 @@ class Classe(models.Model):
return self.no_semaine(date.today()) return self.no_semaine(date.today())
def date_debut_sem(self, n): def date_debut_sem(self, n):
""" """
Entrée: Entrée:
@ -138,8 +142,29 @@ class Periode(models.Model):
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): 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): def __str__(self):
return self.libelle return self.libelle
@ -175,8 +200,9 @@ class Groupe(models.Model):
def __str__(self): def __str__(self):
return self.libelle return self.libelle
def get_colles(self): #def get_colles(self):
return Rotation.objects.filter(groupes=self).order_by("date") # return Rotation.objects.filter(creneau__periode=self.periode,
# Q(groupes=self) || Q(a)).order_by("date")
def get_colles_par_sem(self): def get_colles_par_sem(self):
semaines = ((s, self.periode.classe.date_debut_sem(s)) for s in self.periode.range_semaines()) semaines = ((s, self.periode.classe.date_debut_sem(s)) for s in self.periode.range_semaines())
@ -260,6 +286,10 @@ class Creneau(models.Model):
class Rotation(models.Model): 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) creneau = models.ForeignKey(Creneau, on_delete=models.CASCADE)
groupes = models.ManyToManyField(Groupe) groupes = models.ManyToManyField(Groupe)
date = models.DateField() date = models.DateField()
@ -305,7 +335,7 @@ class Rotation(models.Model):
""" """
return Amendement.objects.filter(rotation=self).exists() 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. Amende la colle en (des)inscrivant etudiant à la colle self, selon est_positif.
""" """
@ -326,10 +356,13 @@ class Rotation(models.Model):
amendement = Amendement(rotation=self, etudiant=etudiant, est_positif=est_positif) amendement = Amendement(rotation=self, etudiant=etudiant, est_positif=est_positif)
amendement.save() amendement.save()
if notifier:
loop =
asyncio.run_until_complete(amendement.notifier())
def __str__(self): def __str__(self):
return f"{self.creneau} le {self.date} avec groupes {'+'.join(str(groupe) for groupe in self.groupes.all())}" return f"{self.creneau} le {self.date} avec groupes {'+'.join(str(groupe) for groupe in self.groupes.all())}"
def datetime(self): 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"))
@ -339,6 +372,15 @@ class Amendement(models.Model):
rotation = models.ForeignKey(Rotation, on_delete=models.CASCADE) rotation = models.ForeignKey(Rotation, on_delete=models.CASCADE)
etudiant = models.ForeignKey(Etudiant, 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): class Profil(models.Model):
utilisateur = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True) utilisateur = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)

View File

@ -20,7 +20,13 @@ Bienvenue {{ etudiant }}. Votre lycée est {{ periode.classe.lycee.libelle }}, e
<li>Semaine {{n}} ({{lundi}})</li> <li>Semaine {{n}} ({{lundi}})</li>
<ul> <ul>
{% for colle in colles %} {% for colle in colles %}
<li>{{colle}}</li> <li>{{ colle.creneau.matiere }} ({{ colle.creneau.colleur }}) <a href="{% url "colloscope.desinscription" colle_id=colle.id %}">Absent&nbsp;?</a>:</li>
<ul>
<li>Le {{ colle.date }} à {{ colle.creneau.heure }}</li>
<li>Groupes&nbsp;: {{ colle.groupes.all | join:"+" }}</li>
<li>Salle&nbsp;: {{ colle.creneau.salle }}</li>
<li>Capacité : {{ colle.volume }} / {{ colle.creneau.capacite }}</li>
</ul>
{% endfor %} {% endfor %}
</ul> </ul>
{% endfor %} {% endfor %}

View File

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% block title %}Marketplace{% endblock title %}
{% block main %}
<h1>Marketplace</h1>
Bienvenue sur le marketplace.
Les colles libres sont&nbsp;:
<ul>
{% for colle in colles %}
<li>Colle !</li>
<ul>
<li>{{ colle }}</li>
<li>Places occupées&nbsp;: {{ colle.volume }} / {{ colle.creneau.capacite }}</li>
<li><a href={% url "colloscope.inscription" colle_id=colle.id %}>Réserver</a></li>
</ul>
{% endfor %}
</ul>
{% endblock main %}

View File

@ -8,4 +8,7 @@ urlpatterns = [
path("export.pdf", views.export, name="colloscope.export"), path("export.pdf", views.export, name="colloscope.export"),
path("calendrier.ics", views.icalendar, name="colloscope.calendrier"), path("calendrier.ics", views.icalendar, name="colloscope.calendrier"),
path("choix_profil", views.choix_profil, name="colloscope.choix_profil"), path("choix_profil", views.choix_profil, name="colloscope.choix_profil"),
path("marketplace.html", views.marketplace, name="colloscope.marketplace"),
path("action/inscription/<int:colle_id>", views.inscription, name="colloscope.inscription"),
path("action/desinscription/<int:colle_id>", views.desinscription, name="colloscope.desinscription"),
] ]

View File

@ -1,6 +1,7 @@
from datetime import date, timedelta from datetime import date, timedelta
from uuid import uuid4 from uuid import uuid4
from django.contrib.auth.models import User
from django.shortcuts import redirect from django.shortcuts import redirect
from django.http import HttpResponse from django.http import HttpResponse
from django.template import loader from django.template import loader
@ -34,7 +35,6 @@ def choix_profil(request):
else: else:
profil = Profil.objects.get(utilisateur=user) profil = Profil.objects.get(utilisateur=user)
if profil.etudiant is not None and profil.colleur is None: if profil.etudiant is not None and profil.colleur is None:
session["profil"] = "etudiant" session["profil"] = "etudiant"
return redirect("/colloscope/") return redirect("/colloscope/")
@ -66,9 +66,6 @@ def get_lien_calendrier(etudiant, periode):
@login_required @login_required
def dashboard(request): def dashboard(request):
user = request.user
session = request.session
try: try:
etudiant = Profil.from_request( etudiant = Profil.from_request(
request, request,
@ -84,7 +81,15 @@ def dashboard(request):
periode = etudiant.classe.periode_actuelle() periode = etudiant.classe.periode_actuelle()
groupe = etudiant.groupe_de_colle(periode) 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") template = loader.get_template("dashboard.html")
lien_calendrier = get_lien_calendrier(etudiant, periode) lien_calendrier = get_lien_calendrier(etudiant, periode)
@ -92,7 +97,7 @@ def dashboard(request):
"etudiant": etudiant, "etudiant": etudiant,
"periode": periode, "periode": periode,
"groupe": groupe, "groupe": groupe,
"rotations" : rotations, "rotations": colles_par_sem,
"lien_calendrier": lien_calendrier, "lien_calendrier": lien_calendrier,
} }
@ -100,10 +105,7 @@ def dashboard(request):
@login_required @login_required
def colloscope(request): def marketplace(request):
user = request.user
session = request.session
try: try:
etudiant = Profil.from_request( etudiant = Profil.from_request(
request, request,
@ -118,6 +120,33 @@ def colloscope(request):
return HttpResponse("pas encore supporté") 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") periode_str = request.GET.get("periode")
if periode_str is None: if periode_str is None:
periode = etudiant.classe.periode_actuelle() periode = etudiant.classe.periode_actuelle()
@ -130,7 +159,6 @@ def colloscope(request):
response = HttpResponse(template.render(context, request)) response = HttpResponse(template.render(context, request))
response.status_code = 404 response.status_code = 404
creneaux = Creneau.objects \ creneaux = Creneau.objects \
.filter(periode=periode, est_colle=True) \ .filter(periode=periode, est_colle=True) \
.prefetch_related("rotation_set") .prefetch_related("rotation_set")
@ -181,21 +209,64 @@ def get_lien_calendrier(etudiant, periode):
return f"calendrier.ics?key={lien.code}" return f"calendrier.ics?key={lien.code}"
def icalendar(request): def icalendar(request):
if request.GET.get("key") is not None: if request.GET.get("key") is not None:
try: try:
lien = LienCalendrier.objects.get(code=request.GET.get("key")) lien = LienCalendrier.objects.get(code=request.GET.get("key"))
if not request.GET.get("edt"): 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: except LienCalendrier.DoesNotExist:
return HttpResponse("Invalid key", status=404) return HttpResponse("Invalid key", status=404)
else: else:
return HttpResponse("Unspecified key", status=404) 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): def data_dump(request):
template = loader.get_template("data_dump.html") template = loader.get_template("data_dump.html")
return HttpResponse(template.render()) return HttpResponse(template.render())