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 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": [
@ -64,7 +69,6 @@ class Classe(models.Model):
return self.no_semaine(date.today())
def date_debut_sem(self, n):
"""
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)
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
@ -175,8 +200,9 @@ 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())
@ -260,6 +286,10 @@ class Creneau(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)
groupes = models.ManyToManyField(Groupe)
date = models.DateField()
@ -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,10 +356,13 @@ 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"))
@ -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)

View File

@ -20,7 +20,13 @@ Bienvenue {{ etudiant }}. Votre lycée est {{ periode.classe.lycee.libelle }}, e
<li>Semaine {{n}} ({{lundi}})</li>
<ul>
{% 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 %}
</ul>
{% 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("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/<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 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/")
@ -66,9 +66,6 @@ def get_lien_calendrier(etudiant, periode):
@login_required
def dashboard(request):
user = request.user
session = request.session
try:
etudiant = Profil.from_request(
request,
@ -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,10 +105,7 @@ def dashboard(request):
@login_required
def colloscope(request):
user = request.user
session = request.session
def marketplace(request):
try:
etudiant = Profil.from_request(
request,
@ -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,7 +159,6 @@ 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")
@ -181,21 +209,64 @@ def get_lien_calendrier(etudiant, periode):
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())