from datetime import date, datetime, timedelta from django.db import models from django.db.models import F, Q from django.contrib.auth.models import User 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é ] } class Lycee(models.Model): uai = models.CharField(max_length=10) libelle = models.CharField(max_length=100) vacances = models.CharField(max_length=1) def __self__(self): return self.uai class Classe(models.Model): lycee = models.ForeignKey(Lycee, on_delete=models.CASCADE) libelle = models.CharField(max_length=20) annee = models.IntegerField() jour_zero = models.DateField() def no_semaine(self, jour): """ Entrées : - self - jour (datetime.date) Sortie : - Le numéro de la semaine contenant jour, sans compter les vacances. Renvoie un numéro non spécifiée si le jour est pendant une période de vacances """ zone = self.lycee.vacances vacances = calendrier[zone] jour0 = self.jour_zero n = 1 + ((jour - jour0).days)//7 for debut, fin in vacances: if jour > debut: n -= round( ( fin - debut )/timedelta(weeks=1) ) return n def no_aujourdhui(self): """ Entrée: - self Sortie: - Le numéro de la semaine courante """ return self.no_semaine(date.today()) def date_debut_sem(self, n): """ Entrée: - self - n (int) <-> numéro de semaine 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) for debut, fin in vacances: if jour >= debut: jour += round( (fin - debut)/timedelta(weeks=1) )*timedelta(weeks=1) return jour def periode(self, jour): """ Entrées : - self - jour (datetime.date) Sortie : - La période (si elle existe et est unique) contenant jour Exceptions: - Le jour n'est pas dans une période - Le jour est au chevauchement de deux périodes """ return Periode.objects.get(classe=self, debut__lte=jour, fin__gte=jour) def __str__(self): return f"{self.libelle} ({self.lycee.libelle})" class Periode(models.Model): classe = models.ForeignKey(Classe, on_delete=models.CASCADE) #critere_colle = models.ForeignKey(Critere, on_delete=models.SET_NULL, null=True) libelle = models.CharField(max_length=100) debut = models.DateField() fin = models.DateField() class Meta: ordering = ["debut"] def range_semaines(self): """ Entrée: - self Sortie: - Un range des numéros de semaine """ 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) def __str__(self): return self.libelle class Matiere(models.Model): classe = models.ForeignKey(Classe, on_delete=models.CASCADE) libelle = models.CharField(max_length=100) code = models.CharField(max_length=20) def __str__(self): return self.libelle class Critere(models.Model): periode = models.ForeignKey(Periode, on_delete=models.CASCADE) libelle = models.CharField(max_length=100) def __str__(self): return self.libelle 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) membres = models.ManyToManyField("Etudiant", through="Appartenance") def __str__(self): return self.libelle def get_colles(self): return Rotation.objects.filter(groupes=self).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() ) colles_flat = self.get_colles() return [ (sem, lundi, 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"] classe = models.ForeignKey(Classe, on_delete=models.CASCADE) prenom = models.CharField(max_length=100) nom = models.CharField(max_length=100) groupes = models.ManyToManyField("Groupe", through="Appartenance") def appartient(self, groupe): """ Renvoie si self appartient au groupe. """ return groupe.membres.contains(self) def groupe_du_critere(self, periode, critere): """ Renvoie le groupe du critère auquel self appartient. """ if isinstance(critere, str): critere = Critere.objects.get(periode=periode, libelle=critere) return Appartenance.objects.get(groupe__periode=periode, etudiant=self, groupe__critere=critere).groupe def groupe_de_colle(self, periode): """ Renvoie le groupe de colle de self pendant periode. """ return self.groupe_du_critere(periode, "colle") def __str__(self): return f"{self.prenom} {self.nom}" class Appartenance(models.Model): etudiant = models.ForeignKey(Etudiant, on_delete=models.CASCADE) groupe = models.ForeignKey(Groupe, on_delete=models.CASCADE) class Colleur(models.Model): civilite = models.CharField(max_length=1) nom = models.CharField(max_length=100) def __str__(self): if self.civilite == "M": return f"M. {self.nom}" else: return f"Mme {self.nom}" def get_classes(self): return (x.periode.classe for x in Creneau.objects.filter(colleur=self).select_related("periode__classe")) class Creneau(models.Model): periode = models.ForeignKey(Periode, on_delete=models.CASCADE) jour = models.IntegerField() heure = models.TimeField() duree = models.DurationField() salle = models.CharField(max_length=20) matiere = models.ForeignKey(Matiere, on_delete=models.CASCADE) colleur = models.ForeignKey(Colleur, on_delete=models.CASCADE) est_colle = models.BooleanField() capacite = models.IntegerField() 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): creneau = models.ForeignKey(Creneau, on_delete=models.CASCADE) groupes = models.ManyToManyField(Groupe) date = models.DateField() def groupe_initial(self): """ Renvoie les étudiants inscrits à la colle sans prendre en compte les amendements. """ return Etudiant.objects.filter(id__in=Appartenance.objects.filter(groupe__in=self.groupes.all())) def groupe_effectif(self): """ Renvoie les étudiants inscrits à la colle en tenant compte des amendements. """ 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=amendements.filter(est_positif=False).values("etudiant_id")) ) def effectif(self): """ Renvoie le nombre d'étudiants inscrits à la colle en tenant compte des amendements. """ n_base = sum(len(groupe.membres.count()) for groupe in self.groupes.all()) n_plus = len(Amendement.objects.filter(est_positif=True, rotation=self)) n_moins = len(Amendement.objects.filter(est_positif=False, rotation=self)) return n_base + n_plus - n_moins def est_pleine(self): """ Renvoie si la colle est pleine. """ eff = self.effectif() return eff>=self.creneau.capacite def est_modifiee(self): """ Renvoie si la colle a été amendée. """ return Amendement.objects.filter(rotation=self).exists() def amender(self, etudiant, est_positif): """ Amende la colle en (des)inscrivant etudiant à la colle self, selon est_positif. """ if Amendement.objects.filter(rotation=self, etudiant=etudiant, est_positif=est_positif).exists(): raise Exception("Duplication") elif Amendement.objects.filter(rotation=self, etudiant=etudiant, est_positif=not est_positif).exists(): # les amendements complémentaires s'annulent Amendement.objects.get(rotation=self, etudiant=etudiant, est_positif=not est_positif).delete() elif est_positif and any(etudiant.appartient(groupe) for groupe in self.groupes.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 est_positif and all(not etudiant.appartient(groupe) for groupe in self.groupes.all()): raise Exception("Vous n'êtes pas dans le groupe") elif est_positif and self.est_pleine(): raise Exception("Capacité dépassée") else: amendement = Amendement(rotation=self, etudiant=etudiant, est_positif=est_positif) amendement.save() def __str__(self): return f"{self.creneau} le {self.date} avec groupes {'+'.join(str(groupe) for groupe in self.groupes.all())}" class Amendement(models.Model): est_positif = models.BooleanField() rotation = models.ForeignKey(Rotation, on_delete=models.CASCADE) etudiant = models.ForeignKey(Etudiant, on_delete=models.CASCADE) class Profil(models.Model): utilisateur = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True) etudiant = models.ForeignKey(Etudiant, null=True, default=None, on_delete=models.SET_NULL) colleur = models.ForeignKey(Colleur, null=True, default=None, on_delete=models.SET_NULL) def __str__(self): return f"Profil {self.utilisateur} : {self.etudiant} ; {self.colleur}"