diff --git a/colloscope/models.py b/colloscope/models.py index fb93ddc..08925c2 100644 --- a/colloscope/models.py +++ b/colloscope/models.py @@ -1,11 +1,12 @@ from datetime import date, datetime, timedelta +from pprint import pprint from pytz import timezone import aiohttp from django.db import models -from django.db.models import F, Q, Count, QuerySet +from django.db.models import F, Q, Count, QuerySet, Subquery, OuterRef, Sum from django.contrib.auth.models import User from django.conf import settings @@ -144,25 +145,35 @@ class Term(models.Model): .select_related("slot", "slot__term") .prefetch_related("swap_set") .filter(slot__term=self) - .annotate(adt_plus=Count("swap", filter=Q(swap__enroll=1))) - .annotate(adt_minus=Count("swap", filter=Q(swap__enroll=0))) - .annotate(volume=F("slot__capacity") + F("adt_plus") - F("adt_minus"))) + .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"))) 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) - .filter((Q(groups__student=student) - & ~Q(swap__enroll=0, swap__student=student)) - | Q(swap__enroll=1, swap__student=student)) - .annotate(adt_plus=Count("swap", filter=Q(swap__enroll=1))) - .annotate(adt_minus=Count("swap", filter=Q(swap__enroll=0))) - .annotate(volume=F("slot__capacity") + F("adt_plus") - F("adt_minus"))) + .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) + ) + + 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)) - def query_colles_not_full(self) -> QuerySet: return (self.query_colles() - .filter(volume__lt=F("slot__capacity"), date__gte=date.today())) + .filter(volume__lt=F("slot__capacity"), date__gte=date.today()) + .exclude(has_student)) def __str__(self) -> str: return self.description @@ -295,7 +306,7 @@ class Colle(models.Model): "slot__time"] slot = models.ForeignKey(Slot, on_delete=models.CASCADE) - groups = models.ManyToManyField(Group) + groups = models.ManyToManyField(Group, blank=True) date = models.DateField() def initial_group(self): @@ -319,23 +330,22 @@ class Colle(models.Model): def is_attendee(self, student): return self.final_group().contains(student) - - def volume(self): + def get_volume(self): """ Renvoie le nombre d'étudiants inscrits à la colle en tenant compte des swaps. """ - n_base = sum(group.members.count() for group in self.groups.all()) - n_plus = len(Swap.objects.filter(enroll=True, colle=self)) - n_moins = len(Swap.objects.filter(enroll=False, colle=self)) - - return n_base + n_plus - n_moins + 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. """ - eff = self.volume() - return eff >= self.slot.capacity + return self.get_volume() >= self.slot.capacity def is_edited(self): """ @@ -369,7 +379,7 @@ class Colle(models.Model): # func() def __str__(self): - return f"{self.slot} le {self.date} avec groupes {'+'.join(str(groupe) for groupe in self.groups.all())}" + return f"Colle {self.slot.subject} ({self.slot.colleur}); {self.date} {self.slot.time} {self.slot.room}. Groupe(s) {{{'; '.join(str(groupe) for groupe in self.groups.all())}}}" def datetime(self): return datetime.combine(self.date, self.slot.time, tzinfo=timezone("Europe/Paris")) @@ -425,3 +435,13 @@ class CalendarLink(models.Model): 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}") \ No newline at end of file diff --git a/colloscope/views.py b/colloscope/views.py index f74ad3f..e12aadb 100644 --- a/colloscope/views.py +++ b/colloscope/views.py @@ -131,7 +131,7 @@ def marketplace(request): return HttpResponse("pas encore supporté") term = student.cls.current_term() - colles = term.query_colles_not_full() + colles = term.query_colles_not_full_excluding_student(student) context = { "colles": colles, diff --git a/kholles_web/settings.py b/kholles_web/settings.py index 9bc1f90..33603d5 100644 --- a/kholles_web/settings.py +++ b/kholles_web/settings.py @@ -80,7 +80,7 @@ TEMPLATES = [ ] ASGI_APPLICATION = "kholles_web.asgi.application" -#WSGI_APPLICATION = 'kholles_web.wsgi.application' +WSGI_APPLICATION = 'kholles_web.wsgi.application' # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases