820 lines
30 KiB
Python
820 lines
30 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
|
#
|
|
##############################################################################
|
|
|
|
"""Opérations d'inscriptions aux modules (interface pour gérer options ou parcours)
|
|
"""
|
|
import collections
|
|
from operator import attrgetter
|
|
|
|
import flask
|
|
from flask import url_for, g, request
|
|
from flask_login import current_user
|
|
|
|
from app.comp import res_sem
|
|
from app.comp.res_compat import NotesTableCompat
|
|
from app.models import (
|
|
FormSemestre,
|
|
Identite,
|
|
Partition,
|
|
ScolarFormSemestreValidation,
|
|
UniteEns,
|
|
)
|
|
|
|
from app import log
|
|
from app.tables import list_etuds
|
|
from app.scodoc.scolog import logdb
|
|
from app.scodoc import html_sco_header
|
|
from app.scodoc import htmlutils
|
|
from app.scodoc import sco_cache
|
|
from app.scodoc import codes_cursus
|
|
from app.scodoc import sco_edit_module
|
|
from app.scodoc import sco_edit_ue
|
|
from app.scodoc import sco_etud
|
|
from app.scodoc import sco_formsemestre
|
|
from app.scodoc import sco_formsemestre_inscriptions
|
|
from app.scodoc import sco_groups
|
|
from app.scodoc import sco_moduleimpl
|
|
import app.scodoc.notesdb as ndb
|
|
from app.scodoc.sco_exceptions import ScoValueError
|
|
from app.scodoc.sco_permissions import Permission
|
|
import app.scodoc.sco_utils as scu
|
|
|
|
|
|
def moduleimpl_inscriptions_edit(moduleimpl_id, etuds=[], submitted=False):
|
|
"""Formulaire inscription des etudiants a ce module
|
|
* Gestion des inscriptions
|
|
Nom TD TA TP (triable)
|
|
[x] M. XXX YYY - - -
|
|
|
|
|
|
ajouter TD A, TD B, TP 1, TP 2 ...
|
|
supprimer TD A, TD B, TP 1, TP 2 ...
|
|
|
|
* Si pas les droits: idem en readonly
|
|
"""
|
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
|
|
formsemestre_id = M["formsemestre_id"]
|
|
mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
|
# -- check lock
|
|
if not sem["etat"]:
|
|
raise ScoValueError("opération impossible: semestre verrouille")
|
|
header = html_sco_header.sco_header(
|
|
page_title="Inscription au module",
|
|
init_qtip=True,
|
|
javascripts=["js/etud_info.js"],
|
|
)
|
|
footer = html_sco_header.sco_footer()
|
|
H = [
|
|
header,
|
|
"""<h2>Inscriptions au module <a href="moduleimpl_status?moduleimpl_id=%s">%s</a> (%s)</a></h2>
|
|
<p class="help">Cette page permet d'éditer les étudiants inscrits à ce module
|
|
(ils doivent évidemment être inscrits au semestre).
|
|
Les étudiants cochés sont (ou seront) inscrits. Vous pouvez facilement inscrire ou
|
|
désinscrire tous les étudiants d'un groupe à l'aide des menus "Ajouter" et "Enlever".
|
|
</p>
|
|
<p class="help">Aucune modification n'est prise en compte tant que l'on n'appuie pas sur le bouton
|
|
"Appliquer les modifications".
|
|
</p>
|
|
"""
|
|
% (
|
|
moduleimpl_id,
|
|
mod["titre"] or "(module sans titre)",
|
|
mod["code"] or "(module sans code)",
|
|
),
|
|
]
|
|
# Liste des inscrits à ce semestre
|
|
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
|
|
formsemestre_id
|
|
)
|
|
for ins in inscrits:
|
|
etuds_info = sco_etud.get_etud_info(etudid=ins["etudid"], filled=1)
|
|
if not etuds_info:
|
|
log(
|
|
f"""moduleimpl_inscriptions_edit: inconsistency for etudid={ins['etudid']} !"""
|
|
)
|
|
raise ScoValueError(
|
|
f"""Étudiant {ins['etudid']} inscrit mais inconnu dans la base !"""
|
|
)
|
|
ins["etud"] = etuds_info[0]
|
|
inscrits.sort(key=lambda inscr: sco_etud.etud_sort_key(inscr["etud"]))
|
|
in_m = sco_moduleimpl.do_moduleimpl_inscription_list(
|
|
moduleimpl_id=M["moduleimpl_id"]
|
|
)
|
|
in_module = set([x["etudid"] for x in in_m])
|
|
#
|
|
partitions = sco_groups.get_partitions_list(formsemestre_id)
|
|
#
|
|
if not submitted:
|
|
H.append(
|
|
"""<script type="text/javascript">
|
|
function group_select(groupName, partitionIdx, check) {
|
|
var nb_inputs_to_skip = 2; // nb d'input avant les checkbox !!!
|
|
var elems = document.getElementById("mi_form").getElementsByTagName("input");
|
|
|
|
if (partitionIdx==-1) {
|
|
for (var i =nb_inputs_to_skip; i < elems.length; i++) {
|
|
elems[i].checked=check;
|
|
}
|
|
} else {
|
|
for (var i =nb_inputs_to_skip; i < elems.length; i++) {
|
|
var cells = elems[i].parentNode.parentNode.getElementsByTagName("td")[partitionIdx].childNodes;
|
|
if (cells.length && cells[0].nodeValue == groupName) {
|
|
elems[i].checked=check;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
</script>"""
|
|
)
|
|
H.append(
|
|
f"""<form method="post" id="mi_form" action="{request.base_url}">
|
|
<input type="hidden" name="moduleimpl_id" value="{M['moduleimpl_id']}"/>
|
|
<input type="submit" name="submitted" value="Appliquer les modifications"/>
|
|
<p></p>
|
|
<table><tr>
|
|
{ _make_menu(partitions, "Ajouter", "true") }
|
|
{ _make_menu(partitions, "Enlever", "false")}
|
|
</tr></table>
|
|
<p><br></p>
|
|
<table class="sortable" id="mi_table">
|
|
<tr>
|
|
<th>Nom</th>
|
|
"""
|
|
)
|
|
for partition in partitions:
|
|
if partition["partition_name"]:
|
|
H.append("<th>%s</th>" % partition["partition_name"])
|
|
H.append("</tr>")
|
|
|
|
for ins in inscrits:
|
|
etud = ins["etud"]
|
|
if etud["etudid"] in in_module:
|
|
checked = 'checked="checked"'
|
|
else:
|
|
checked = ""
|
|
H.append(
|
|
"""<tr><td><input type="checkbox" name="etuds:list" value="%s" %s>"""
|
|
% (etud["etudid"], checked)
|
|
)
|
|
H.append(
|
|
"""<a class="discretelink etudinfo" href="%s" id="%s">%s</a>"""
|
|
% (
|
|
url_for(
|
|
"scolar.ficheEtud",
|
|
scodoc_dept=g.scodoc_dept,
|
|
etudid=etud["etudid"],
|
|
),
|
|
etud["etudid"],
|
|
etud["nomprenom"],
|
|
)
|
|
)
|
|
H.append("""</input></td>""")
|
|
|
|
groups = sco_groups.get_etud_groups(etud["etudid"], formsemestre_id)
|
|
for partition in partitions:
|
|
if partition["partition_name"]:
|
|
gr_name = ""
|
|
for group in groups:
|
|
if group["partition_id"] == partition["partition_id"]:
|
|
gr_name = group["group_name"]
|
|
break
|
|
# gr_name == '' si etud non inscrit dans un groupe de cette partition
|
|
H.append(f"<td>{gr_name}</td>")
|
|
H.append("""</table></form>""")
|
|
else: # SUBMISSION
|
|
# inscrit a ce module tous les etuds selectionnes
|
|
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
|
|
moduleimpl_id, formsemestre_id, etuds, reset=True
|
|
)
|
|
return flask.redirect(
|
|
url_for(
|
|
"notes.moduleimpl_status",
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=moduleimpl_id,
|
|
)
|
|
)
|
|
#
|
|
H.append(footer)
|
|
return "\n".join(H)
|
|
|
|
|
|
def _make_menu(partitions: list[dict], title="", check="true") -> str:
|
|
"""Menu with list of all groups"""
|
|
items = [{"title": "Tous", "attr": "onclick=\"group_select('', -1, %s)\"" % check}]
|
|
p_idx = 0
|
|
for partition in partitions:
|
|
if partition["partition_name"] != None:
|
|
p_idx += 1
|
|
for group in sco_groups.get_partition_groups(partition):
|
|
items.append(
|
|
{
|
|
"title": "%s %s"
|
|
% (partition["partition_name"], group["group_name"]),
|
|
"attr": "onclick=\"group_select('%s', %s, %s)\""
|
|
% (group["group_name"], p_idx, check),
|
|
}
|
|
)
|
|
return (
|
|
'<td class="inscr_addremove_menu">'
|
|
+ htmlutils.make_menu(title, items, alone=True)
|
|
+ "</td>"
|
|
)
|
|
|
|
|
|
def moduleimpl_inscriptions_stats(formsemestre_id):
|
|
"""Affiche quelques informations sur les inscriptions
|
|
aux modules de ce semestre.
|
|
|
|
Inscrits au semestre: <nb>
|
|
|
|
Modules communs (tous inscrits): <liste des modules (codes)
|
|
|
|
Autres modules: (regroupés par UE)
|
|
UE 1
|
|
<code du module>: <nb inscrits> (<description en termes de groupes>)
|
|
...
|
|
|
|
En APC, n'affiche pas la colonne UE, car le rattachement n'a pas
|
|
d'importance pédagogique.
|
|
|
|
descriptions:
|
|
groupes de TD A, B et C
|
|
tous sauf groupe de TP Z (?)
|
|
tous sauf <liste d'au plus 7 noms>
|
|
|
|
"""
|
|
authuser = current_user
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
|
is_apc = formsemestre.formation.is_apc()
|
|
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
|
args={"formsemestre_id": formsemestre_id}
|
|
)
|
|
set_all = set([x["etudid"] for x in inscrits])
|
|
partitions, _ = sco_groups.get_formsemestre_groups(formsemestre_id)
|
|
|
|
can_change = (
|
|
authuser.has_permission(Permission.ScoEtudInscrit) and formsemestre.etat
|
|
)
|
|
|
|
# Décrit les inscriptions aux modules:
|
|
commons = [] # modules communs a tous les etuds du semestre
|
|
options = [] # modules ou seuls quelques etudiants sont inscrits
|
|
mod_description = {} # modimplid : str
|
|
mod_nb_inscrits = {} # modimplid : int
|
|
if is_apc:
|
|
modimpls = sorted(formsemestre.modimpls, key=lambda m: m.module.sort_key_apc())
|
|
else:
|
|
modimpls = formsemestre.modimpls_sorted
|
|
for modimpl in modimpls:
|
|
tous_inscrits, nb_inscrits, descr = descr_inscrs_module(
|
|
modimpl.id,
|
|
set_all,
|
|
partitions,
|
|
)
|
|
if tous_inscrits:
|
|
commons.append(modimpl)
|
|
else:
|
|
mod_description[modimpl.id] = descr
|
|
mod_nb_inscrits[modimpl.id] = nb_inscrits
|
|
options.append(modimpl)
|
|
|
|
# Page HTML:
|
|
H = [
|
|
html_sco_header.html_sem_header(
|
|
"Inscriptions aux modules et UE du semestre",
|
|
javascripts=["js/etud_info.js", "js/moduleimpl_inscriptions_stats.js"],
|
|
init_qtip=True,
|
|
)
|
|
]
|
|
|
|
H.append(f"<h3>Inscrits au semestre: {len(inscrits)} étudiants</h3>")
|
|
|
|
if options:
|
|
H.append("<h3>Modules auxquels tous les étudiants ne sont pas inscrits:</h3>")
|
|
H.append(
|
|
f"""<table class="formsemestre_status formsemestre_inscr">
|
|
<tr>
|
|
{'<th>UE</th>' if not is_apc else ""}
|
|
<th>Code</th>
|
|
<th>Inscrits</th>
|
|
<th></th>
|
|
</tr>
|
|
"""
|
|
)
|
|
for modimpl in options:
|
|
if can_change:
|
|
c_link = f"""<a class="discretelink" href="{url_for(
|
|
'notes.moduleimpl_inscriptions_edit',
|
|
scodoc_dept=g.scodoc_dept,
|
|
moduleimpl_id=modimpl.id)
|
|
}">{mod_description[modimpl.id] or "<i>(inscrire des étudiants)</i>"}</a>
|
|
"""
|
|
else:
|
|
c_link = mod_description[modimpl.id]
|
|
H.append("""<tr class="formsemestre_status">""")
|
|
if not is_apc:
|
|
H.append(
|
|
f"""
|
|
<td>{
|
|
modimpl.module.ue.acronyme or ""
|
|
}</td>
|
|
"""
|
|
)
|
|
H.append(
|
|
f"""
|
|
<td class="formsemestre_status_code">{
|
|
modimpl.module.code or "(module sans code)"
|
|
}</td>
|
|
<td class="formsemestre_status_inscrits">{
|
|
mod_nb_inscrits[modimpl.id]}</td><td>{c_link}</td>
|
|
</tr>
|
|
"""
|
|
)
|
|
H.append("</table>")
|
|
else:
|
|
H.append(
|
|
"""<span style="font-size:110%; font-style:italic; color: red;"
|
|
>Tous les étudiants sont inscrits à tous les modules.</span>"""
|
|
)
|
|
|
|
if commons:
|
|
H.append(
|
|
f"""<h3>Modules communs (auxquels tous les étudiants sont inscrits):</h3>
|
|
|
|
<table class="formsemestre_status formsemestre_inscr">
|
|
<tr>
|
|
{'<th>UE</th>' if not is_apc else ""}
|
|
<th>Code</th>
|
|
<th>Module</th>"""
|
|
)
|
|
if is_apc:
|
|
H.append("<th>Parcours</th>")
|
|
H.append("""</tr>""")
|
|
for modimpl in commons:
|
|
if can_change:
|
|
c_link = f"""<a class="discretelink" href="{
|
|
url_for("notes.moduleimpl_inscriptions_edit",
|
|
scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
|
}">{modimpl.module.titre}</a>"""
|
|
else:
|
|
c_link = modimpl.module.titre
|
|
H.append("""<tr class="formsemestre_status_green">""")
|
|
if not is_apc:
|
|
H.append(
|
|
f"""
|
|
<td>{modimpl.module.ue.acronyme or ""}</td>
|
|
"""
|
|
)
|
|
H.append(
|
|
f"""
|
|
<td class="formsemestre_status_code">{
|
|
modimpl.module.code or "(module sans code)"
|
|
}</td><td>{c_link}</td>"""
|
|
)
|
|
if is_apc:
|
|
H.append(
|
|
f"""<td><em>{', '.join(p.code for p in modimpl.module.parcours)}</em></td>"""
|
|
)
|
|
H.append("</tr>")
|
|
H.append("</table>")
|
|
|
|
# Etudiants "dispensés" d'une UE (capitalisée)
|
|
ues_cap_info = get_etuds_with_capitalized_ue(formsemestre_id)
|
|
if ues_cap_info:
|
|
H.append(
|
|
'<h3>Étudiants avec UEs capitalisées (ADM):</h3><ul class="ue_inscr_list">'
|
|
)
|
|
ues = [
|
|
sco_edit_ue.ue_list({"ue_id": ue_id})[0] for ue_id in ues_cap_info.keys()
|
|
]
|
|
ues.sort(key=lambda u: u["numero"])
|
|
for ue in ues:
|
|
H.append(
|
|
f"""<li class="tit"><span class="tit">{ue['acronyme']}: {ue['titre']}</span>"""
|
|
)
|
|
H.append("<ul>")
|
|
for info in ues_cap_info[ue["ue_id"]]:
|
|
etud = sco_etud.get_etud_info(etudid=info["etudid"], filled=True)[0]
|
|
H.append(
|
|
f"""<li class="etud"><a class="discretelink" href="{
|
|
url_for(
|
|
"scolar.ficheEtud",
|
|
scodoc_dept=g.scodoc_dept,
|
|
etudid=etud["etudid"],
|
|
)
|
|
}">{etud["nomprenom"]}</a>"""
|
|
)
|
|
if info["ue_status"]["event_date"]:
|
|
H.append(
|
|
f"""(cap. le {info["ue_status"]["event_date"].strftime("%d/%m/%Y")})"""
|
|
)
|
|
if is_apc:
|
|
is_inscrit_ue = (etud["etudid"], ue["id"]) not in res.dispense_ues
|
|
else:
|
|
# CLASSIQUE
|
|
is_inscrit_ue = info["is_ins"]
|
|
if is_inscrit_ue:
|
|
dm = ", ".join(
|
|
[
|
|
m["code"] or m["abbrev"] or "pas_de_code"
|
|
for m in info["is_ins"]
|
|
]
|
|
)
|
|
H.append(
|
|
f"""actuellement inscrit dans <a title="{dm}" class="discretelink"
|
|
>{len(info["is_ins"])} modules</a>"""
|
|
)
|
|
if is_inscrit_ue:
|
|
if info["ue_status"]["is_capitalized"]:
|
|
H.append(
|
|
"""<div><em style="font-size: 70%">UE actuelle moins bonne que
|
|
l'UE capitalisée</em>
|
|
</div>"""
|
|
)
|
|
else:
|
|
H.append(
|
|
"""<div><em style="font-size: 70%">UE actuelle meilleure que
|
|
l'UE capitalisée</em>
|
|
</div>"""
|
|
)
|
|
if can_change:
|
|
H.append(
|
|
f"""<div><a class="stdlink" href="{
|
|
url_for("notes.etud_desinscrit_ue",
|
|
scodoc_dept=g.scodoc_dept, etudid=etud["etudid"],
|
|
formsemestre_id=formsemestre_id, ue_id=ue["ue_id"])
|
|
}">désinscrire {"des modules" if not is_apc else ""} de cette UE</a></div>
|
|
"""
|
|
)
|
|
else:
|
|
H.append("(non réinscrit dans cette UE)")
|
|
if can_change:
|
|
H.append(
|
|
f"""<div><a class="stdlink" href="{
|
|
url_for("notes.etud_inscrit_ue",
|
|
scodoc_dept=g.scodoc_dept, etudid=etud["etudid"],
|
|
formsemestre_id=formsemestre_id, ue_id=ue["ue_id"])
|
|
}">inscrire à {"" if is_apc else "tous les modules de"} cette UE</a></div>
|
|
"""
|
|
)
|
|
H.append("</li>")
|
|
H.append("</ul></li>")
|
|
H.append("</ul>")
|
|
# BUT: propose dispense de toutes UEs
|
|
if is_apc:
|
|
H.append(_list_but_ue_inscriptions(res, read_only=not can_change))
|
|
|
|
H.append(
|
|
"""<hr/><p class="help">Cette page décrit les inscriptions actuelles.
|
|
Vous pouvez changer (si vous en avez le droit) les inscrits dans chaque module en
|
|
cliquant sur la ligne du module.</p>
|
|
<p class="help">Note: la déinscription d'un module ne perd pas les notes. Ainsi, si
|
|
l'étudiant est ensuite réinscrit au même module, il retrouvera ses notes.</p>
|
|
"""
|
|
)
|
|
|
|
H.append(html_sco_header.sco_footer())
|
|
return "\n".join(H)
|
|
|
|
|
|
def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) -> str:
|
|
"""HTML pour dispenser/reinscrire chaque étudiant à chaque UE du BUT"""
|
|
H = [
|
|
"""
|
|
<div class="list_but_ue_inscriptions">
|
|
<h3>Inscriptions/déinscription aux UEs du BUT</h3>
|
|
<form class="list_but_ue_inscriptions">
|
|
"""
|
|
]
|
|
table_inscr = _table_but_ue_inscriptions(res)
|
|
ue_ids = (
|
|
set.union(*(set(x.keys()) for x in table_inscr.values()))
|
|
if table_inscr
|
|
else set()
|
|
)
|
|
ues = sorted(
|
|
(UniteEns.query.get(ue_id) for ue_id in ue_ids),
|
|
key=lambda u: (u.numero or 0, u.acronyme),
|
|
)
|
|
H.append(
|
|
"""<table id="but_ue_inscriptions" class="stripe compact">
|
|
<thead>
|
|
<tr><th>Nom</th><th>Parcours</th>
|
|
"""
|
|
)
|
|
for ue in ues:
|
|
H.append(f"""<th title="{ue.titre or ''}">{ue.acronyme}</th>""")
|
|
H.append(
|
|
"""</tr>
|
|
</thead>
|
|
<tbody>
|
|
"""
|
|
)
|
|
partition_parcours: Partition = Partition.query.filter_by(
|
|
formsemestre=res.formsemestre, partition_name=scu.PARTITION_PARCOURS
|
|
).first()
|
|
etuds = list_etuds.etuds_sorted_from_ids(table_inscr.keys())
|
|
for etud in etuds:
|
|
ues_etud = table_inscr[etud.id]
|
|
H.append(
|
|
f"""<tr><td><a class="discretelink etudinfo" id={etud.id}
|
|
href="{url_for(
|
|
"scolar.ficheEtud",
|
|
scodoc_dept=g.scodoc_dept,
|
|
etudid=etud.id,
|
|
)}"
|
|
>{etud.nomprenom}</a></td>"""
|
|
)
|
|
# Parcours:
|
|
if partition_parcours:
|
|
group = partition_parcours.get_etud_group(etud.id)
|
|
parcours_name = group.group_name if group else ""
|
|
else:
|
|
parcours_name = ""
|
|
H.append(f"""<td class="parcours">{parcours_name}</td>""")
|
|
# UEs:
|
|
for ue in ues:
|
|
td_class = ""
|
|
est_inscr = ues_etud.get(ue.id) # None si pas concerné
|
|
if est_inscr is None:
|
|
content = ""
|
|
else:
|
|
# Validations d'UE déjà enregistrées dans d'autres semestres
|
|
validations_ue = (
|
|
ScolarFormSemestreValidation.query.filter_by(etudid=etud.id)
|
|
.filter(
|
|
ScolarFormSemestreValidation.formsemestre_id
|
|
!= res.formsemestre.id,
|
|
ScolarFormSemestreValidation.code.in_(
|
|
codes_cursus.CODES_UE_VALIDES
|
|
),
|
|
)
|
|
.join(UniteEns)
|
|
.filter_by(ue_code=ue.ue_code)
|
|
.all()
|
|
)
|
|
validations_ue.sort(
|
|
key=lambda v: codes_cursus.BUT_CODES_ORDER.get(v.code, 0)
|
|
)
|
|
validation = validations_ue[-1] if validations_ue else None
|
|
expl_validation = (
|
|
f"""Validée ({validation.code}) le {
|
|
validation.event_date.strftime("%d/%m/%Y")}"""
|
|
if validation
|
|
else ""
|
|
)
|
|
td_class = ' class="ue_validee"' if validation else ""
|
|
content = f"""<input type="checkbox"
|
|
{'checked' if est_inscr else ''}
|
|
{'disabled' if read_only else ''}
|
|
title="{etud.nomprenom} {'inscrit' if est_inscr else 'non inscrit'} à l'UE {ue.acronyme}. {expl_validation}"
|
|
onchange="change_ue_inscr(this);"
|
|
data-url_inscr="{
|
|
url_for("notes.etud_inscrit_ue",
|
|
scodoc_dept=g.scodoc_dept, etudid=etud.id,
|
|
formsemestre_id=res.formsemestre.id, ue_id=ue.id)
|
|
}"
|
|
data-url_desinscr="{
|
|
url_for("notes.etud_desinscrit_ue",
|
|
scodoc_dept=g.scodoc_dept, etudid=etud.id,
|
|
formsemestre_id=res.formsemestre.id, ue_id=ue.id)
|
|
}"
|
|
/>
|
|
"""
|
|
|
|
H.append(f"""<td{td_class}>{content}</td>""")
|
|
H.append(
|
|
"""
|
|
</tbody>
|
|
</table>
|
|
</form>
|
|
<div class="help">
|
|
<p>L'inscription ou désinscription aux UEs du BUT n'affecte pas les inscriptions aux modules
|
|
mais permet de "dispenser" un étudiant de suivre certaines UEs de son parcours.
|
|
</p>
|
|
<p>Il peut s'agit d'étudiants redoublants ayant déjà acquis l'UE, ou d'une UE
|
|
présente dans le semestre mais pas dans le parcours de l'étudiant, ou bien d'autres
|
|
cas particuliers.
|
|
</p>
|
|
<p>La dispense d'UE est réversible à tout moment (avant le jury de fin de semestre)
|
|
et n'affecte pas les notes saisies.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
"""
|
|
)
|
|
return "\n".join(H)
|
|
|
|
|
|
def _table_but_ue_inscriptions(res: NotesTableCompat) -> dict[int, dict]:
|
|
""" "table" avec les inscriptions aux UEs de chaque étudiant
|
|
{
|
|
etudid : { ue_id : True | False }
|
|
}
|
|
"""
|
|
return {
|
|
etudid: {
|
|
ue_id: (etudid, ue_id) not in res.dispense_ues
|
|
for ue_id in res.etud_ues_ids(etudid)
|
|
}
|
|
for etudid, inscr in res.formsemestre.etuds_inscriptions.items()
|
|
if inscr.etat == scu.INSCRIT
|
|
}
|
|
|
|
|
|
def descr_inscrs_module(moduleimpl_id, set_all, partitions):
|
|
"""returns tous_inscrits, nb_inscrits, descr"""
|
|
ins = sco_moduleimpl.do_moduleimpl_inscription_list(moduleimpl_id=moduleimpl_id)
|
|
set_m = set([x["etudid"] for x in ins]) # ens. des inscrits au module
|
|
non_inscrits = set_all - set_m
|
|
if len(non_inscrits) == 0:
|
|
return True, len(ins), "" # tous inscrits
|
|
if len(non_inscrits) <= 7: # seuil arbitraire
|
|
return False, len(ins), "tous sauf " + _fmt_etud_set(non_inscrits)
|
|
# Cherche les groupes:
|
|
gr = [] # [ ( partition_name , [ group_names ] ) ]
|
|
for partition in partitions:
|
|
grp = [] # groupe de cette partition
|
|
for group in sco_groups.get_partition_groups(partition):
|
|
members = sco_groups.get_group_members(group["group_id"])
|
|
set_g = set([m["etudid"] for m in members])
|
|
if set_g.issubset(set_m):
|
|
grp.append(group["group_name"])
|
|
set_m = set_m - set_g
|
|
gr.append((partition["partition_name"], grp))
|
|
#
|
|
d = []
|
|
for partition_name, grp in gr:
|
|
if grp:
|
|
d.append("groupes de %s: %s" % (partition_name, ", ".join(grp)))
|
|
r = []
|
|
if d:
|
|
r.append(", ".join(d))
|
|
if set_m:
|
|
r.append(_fmt_etud_set(set_m))
|
|
#
|
|
return False, len(ins), " et ".join(r)
|
|
|
|
|
|
def _fmt_etud_set(etudids, max_list_size=7) -> str:
|
|
# max_list_size est le nombre max de noms d'etudiants listés
|
|
# au delà, on indique juste le nombre, sans les noms.
|
|
if len(etudids) > max_list_size:
|
|
return f"{len(etudids)} étudiants"
|
|
etuds = []
|
|
for etudid in etudids:
|
|
etud = Identite.query.get(etudid)
|
|
if etud:
|
|
etuds.append(etud)
|
|
|
|
return ", ".join(
|
|
[
|
|
f"""<a class="discretelink" href="{
|
|
url_for(
|
|
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
|
)
|
|
}">{etud.nomprenom}</a>"""
|
|
for etud in sorted(etuds, key=attrgetter("sort_key"))
|
|
]
|
|
)
|
|
|
|
|
|
def get_etuds_with_capitalized_ue(formsemestre_id: int) -> list[dict]:
|
|
"""For each UE, computes list of students capitalizing the UE.
|
|
returns { ue_id : [ { infos } ] }
|
|
"""
|
|
ues_cap_info = collections.defaultdict(list)
|
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
|
|
|
inscrits = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
|
args={"formsemestre_id": formsemestre_id}
|
|
)
|
|
ues = nt.get_ues_stat_dict()
|
|
for ue in ues:
|
|
for etud in inscrits:
|
|
ue_status = nt.get_etud_ue_status(etud["etudid"], ue["ue_id"])
|
|
if ue_status and ue_status["was_capitalized"]:
|
|
ues_cap_info[ue["ue_id"]].append(
|
|
{
|
|
"etudid": etud["etudid"],
|
|
"ue_status": ue_status,
|
|
"is_ins": etud_modules_ue_inscr(
|
|
etud["etudid"], formsemestre_id, ue["ue_id"]
|
|
),
|
|
}
|
|
)
|
|
return ues_cap_info
|
|
|
|
|
|
def etud_modules_ue_inscr(etudid, formsemestre_id, ue_id) -> list[int]:
|
|
"""Modules de cette UE dans ce semestre
|
|
auxquels l'étudiant est inscrit.
|
|
Utile pour formations classiques seulement.
|
|
"""
|
|
r = ndb.SimpleDictFetch(
|
|
"""SELECT mod.id AS module_id, mod.*
|
|
FROM notes_moduleimpl mi, notes_modules mod,
|
|
notes_formsemestre sem, notes_moduleimpl_inscription i
|
|
WHERE sem.id = %(formsemestre_id)s
|
|
AND mi.formsemestre_id = sem.id
|
|
AND mod.id = mi.module_id
|
|
AND mod.ue_id = %(ue_id)s
|
|
AND i.moduleimpl_id = mi.id
|
|
AND i.etudid = %(etudid)s
|
|
ORDER BY mod.numero
|
|
""",
|
|
{"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id},
|
|
)
|
|
return r
|
|
|
|
|
|
def do_etud_desinscrit_ue_classic(etudid, formsemestre_id, ue_id):
|
|
"""Désinscrit l'etudiant de tous les modules de cette UE dans ce semestre.
|
|
N'utiliser que pour les formations classiques, pas APC.
|
|
"""
|
|
cnx = ndb.GetDBConnexion()
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
|
cursor.execute(
|
|
"""DELETE FROM notes_moduleimpl_inscription
|
|
WHERE id IN (
|
|
SELECT i.id FROM
|
|
notes_moduleimpl mi, notes_modules mod,
|
|
notes_formsemestre sem, notes_moduleimpl_inscription i
|
|
WHERE sem.id = %(formsemestre_id)s
|
|
AND mi.formsemestre_id = sem.id
|
|
AND mod.id = mi.module_id
|
|
AND mod.ue_id = %(ue_id)s
|
|
AND i.moduleimpl_id = mi.id
|
|
AND i.etudid = %(etudid)s
|
|
)
|
|
""",
|
|
{"etudid": etudid, "formsemestre_id": formsemestre_id, "ue_id": ue_id},
|
|
)
|
|
logdb(
|
|
cnx,
|
|
method="etud_desinscrit_ue",
|
|
etudid=etudid,
|
|
msg=f"desinscription UE {ue_id}",
|
|
commit=False,
|
|
)
|
|
sco_cache.invalidate_formsemestre(
|
|
formsemestre_id=formsemestre_id
|
|
) # > desinscription etudiant des modules
|
|
|
|
|
|
def do_etud_inscrit_ue(etudid, formsemestre_id, ue_id):
|
|
"""Incrit l'etudiant de tous les modules de cette UE dans ce semestre."""
|
|
# Verifie qu'il est bien inscrit au semestre
|
|
insem = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
|
|
args={"formsemestre_id": formsemestre_id, "etudid": etudid}
|
|
)
|
|
if not insem:
|
|
raise ScoValueError("%s n'est pas inscrit au semestre !" % etudid)
|
|
|
|
cnx = ndb.GetDBConnexion()
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
|
cursor.execute(
|
|
"""SELECT mi.id
|
|
FROM notes_moduleimpl mi, notes_modules mod, notes_formsemestre sem
|
|
WHERE sem.id = %(formsemestre_id)s
|
|
AND mi.formsemestre_id = sem.id
|
|
AND mod.id = mi.module_id
|
|
AND mod.ue_id = %(ue_id)s
|
|
""",
|
|
{"formsemestre_id": formsemestre_id, "ue_id": ue_id},
|
|
)
|
|
res = cursor.dictfetchall()
|
|
for moduleimpl_id in [x["id"] for x in res]:
|
|
sco_moduleimpl.do_moduleimpl_inscription_create(
|
|
{"moduleimpl_id": moduleimpl_id, "etudid": etudid},
|
|
formsemestre_id=formsemestre_id,
|
|
)
|