forked from DGNum/gestioCOF
Merge branch 'Elarnon/petitscours_proposals_cleanup' into 'master'
[petitscours] Extrait la proposition de profs dans une méthode See merge request klub-dev-ens/gestioCOF!332
This commit is contained in:
commit
dee2f4badc
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.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Min
|
from django.db.models import Min
|
||||||
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,6 +56,12 @@ class PetitCoursAbility(models.Model):
|
||||||
self.user.username, self.matiere, self.niveau
|
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):
|
class PetitCoursDemande(models.Model):
|
||||||
name = models.CharField(_("Nom/prénom"), max_length=200)
|
name = models.CharField(_("Nom/prénom"), max_length=200)
|
||||||
|
@ -128,6 +135,42 @@ class PetitCoursDemande(models.Model):
|
||||||
candidates = candidates.order_by("?").select_related().all()
|
candidates = candidates.order_by("?").select_related().all()
|
||||||
yield (matiere, candidates)
|
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:
|
class Meta:
|
||||||
app_label = "gestioncof"
|
app_label = "gestioncof"
|
||||||
verbose_name = "Demande de petits cours"
|
verbose_name = "Demande de petits cours"
|
||||||
|
|
|
@ -7,7 +7,12 @@ from gestioncof.decorators import buro_required
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r"^inscription$", views.inscription, name="petits-cours-inscription"),
|
url(r"^inscription$", views.inscription, name="petits-cours-inscription"),
|
||||||
url(r"^demande$", views.demande, name="petits-cours-demande"),
|
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(
|
url(
|
||||||
r"^demandes$",
|
r"^demandes$",
|
||||||
buro_required(DemandeListView.as_view()),
|
buro_required(DemandeListView.as_view()),
|
||||||
|
@ -25,7 +30,8 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
r"^demandes/(?P<demande_id>\d+)/retraitement$",
|
r"^demandes/(?P<demande_id>\d+)/retraitement$",
|
||||||
views.retraitement,
|
views.traitement,
|
||||||
|
kwargs={"redo": True},
|
||||||
name="petits-cours-demande-retraitement",
|
name="petits-cours-demande-retraitement",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -53,64 +53,27 @@ def traitement(request, demande_id, redo=False):
|
||||||
return _traitement_other(request, demande, redo)
|
return _traitement_other(request, demande, redo)
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
return _traitement_post(request, demande)
|
return _traitement_post(request, demande)
|
||||||
proposals = {}
|
proposals, unsatisfied = demande.get_proposals(redo=redo, max_candidates=3)
|
||||||
proposed_for = {}
|
return _finalize_traitement(request, demande, proposals, unsatisfied, redo)
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def _finalize_traitement(
|
def _finalize_traitement(
|
||||||
request,
|
request, demande, proposals, unsatisfied, redo=False, errors=None
|
||||||
demande,
|
|
||||||
proposals,
|
|
||||||
proposed_for,
|
|
||||||
unsatisfied,
|
|
||||||
attribdata,
|
|
||||||
redo=False,
|
|
||||||
errors=None,
|
|
||||||
):
|
):
|
||||||
proposals = proposals.items()
|
attribdata = [
|
||||||
proposed_for = proposed_for.items()
|
(matiere.id, [user.id for user in users])
|
||||||
attribdata = list(attribdata.items())
|
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)
|
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||||
mainmail = render_custom_mail(
|
mainmail = render_custom_mail(
|
||||||
"petits-cours-mail-demandeur",
|
"petits-cours-mail-demandeur",
|
||||||
{
|
{
|
||||||
"proposals": proposals,
|
"proposals": proposals.items(),
|
||||||
"unsatisfied": unsatisfied,
|
"unsatisfied": unsatisfied,
|
||||||
"extra": '<textarea name="extra" '
|
"extra": '<textarea name="extra" '
|
||||||
'style="width:99%; height: 90px;">'
|
'style="width:99%; height: 90px;">'
|
||||||
|
@ -126,8 +89,8 @@ def _finalize_traitement(
|
||||||
{
|
{
|
||||||
"demande": demande,
|
"demande": demande,
|
||||||
"unsatisfied": unsatisfied,
|
"unsatisfied": unsatisfied,
|
||||||
"proposals": proposals,
|
"proposals": proposals.items(),
|
||||||
"proposed_for": proposed_for,
|
"proposed_for": proposed_for.items(),
|
||||||
"proposed_mails": proposed_mails,
|
"proposed_mails": proposed_mails,
|
||||||
"mainmail": mainmail,
|
"mainmail": mainmail,
|
||||||
"attribdata": json.dumps(attribdata),
|
"attribdata": json.dumps(attribdata),
|
||||||
|
@ -144,7 +107,7 @@ def _generate_eleve_email(demande, proposed_for):
|
||||||
"petit-cours-mail-eleve", {"demande": demande, "matieres": matieres}
|
"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
|
redo = "redo" in request.POST
|
||||||
unsatisfied = []
|
unsatisfied = []
|
||||||
proposals = {}
|
proposals = {}
|
||||||
proposed_for = {}
|
|
||||||
attribdata = {}
|
|
||||||
errors = []
|
errors = []
|
||||||
for matiere, candidates in demande.get_candidates(redo):
|
for matiere, candidates in demande.get_candidates(redo):
|
||||||
if candidates:
|
if candidates:
|
||||||
candidates = dict(
|
candidates = dict(
|
||||||
[(candidate.user.id, candidate.user) for candidate in candidates]
|
[(candidate.user.id, candidate.user) for candidate in candidates]
|
||||||
)
|
)
|
||||||
attribdata[matiere.id] = []
|
|
||||||
proposals[matiere] = []
|
proposals[matiere] = []
|
||||||
for choice_id in range(min(3, len(candidates))):
|
for choice_id in range(min(3, len(candidates))):
|
||||||
choice = int(
|
choice = int(
|
||||||
|
@ -183,11 +143,6 @@ def _traitement_other_preparing(request, demande):
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
proposals[matiere].append(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)
|
|
||||||
if not proposals[matiere]:
|
if not proposals[matiere]:
|
||||||
errors.append("Aucune proposition pour {!s}".format(matiere))
|
errors.append("Aucune proposition pour {!s}".format(matiere))
|
||||||
elif len(proposals[matiere]) < 3:
|
elif len(proposals[matiere]) < 3:
|
||||||
|
@ -200,15 +155,7 @@ def _traitement_other_preparing(request, demande):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
unsatisfied.append(matiere)
|
unsatisfied.append(matiere)
|
||||||
return _finalize_traitement(
|
return _finalize_traitement(request, demande, proposals, unsatisfied, errors=errors)
|
||||||
request,
|
|
||||||
demande,
|
|
||||||
proposals,
|
|
||||||
proposed_for,
|
|
||||||
unsatisfied,
|
|
||||||
attribdata,
|
|
||||||
errors=errors,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _traitement_other(request, demande, redo):
|
def _traitement_other(request, demande, redo):
|
||||||
|
@ -217,45 +164,14 @@ def _traitement_other(request, demande, redo):
|
||||||
return _traitement_other_preparing(request, demande)
|
return _traitement_other_preparing(request, demande)
|
||||||
else:
|
else:
|
||||||
return _traitement_post(request, demande)
|
return _traitement_post(request, demande)
|
||||||
proposals = {}
|
proposals, unsatisfied = demande.get_proposals(redo=redo)
|
||||||
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()
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"petitscours/traitement_demande_autre_niveau.html",
|
"petitscours/traitement_demande_autre_niveau.html",
|
||||||
{
|
{
|
||||||
"demande": demande,
|
"demande": demande,
|
||||||
"unsatisfied": unsatisfied,
|
"unsatisfied": unsatisfied,
|
||||||
"proposals": proposals,
|
"proposals": proposals.items(),
|
||||||
"proposed_for": proposed_for,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -280,12 +196,10 @@ def _traitement_post(request, demande):
|
||||||
proposed_for[user] = [matiere]
|
proposed_for[user] = [matiere]
|
||||||
else:
|
else:
|
||||||
proposed_for[user].append(matiere)
|
proposed_for[user].append(matiere)
|
||||||
proposals_list = proposals.items()
|
|
||||||
proposed_for = proposed_for.items()
|
|
||||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||||
mainmail_object, mainmail_body = render_custom_mail(
|
mainmail_object, mainmail_body = render_custom_mail(
|
||||||
"petits-cours-mail-demandeur",
|
"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"]
|
frommail = settings.MAIL_DATA["petits_cours"]["FROM"]
|
||||||
bccaddress = settings.MAIL_DATA["petits_cours"]["BCC"]
|
bccaddress = settings.MAIL_DATA["petits_cours"]["BCC"]
|
||||||
|
@ -314,8 +228,8 @@ def _traitement_post(request, demande):
|
||||||
connection = mail.get_connection(fail_silently=False)
|
connection = mail.get_connection(fail_silently=False)
|
||||||
connection.send_messages(mails_to_send)
|
connection.send_messages(mails_to_send)
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for matiere in proposals:
|
for matiere, users in proposals.items():
|
||||||
for rank, user in enumerate(proposals[matiere]):
|
for rank, user in enumerate(users):
|
||||||
# TODO(AD): Prefer PetitCoursAttributionCounter.get_uptodate()
|
# TODO(AD): Prefer PetitCoursAttributionCounter.get_uptodate()
|
||||||
counter = PetitCoursAttributionCounter.objects.get(
|
counter = PetitCoursAttributionCounter.objects.get(
|
||||||
user=user, matiere=matiere
|
user=user, matiere=matiere
|
||||||
|
@ -373,7 +287,7 @@ def inscription(request):
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def demande(request):
|
def demande(request, *, raw: bool = False):
|
||||||
success = False
|
success = False
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = DemandeForm(request.POST)
|
form = DemandeForm(request.POST)
|
||||||
|
@ -382,21 +296,7 @@ def demande(request):
|
||||||
success = True
|
success = True
|
||||||
else:
|
else:
|
||||||
form = DemandeForm()
|
form = DemandeForm()
|
||||||
return render(
|
template_name = "petitscours/demande.html"
|
||||||
request, "petitscours/demande.html", {"form": form, "success": success}
|
if raw:
|
||||||
)
|
template_name = "petitscours/demande_raw.html"
|
||||||
|
return render(request, template_name, {"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}
|
|
||||||
)
|
|
||||||
|
|
Loading…
Reference in a new issue