[petitscours] Extrait la proposition de profs dans une méthode
Ce patch simplifie le code (dupliqué) de calcul des proposition de profs pour une demande dans une méthode du modèle`Demande`, et l'utilise. Il s'agit d'un préparatif pour #208; ce code devra être réutilisé dans le nouveau système. J'en ai également profité pour nettoyer deux vues de `petitscours`, `retraitement` et `demande_raw`, qui dupliquaient les vues `traitement` et `demande`, en utilisant des arguments nommés. petitscours/ * models.py: Définition de `get_proposals` pour calculer les propositions de profs pour une demande. * views.py: Utilise `get_proposals` à la place du code copié-collé. La fonction `_finalize_traitement` est maintenant responsable du calcul des `proposed_for` et `attribdata` à fournir aux templates. * urls.py: Passe directement les arguments aux vues plutôt que de faire deux fonctions séparées.
This commit is contained in:
parent
c960d97b67
commit
2b8f81c94b
3 changed files with 78 additions and 129 deletions
|
@ -3,6 +3,7 @@ from functools import reduce
|
|||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.db.models import Min
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
|
@ -55,6 +56,12 @@ class PetitCoursAbility(models.Model):
|
|||
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
|
||||
|
||||
|
||||
class PetitCoursDemande(models.Model):
|
||||
name = models.CharField(_("Nom/prénom"), max_length=200)
|
||||
|
@ -128,6 +135,42 @@ class PetitCoursDemande(models.Model):
|
|||
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
|
||||
|
||||
class Meta:
|
||||
app_label = "gestioncof"
|
||||
verbose_name = "Demande de petits cours"
|
||||
|
|
|
@ -7,7 +7,12 @@ from gestioncof.decorators import buro_required
|
|||
urlpatterns = [
|
||||
url(r"^inscription$", views.inscription, name="petits-cours-inscription"),
|
||||
url(r"^demande$", views.demande, name="petits-cours-demande"),
|
||||
url(r"^demande-raw$", views.demande_raw, name="petits-cours-demande-raw"),
|
||||
url(
|
||||
r"^demande-raw$",
|
||||
views.demande,
|
||||
kwargs={"raw": True},
|
||||
name="petits-cours-demande-raw",
|
||||
),
|
||||
url(
|
||||
r"^demandes$",
|
||||
buro_required(DemandeListView.as_view()),
|
||||
|
@ -25,7 +30,8 @@ urlpatterns = [
|
|||
),
|
||||
url(
|
||||
r"^demandes/(?P<demande_id>\d+)/retraitement$",
|
||||
views.retraitement,
|
||||
views.traitement,
|
||||
kwargs={"redo": True},
|
||||
name="petits-cours-demande-retraitement",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -53,64 +53,27 @@ def traitement(request, demande_id, redo=False):
|
|||
return _traitement_other(request, demande, redo)
|
||||
if request.method == "POST":
|
||||
return _traitement_post(request, demande)
|
||||
proposals = {}
|
||||
proposed_for = {}
|
||||
unsatisfied = []
|
||||
attribdata = {}
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
tuples = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
tuples.append(
|
||||
(
|
||||
candidate,
|
||||
PetitCoursAttributionCounter.get_uptodate(user, matiere),
|
||||
)
|
||||
)
|
||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||
candidates, _ = zip(*tuples)
|
||||
candidates = candidates[0 : min(3, len(candidates))]
|
||||
attribdata[matiere.id] = []
|
||||
proposals[matiere] = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
proposals[matiere].append(user)
|
||||
attribdata[matiere.id].append(user.id)
|
||||
if user not in proposed_for:
|
||||
proposed_for[user] = [matiere]
|
||||
else:
|
||||
proposed_for[user].append(matiere)
|
||||
else:
|
||||
unsatisfied.append(matiere)
|
||||
return _finalize_traitement(
|
||||
request, demande, proposals, proposed_for, unsatisfied, attribdata, redo
|
||||
)
|
||||
|
||||
|
||||
@buro_required
|
||||
def retraitement(request, demande_id):
|
||||
return traitement(request, demande_id, redo=True)
|
||||
proposals, unsatisfied = demande.get_proposals(redo=redo, max_candidates=3)
|
||||
return _finalize_traitement(request, demande, proposals, unsatisfied, redo)
|
||||
|
||||
|
||||
def _finalize_traitement(
|
||||
request,
|
||||
demande,
|
||||
proposals,
|
||||
proposed_for,
|
||||
unsatisfied,
|
||||
attribdata,
|
||||
redo=False,
|
||||
errors=None,
|
||||
request, demande, proposals, unsatisfied, redo=False, errors=None
|
||||
):
|
||||
proposals = proposals.items()
|
||||
proposed_for = proposed_for.items()
|
||||
attribdata = list(attribdata.items())
|
||||
attribdata = [
|
||||
(matiere.id, [user.id for user in users])
|
||||
for matiere, users in proposals.items()
|
||||
]
|
||||
proposed_for = {}
|
||||
for matiere, users in proposals.items():
|
||||
for user in users:
|
||||
proposed_for.setdefault(user, []).append(matiere)
|
||||
|
||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||
mainmail = render_custom_mail(
|
||||
"petits-cours-mail-demandeur",
|
||||
{
|
||||
"proposals": proposals,
|
||||
"proposals": proposals.items(),
|
||||
"unsatisfied": unsatisfied,
|
||||
"extra": '<textarea name="extra" '
|
||||
'style="width:99%; height: 90px;">'
|
||||
|
@ -126,8 +89,8 @@ def _finalize_traitement(
|
|||
{
|
||||
"demande": demande,
|
||||
"unsatisfied": unsatisfied,
|
||||
"proposals": proposals,
|
||||
"proposed_for": proposed_for,
|
||||
"proposals": proposals.items(),
|
||||
"proposed_for": proposed_for.items(),
|
||||
"proposed_mails": proposed_mails,
|
||||
"mainmail": mainmail,
|
||||
"attribdata": json.dumps(attribdata),
|
||||
|
@ -144,7 +107,7 @@ def _generate_eleve_email(demande, proposed_for):
|
|||
"petit-cours-mail-eleve", {"demande": demande, "matieres": matieres}
|
||||
),
|
||||
)
|
||||
for user, matieres in proposed_for
|
||||
for user, matieres in proposed_for.items()
|
||||
]
|
||||
|
||||
|
||||
|
@ -152,15 +115,12 @@ def _traitement_other_preparing(request, demande):
|
|||
redo = "redo" in request.POST
|
||||
unsatisfied = []
|
||||
proposals = {}
|
||||
proposed_for = {}
|
||||
attribdata = {}
|
||||
errors = []
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
candidates = dict(
|
||||
[(candidate.user.id, candidate.user) for candidate in candidates]
|
||||
)
|
||||
attribdata[matiere.id] = []
|
||||
proposals[matiere] = []
|
||||
for choice_id in range(min(3, len(candidates))):
|
||||
choice = int(
|
||||
|
@ -183,11 +143,6 @@ def _traitement_other_preparing(request, demande):
|
|||
)
|
||||
continue
|
||||
proposals[matiere].append(user)
|
||||
attribdata[matiere.id].append(user.id)
|
||||
if user not in proposed_for:
|
||||
proposed_for[user] = [matiere]
|
||||
else:
|
||||
proposed_for[user].append(matiere)
|
||||
if not proposals[matiere]:
|
||||
errors.append("Aucune proposition pour {!s}".format(matiere))
|
||||
elif len(proposals[matiere]) < 3:
|
||||
|
@ -200,15 +155,7 @@ def _traitement_other_preparing(request, demande):
|
|||
)
|
||||
else:
|
||||
unsatisfied.append(matiere)
|
||||
return _finalize_traitement(
|
||||
request,
|
||||
demande,
|
||||
proposals,
|
||||
proposed_for,
|
||||
unsatisfied,
|
||||
attribdata,
|
||||
errors=errors,
|
||||
)
|
||||
return _finalize_traitement(request, demande, proposals, unsatisfied, errors=errors)
|
||||
|
||||
|
||||
def _traitement_other(request, demande, redo):
|
||||
|
@ -217,45 +164,14 @@ def _traitement_other(request, demande, redo):
|
|||
return _traitement_other_preparing(request, demande)
|
||||
else:
|
||||
return _traitement_post(request, demande)
|
||||
proposals = {}
|
||||
proposed_for = {}
|
||||
unsatisfied = []
|
||||
attribdata = {}
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
tuples = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
tuples.append(
|
||||
(
|
||||
candidate,
|
||||
PetitCoursAttributionCounter.get_uptodate(user, matiere),
|
||||
)
|
||||
)
|
||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||
candidates, _ = zip(*tuples)
|
||||
attribdata[matiere.id] = []
|
||||
proposals[matiere] = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
proposals[matiere].append(user)
|
||||
attribdata[matiere.id].append(user.id)
|
||||
if user not in proposed_for:
|
||||
proposed_for[user] = [matiere]
|
||||
else:
|
||||
proposed_for[user].append(matiere)
|
||||
else:
|
||||
unsatisfied.append(matiere)
|
||||
proposals = proposals.items()
|
||||
proposed_for = proposed_for.items()
|
||||
proposals, unsatisfied = demande.get_proposals(redo=redo)
|
||||
return render(
|
||||
request,
|
||||
"petitscours/traitement_demande_autre_niveau.html",
|
||||
{
|
||||
"demande": demande,
|
||||
"unsatisfied": unsatisfied,
|
||||
"proposals": proposals,
|
||||
"proposed_for": proposed_for,
|
||||
"proposals": proposals.items(),
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -280,12 +196,10 @@ def _traitement_post(request, demande):
|
|||
proposed_for[user] = [matiere]
|
||||
else:
|
||||
proposed_for[user].append(matiere)
|
||||
proposals_list = proposals.items()
|
||||
proposed_for = proposed_for.items()
|
||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||
mainmail_object, mainmail_body = render_custom_mail(
|
||||
"petits-cours-mail-demandeur",
|
||||
{"proposals": proposals_list, "unsatisfied": unsatisfied, "extra": extra},
|
||||
{"proposals": proposals.items(), "unsatisfied": unsatisfied, "extra": extra},
|
||||
)
|
||||
frommail = settings.MAIL_DATA["petits_cours"]["FROM"]
|
||||
bccaddress = settings.MAIL_DATA["petits_cours"]["BCC"]
|
||||
|
@ -314,8 +228,8 @@ def _traitement_post(request, demande):
|
|||
connection = mail.get_connection(fail_silently=False)
|
||||
connection.send_messages(mails_to_send)
|
||||
with transaction.atomic():
|
||||
for matiere in proposals:
|
||||
for rank, user in enumerate(proposals[matiere]):
|
||||
for matiere, users in proposals.items():
|
||||
for rank, user in enumerate(users):
|
||||
# TODO(AD): Prefer PetitCoursAttributionCounter.get_uptodate()
|
||||
counter = PetitCoursAttributionCounter.objects.get(
|
||||
user=user, matiere=matiere
|
||||
|
@ -373,7 +287,7 @@ def inscription(request):
|
|||
|
||||
|
||||
@csrf_exempt
|
||||
def demande(request):
|
||||
def demande(request, *, raw: bool = False):
|
||||
success = False
|
||||
if request.method == "POST":
|
||||
form = DemandeForm(request.POST)
|
||||
|
@ -382,21 +296,7 @@ def demande(request):
|
|||
success = True
|
||||
else:
|
||||
form = DemandeForm()
|
||||
return render(
|
||||
request, "petitscours/demande.html", {"form": form, "success": success}
|
||||
)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def demande_raw(request):
|
||||
success = False
|
||||
if request.method == "POST":
|
||||
form = DemandeForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
success = True
|
||||
else:
|
||||
form = DemandeForm()
|
||||
return render(
|
||||
request, "petitscours/demande_raw.html", {"form": form, "success": success}
|
||||
)
|
||||
template_name = "petitscours/demande.html"
|
||||
if raw:
|
||||
template_name = "petitscours/demande_raw.html"
|
||||
return render(request, template_name, {"form": form, "success": success})
|
||||
|
|
Loading…
Reference in a new issue