gestioCOF/petitscours/models.py

246 lines
8.9 KiB
Python
Raw Normal View History

from django.contrib.auth.models import User
2013-10-06 11:20:59 +02:00
from django.db import models
from django.db.models import Min
from django.utils.functional import cached_property
2022-06-29 16:09:50 +02:00
from django.utils.translation import gettext_lazy as _
2013-10-06 11:20:59 +02:00
2019-06-26 18:57:16 +02:00
from shared.utils import choices_length
2013-10-06 11:20:59 +02:00
LEVELS_CHOICES = (
("college", _("Collège")),
("lycee", _("Lycée")),
("prepa1styear", _("Prépa 1ère année / L1")),
("prepa2ndyear", _("Prépa 2ème année / L2")),
("licence3", _("Licence 3")),
("master1", _("Master (1ère ou 2ème année)")),
("other", _("Autre (préciser dans les commentaires)")),
2013-10-06 11:20:59 +02:00
)
2013-10-06 11:20:59 +02:00
class PetitCoursSubject(models.Model):
name = models.CharField(_("Matière"), max_length=30)
users = models.ManyToManyField(
User, related_name="petits_cours_matieres", through="PetitCoursAbility"
)
2013-10-06 11:20:59 +02:00
class Meta:
Extrait les petits cours dans une application séparée L'application `petitscours` reste assez fortement couplée à `gestioncof`, et n'est pas (encore ?) faite pour être utilisée séparément. De façon similaire, et afin de minimiser de potentiels problèmes dûs à des migrations, les modèles de l'application `petitscours` utilisent `app_label = "gestioncof"` pour que Django les considère comme faisant partie de l'application `"gestioncof"`. Ils pourront être migrés dans un second temps si cela s'avère nécessaire. Les changements sont nombreux, mais assez simples: il s'agit principalement de déplacer des fichiers et changer des imports. J'ai également profité de l'occasion pour réorganiser les templates afin de les placer dans l'espace de nom "petitscours/". cof/ * settings/common.py: Add `petitscours` app * urls.py: Use `petitscours.urls` petitscours/ * __init__.py: Added. * tests/__init__.py: Added. * tests/utils.py: Added. * urls.py: Added. gestioncof/ * admin.py: * management/commands/loaddevdata.py: * models.py: * signals.py: Typo. * urls.py: Moved petitscours_patterns to petitscours.urls * petits_cours_forms.py: Moved to petitscours/forms.py * petits_cours_models.py: Moved to petitscours/models.py * petits_cours_views.py: Moved to petitscours/views.py * tests/utils.py: * tests/test_petitscours_views.py: Moved to petitscours/tests/test_petitscours_views.py * templates/base_title_petitscours.html: Moved to petitscours/templates/petitscours/base_title.html * templates/demande-petit-cours.html: Moved topetitscours/templates/petitscours/demande.html * templates/gestioncof/details_demande_petit_cours.html: Moved to petitscours/templates/petitscours/demande_detail.html * templates/petits_cours_demandes_list.html: Moved to petitscours/templates/petitscours/demande_list.html * templates/demande-petit-cours-raw.html: Moved to petitscours/templates/petitscours/demande_raw.html * templates/details_demande_petit_cours_infos.html: Moved to petitscours/templates/petitscours/details_demande_infos.html * templates/inscription-petit-cours.html: Moved to petitscours/templates/petitscours/inscription.html * templates/inscription-petit-cours-formset.html: Moved to petitscours/templates/petitscours/inscription_formset.html * templates/gestioncof/traitement_demande_petit_cours.html: Moved to petitscours/templates/petitscours/traitement_demande.html * templates/gestioncof/traitement_demande_petit_cours_autre_niveau.html: Moved to petitscours/templates/petitscours/traitement_demande_autre_niveau.html * templates/gestioncof/traitement_demande_petit_cours_success.html: Moved to petitscours/templates/petitscours/traitement_demande_success.html
2018-11-25 00:37:22 +01:00
app_label = "gestioncof"
2013-10-06 11:20:59 +02:00
verbose_name = "Matière de petits cours"
verbose_name_plural = "Matières des petits cours"
def __str__(self):
2013-10-06 11:20:59 +02:00
return self.name
2013-10-06 11:20:59 +02:00
class PetitCoursAbility(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
matiere = models.ForeignKey(
PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matière")
)
niveau = models.CharField(
_("Niveau"), choices=LEVELS_CHOICES, max_length=choices_length(LEVELS_CHOICES)
)
2016-07-15 00:02:56 +02:00
agrege = models.BooleanField(_("Agrégé"), default=False)
2013-10-06 11:20:59 +02:00
class Meta:
Extrait les petits cours dans une application séparée L'application `petitscours` reste assez fortement couplée à `gestioncof`, et n'est pas (encore ?) faite pour être utilisée séparément. De façon similaire, et afin de minimiser de potentiels problèmes dûs à des migrations, les modèles de l'application `petitscours` utilisent `app_label = "gestioncof"` pour que Django les considère comme faisant partie de l'application `"gestioncof"`. Ils pourront être migrés dans un second temps si cela s'avère nécessaire. Les changements sont nombreux, mais assez simples: il s'agit principalement de déplacer des fichiers et changer des imports. J'ai également profité de l'occasion pour réorganiser les templates afin de les placer dans l'espace de nom "petitscours/". cof/ * settings/common.py: Add `petitscours` app * urls.py: Use `petitscours.urls` petitscours/ * __init__.py: Added. * tests/__init__.py: Added. * tests/utils.py: Added. * urls.py: Added. gestioncof/ * admin.py: * management/commands/loaddevdata.py: * models.py: * signals.py: Typo. * urls.py: Moved petitscours_patterns to petitscours.urls * petits_cours_forms.py: Moved to petitscours/forms.py * petits_cours_models.py: Moved to petitscours/models.py * petits_cours_views.py: Moved to petitscours/views.py * tests/utils.py: * tests/test_petitscours_views.py: Moved to petitscours/tests/test_petitscours_views.py * templates/base_title_petitscours.html: Moved to petitscours/templates/petitscours/base_title.html * templates/demande-petit-cours.html: Moved topetitscours/templates/petitscours/demande.html * templates/gestioncof/details_demande_petit_cours.html: Moved to petitscours/templates/petitscours/demande_detail.html * templates/petits_cours_demandes_list.html: Moved to petitscours/templates/petitscours/demande_list.html * templates/demande-petit-cours-raw.html: Moved to petitscours/templates/petitscours/demande_raw.html * templates/details_demande_petit_cours_infos.html: Moved to petitscours/templates/petitscours/details_demande_infos.html * templates/inscription-petit-cours.html: Moved to petitscours/templates/petitscours/inscription.html * templates/inscription-petit-cours-formset.html: Moved to petitscours/templates/petitscours/inscription_formset.html * templates/gestioncof/traitement_demande_petit_cours.html: Moved to petitscours/templates/petitscours/traitement_demande.html * templates/gestioncof/traitement_demande_petit_cours_autre_niveau.html: Moved to petitscours/templates/petitscours/traitement_demande_autre_niveau.html * templates/gestioncof/traitement_demande_petit_cours_success.html: Moved to petitscours/templates/petitscours/traitement_demande_success.html
2018-11-25 00:37:22 +01:00
app_label = "gestioncof"
constraints = [
models.UniqueConstraint(
fields=["user", "niveau", "matiere"], name="unique_competence_level"
)
]
2013-10-06 11:20:59 +02:00
verbose_name = "Compétence petits cours"
verbose_name_plural = "Compétences des petits cours"
def __str__(self):
return "{:s} - {!s} - {:s}".format(
self.user.username, self.matiere, self.niveau
)
@cached_property
def counter(self) -> int:
"""Le compteur d'attribution associé au professeur pour cette matière."""
return PetitCoursAttributionCounter.get_uptodate(self.user, self.matiere).count
2013-10-06 11:20:59 +02:00
class PetitCoursDemande(models.Model):
2016-07-15 00:02:56 +02:00
name = models.CharField(_("Nom/prénom"), max_length=200)
2020-10-26 13:34:30 +01:00
email = models.EmailField(_("Adresse email"), max_length=300)
phone = models.CharField(_("Téléphone (facultatif)"), max_length=20, blank=True)
2016-06-17 13:26:55 +02:00
quand = models.CharField(
2016-07-15 00:02:56 +02:00
_("Quand ?"),
help_text=_(
"Indiquez ici la période désirée pour les petits"
" cours (vacances scolaires, semaine, week-end)."
),
max_length=300,
blank=True,
)
2016-06-17 13:26:55 +02:00
freq = models.CharField(
2016-07-15 00:02:56 +02:00
_("Fréquence"),
help_text=_(
"Indiquez ici la fréquence envisagée "
"(hebdomadaire, 2 fois par semaine, ...)"
),
max_length=300,
blank=True,
)
2016-06-17 13:26:55 +02:00
lieu = models.CharField(
2016-07-15 00:02:56 +02:00
_("Lieu (si préférence)"),
help_text=_("Si vous avez avez une préférence sur le lieu."),
max_length=300,
blank=True,
)
2016-06-17 13:26:55 +02:00
matieres = models.ManyToManyField(
PetitCoursSubject, verbose_name=_("Matières"), related_name="demandes"
)
2016-07-15 00:02:56 +02:00
agrege_requis = models.BooleanField(_("Agrégé requis"), default=False)
niveau = models.CharField(
_("Niveau"),
default="",
choices=LEVELS_CHOICES,
max_length=choices_length(LEVELS_CHOICES),
)
2013-10-06 11:20:59 +02:00
2016-07-15 00:02:56 +02:00
remarques = models.TextField(_("Remarques et précisions"), blank=True)
2013-10-06 11:20:59 +02:00
2016-07-15 00:02:56 +02:00
traitee = models.BooleanField(_("Traitée"), default=False)
traitee_par = models.ForeignKey(
User, on_delete=models.CASCADE, blank=True, null=True
)
processed = models.DateTimeField(_("Date de traitement"), blank=True, null=True)
2016-07-15 00:02:56 +02:00
created = models.DateTimeField(_("Date de création"), auto_now_add=True)
2013-10-06 11:20:59 +02:00
def get_candidates(self, redo=False):
"""
Donne la liste des profs disponibles pour chaque matière de la demande.
- On ne donne que les agrégés si c'est demandé
- Si ``redo`` vaut ``True``, cela signifie qu'on retraite la demande et
il ne faut pas proposer à nouveau des noms qui ont déjà été proposés
"""
for matiere in self.matieres.all():
candidates = PetitCoursAbility.objects.filter(
matiere=matiere,
niveau=self.niveau,
user__profile__is_cof=True,
user__profile__petits_cours_accept=True,
)
if self.agrege_requis:
candidates = candidates.filter(agrege=True)
if redo:
attrs = self.petitcoursattribution_set.filter(matiere=matiere)
already_proposed = [attr.user for attr in attrs]
candidates = candidates.exclude(user__in=already_proposed)
candidates = candidates.order_by("?").select_related().all()
yield (matiere, candidates)
def get_proposals(self, *, max_candidates: int = None, redo: bool = False):
"""Calcule une proposition de profs pour la demande.
Args:
max_candidates (optionnel; défaut: `None`): Le nombre maximum de
candidats à proposer par demande. Si `None` ou non spécifié,
il n'y a pas de limite.
redo (optionel; défaut: `False`): Détermine si on re-calcule les
propositions pour la demande (les professeurs à qui on a déjà
proposé cette demande sont exclus).
Returns:
proposals: Le dictionnaire qui associe à chaque matière la liste
des professeurs proposés. Les matières pour lesquelles aucun
professeur n'est disponible ne sont pas présentes dans
`proposals`.
unsatisfied: La liste des matières pour lesquelles aucun
professeur n'est disponible.
"""
proposals = {}
unsatisfied = []
for matiere, candidates in self.get_candidates(redo=redo):
if not candidates:
unsatisfied.append(matiere)
else:
proposals[matiere] = matiere_proposals = []
candidates = sorted(candidates, key=lambda c: c.counter)
candidates = candidates[:max_candidates]
for candidate in candidates[:max_candidates]:
matiere_proposals.append(candidate.user)
return proposals, unsatisfied
def get_absolute_url(self):
from django.urls import reverse
return reverse("petits-cours-demande-details", kwargs={"pk": str(self.id)})
2013-10-06 11:20:59 +02:00
class Meta:
Extrait les petits cours dans une application séparée L'application `petitscours` reste assez fortement couplée à `gestioncof`, et n'est pas (encore ?) faite pour être utilisée séparément. De façon similaire, et afin de minimiser de potentiels problèmes dûs à des migrations, les modèles de l'application `petitscours` utilisent `app_label = "gestioncof"` pour que Django les considère comme faisant partie de l'application `"gestioncof"`. Ils pourront être migrés dans un second temps si cela s'avère nécessaire. Les changements sont nombreux, mais assez simples: il s'agit principalement de déplacer des fichiers et changer des imports. J'ai également profité de l'occasion pour réorganiser les templates afin de les placer dans l'espace de nom "petitscours/". cof/ * settings/common.py: Add `petitscours` app * urls.py: Use `petitscours.urls` petitscours/ * __init__.py: Added. * tests/__init__.py: Added. * tests/utils.py: Added. * urls.py: Added. gestioncof/ * admin.py: * management/commands/loaddevdata.py: * models.py: * signals.py: Typo. * urls.py: Moved petitscours_patterns to petitscours.urls * petits_cours_forms.py: Moved to petitscours/forms.py * petits_cours_models.py: Moved to petitscours/models.py * petits_cours_views.py: Moved to petitscours/views.py * tests/utils.py: * tests/test_petitscours_views.py: Moved to petitscours/tests/test_petitscours_views.py * templates/base_title_petitscours.html: Moved to petitscours/templates/petitscours/base_title.html * templates/demande-petit-cours.html: Moved topetitscours/templates/petitscours/demande.html * templates/gestioncof/details_demande_petit_cours.html: Moved to petitscours/templates/petitscours/demande_detail.html * templates/petits_cours_demandes_list.html: Moved to petitscours/templates/petitscours/demande_list.html * templates/demande-petit-cours-raw.html: Moved to petitscours/templates/petitscours/demande_raw.html * templates/details_demande_petit_cours_infos.html: Moved to petitscours/templates/petitscours/details_demande_infos.html * templates/inscription-petit-cours.html: Moved to petitscours/templates/petitscours/inscription.html * templates/inscription-petit-cours-formset.html: Moved to petitscours/templates/petitscours/inscription_formset.html * templates/gestioncof/traitement_demande_petit_cours.html: Moved to petitscours/templates/petitscours/traitement_demande.html * templates/gestioncof/traitement_demande_petit_cours_autre_niveau.html: Moved to petitscours/templates/petitscours/traitement_demande_autre_niveau.html * templates/gestioncof/traitement_demande_petit_cours_success.html: Moved to petitscours/templates/petitscours/traitement_demande_success.html
2018-11-25 00:37:22 +01:00
app_label = "gestioncof"
2013-10-06 11:20:59 +02:00
verbose_name = "Demande de petits cours"
verbose_name_plural = "Demandes de petits cours"
def __str__(self):
return "Demande {:d} du {:s}".format(self.id, self.created.strftime("%d %b %Y"))
2013-10-06 11:20:59 +02:00
class PetitCoursAttribution(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
demande = models.ForeignKey(
PetitCoursDemande, on_delete=models.CASCADE, verbose_name=_("Demande")
)
matiere = models.ForeignKey(
PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matière")
)
2016-07-15 00:02:56 +02:00
date = models.DateTimeField(_("Date d'attribution"), auto_now_add=True)
2013-10-06 11:20:59 +02:00
rank = models.IntegerField("Rang dans l'email")
selected = models.BooleanField(_("Sélectionné par le demandeur"), default=False)
2013-10-06 11:20:59 +02:00
class Meta:
Extrait les petits cours dans une application séparée L'application `petitscours` reste assez fortement couplée à `gestioncof`, et n'est pas (encore ?) faite pour être utilisée séparément. De façon similaire, et afin de minimiser de potentiels problèmes dûs à des migrations, les modèles de l'application `petitscours` utilisent `app_label = "gestioncof"` pour que Django les considère comme faisant partie de l'application `"gestioncof"`. Ils pourront être migrés dans un second temps si cela s'avère nécessaire. Les changements sont nombreux, mais assez simples: il s'agit principalement de déplacer des fichiers et changer des imports. J'ai également profité de l'occasion pour réorganiser les templates afin de les placer dans l'espace de nom "petitscours/". cof/ * settings/common.py: Add `petitscours` app * urls.py: Use `petitscours.urls` petitscours/ * __init__.py: Added. * tests/__init__.py: Added. * tests/utils.py: Added. * urls.py: Added. gestioncof/ * admin.py: * management/commands/loaddevdata.py: * models.py: * signals.py: Typo. * urls.py: Moved petitscours_patterns to petitscours.urls * petits_cours_forms.py: Moved to petitscours/forms.py * petits_cours_models.py: Moved to petitscours/models.py * petits_cours_views.py: Moved to petitscours/views.py * tests/utils.py: * tests/test_petitscours_views.py: Moved to petitscours/tests/test_petitscours_views.py * templates/base_title_petitscours.html: Moved to petitscours/templates/petitscours/base_title.html * templates/demande-petit-cours.html: Moved topetitscours/templates/petitscours/demande.html * templates/gestioncof/details_demande_petit_cours.html: Moved to petitscours/templates/petitscours/demande_detail.html * templates/petits_cours_demandes_list.html: Moved to petitscours/templates/petitscours/demande_list.html * templates/demande-petit-cours-raw.html: Moved to petitscours/templates/petitscours/demande_raw.html * templates/details_demande_petit_cours_infos.html: Moved to petitscours/templates/petitscours/details_demande_infos.html * templates/inscription-petit-cours.html: Moved to petitscours/templates/petitscours/inscription.html * templates/inscription-petit-cours-formset.html: Moved to petitscours/templates/petitscours/inscription_formset.html * templates/gestioncof/traitement_demande_petit_cours.html: Moved to petitscours/templates/petitscours/traitement_demande.html * templates/gestioncof/traitement_demande_petit_cours_autre_niveau.html: Moved to petitscours/templates/petitscours/traitement_demande_autre_niveau.html * templates/gestioncof/traitement_demande_petit_cours_success.html: Moved to petitscours/templates/petitscours/traitement_demande_success.html
2018-11-25 00:37:22 +01:00
app_label = "gestioncof"
2013-10-06 11:20:59 +02:00
verbose_name = "Attribution de petits cours"
verbose_name_plural = "Attributions de petits cours"
def __str__(self):
return "Attribution de la demande {:d} à {:s} pour {!s}".format(
self.demande.id, self.user.username, self.matiere
)
2013-10-06 11:20:59 +02:00
class PetitCoursAttributionCounter(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
matiere = models.ForeignKey(
PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matiere")
)
2016-06-17 13:26:55 +02:00
count = models.IntegerField("Nombre d'envois", default=0)
2013-10-06 11:20:59 +02:00
@classmethod
def get_uptodate(cls, user, matiere):
"""
Donne le compteur de l'utilisateur pour cette matière. Si le compteur
n'existe pas encore, il est initialisé avec le minimum des valeurs des
compteurs de tout le monde.
"""
counter, created = cls.objects.get_or_create(user=user, matiere=matiere)
if created:
mincount = (
cls.objects.filter(matiere=matiere)
.exclude(user=user)
.aggregate(Min("count"))["count__min"]
)
counter.count = mincount or 0
counter.save()
return counter
2013-10-06 11:20:59 +02:00
class Meta:
Extrait les petits cours dans une application séparée L'application `petitscours` reste assez fortement couplée à `gestioncof`, et n'est pas (encore ?) faite pour être utilisée séparément. De façon similaire, et afin de minimiser de potentiels problèmes dûs à des migrations, les modèles de l'application `petitscours` utilisent `app_label = "gestioncof"` pour que Django les considère comme faisant partie de l'application `"gestioncof"`. Ils pourront être migrés dans un second temps si cela s'avère nécessaire. Les changements sont nombreux, mais assez simples: il s'agit principalement de déplacer des fichiers et changer des imports. J'ai également profité de l'occasion pour réorganiser les templates afin de les placer dans l'espace de nom "petitscours/". cof/ * settings/common.py: Add `petitscours` app * urls.py: Use `petitscours.urls` petitscours/ * __init__.py: Added. * tests/__init__.py: Added. * tests/utils.py: Added. * urls.py: Added. gestioncof/ * admin.py: * management/commands/loaddevdata.py: * models.py: * signals.py: Typo. * urls.py: Moved petitscours_patterns to petitscours.urls * petits_cours_forms.py: Moved to petitscours/forms.py * petits_cours_models.py: Moved to petitscours/models.py * petits_cours_views.py: Moved to petitscours/views.py * tests/utils.py: * tests/test_petitscours_views.py: Moved to petitscours/tests/test_petitscours_views.py * templates/base_title_petitscours.html: Moved to petitscours/templates/petitscours/base_title.html * templates/demande-petit-cours.html: Moved topetitscours/templates/petitscours/demande.html * templates/gestioncof/details_demande_petit_cours.html: Moved to petitscours/templates/petitscours/demande_detail.html * templates/petits_cours_demandes_list.html: Moved to petitscours/templates/petitscours/demande_list.html * templates/demande-petit-cours-raw.html: Moved to petitscours/templates/petitscours/demande_raw.html * templates/details_demande_petit_cours_infos.html: Moved to petitscours/templates/petitscours/details_demande_infos.html * templates/inscription-petit-cours.html: Moved to petitscours/templates/petitscours/inscription.html * templates/inscription-petit-cours-formset.html: Moved to petitscours/templates/petitscours/inscription_formset.html * templates/gestioncof/traitement_demande_petit_cours.html: Moved to petitscours/templates/petitscours/traitement_demande.html * templates/gestioncof/traitement_demande_petit_cours_autre_niveau.html: Moved to petitscours/templates/petitscours/traitement_demande_autre_niveau.html * templates/gestioncof/traitement_demande_petit_cours_success.html: Moved to petitscours/templates/petitscours/traitement_demande_success.html
2018-11-25 00:37:22 +01:00
app_label = "gestioncof"
2013-10-06 11:20:59 +02:00
verbose_name = "Compteur d'attribution de petits cours"
verbose_name_plural = "Compteurs d'attributions de petits cours"
2016-07-15 00:02:56 +02:00
def __str__(self):
return "{:d} demandes envoyées à {:s} pour {!s}".format(
self.count, self.user.username, self.matiere
)