forked from ScoDoc/ScoDoc
Compare commits
13 Commits
8ded16b94f
...
635269ff36
Author | SHA1 | Date |
---|---|---|
Emmanuel Viennet | 635269ff36 | |
Emmanuel Viennet | 4aa30a40bd | |
Emmanuel Viennet | 03c03f3725 | |
Emmanuel Viennet | 29eb8c297b | |
Emmanuel Viennet | 38032a8c09 | |
Emmanuel Viennet | 2f2d98954c | |
Emmanuel Viennet | 2e5d94f048 | |
Emmanuel Viennet | 1b1b8ebdc4 | |
Emmanuel Viennet | 9c6db169f3 | |
Emmanuel Viennet | 763f60fb3d | |
Emmanuel Viennet | dece9a82d1 | |
Emmanuel Viennet | 0262b6e2ac | |
Emmanuel Viennet | f8f47e05ff |
|
@ -414,9 +414,16 @@ def bulletin(
|
|||
if version == "pdf":
|
||||
version = "long"
|
||||
pdf = True
|
||||
if version not in scu.BULLETINS_VERSIONS_BUT:
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
if version not in (
|
||||
scu.BULLETINS_VERSIONS_BUT
|
||||
if formsemestre.formation.is_apc()
|
||||
else scu.BULLETINS_VERSIONS
|
||||
):
|
||||
return json_error(404, "version invalide")
|
||||
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first_or_404()
|
||||
if formsemestre.bul_hide_xml and pdf:
|
||||
return json_error(403, "bulletin non disponible")
|
||||
# note: la version json est réduite si bul_hide_xml
|
||||
dept = Departement.query.filter_by(id=formsemestre.dept_id).first_or_404()
|
||||
if g.scodoc_dept and dept.acronym != g.scodoc_dept:
|
||||
return json_error(404, "formsemestre inexistant")
|
||||
|
|
|
@ -52,7 +52,8 @@ def formations():
|
|||
@as_json
|
||||
def formations_ids():
|
||||
"""
|
||||
Retourne la liste de toutes les id de formations (tous départements)
|
||||
Retourne la liste de toutes les id de formations
|
||||
(tous départements, ou du département indiqué dans la route)
|
||||
|
||||
Exemple de résultat : [ 17, 99, 32 ]
|
||||
"""
|
||||
|
|
|
@ -12,7 +12,7 @@ from operator import attrgetter, itemgetter
|
|||
from flask import g, make_response, request
|
||||
from flask_json import as_json
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
import sqlalchemy as sa
|
||||
import app
|
||||
from app import db
|
||||
from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR
|
||||
|
@ -171,6 +171,44 @@ def formsemestres_query():
|
|||
]
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/edit", methods=["POST"])
|
||||
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/edit", methods=["POST"])
|
||||
@scodoc
|
||||
@permission_required(Permission.EditFormSemestre)
|
||||
@as_json
|
||||
def formsemestre_edit(formsemestre_id: int):
|
||||
"""Modifie les champs d'un formsemestre."""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||
editable_keys = {
|
||||
"semestre_id",
|
||||
"titre",
|
||||
"date_debut",
|
||||
"date_fin",
|
||||
"edt_id",
|
||||
"etat",
|
||||
"modalite",
|
||||
"gestion_compensation",
|
||||
"bul_hide_xml",
|
||||
"block_moyennes",
|
||||
"block_moyenne_generale",
|
||||
"mode_calcul_moyennes",
|
||||
"gestion_semestrielle",
|
||||
"bul_bgcolor",
|
||||
"resp_can_edit",
|
||||
"resp_can_change_ens",
|
||||
"ens_can_edit_eval",
|
||||
"elt_sem_apo",
|
||||
"elt_annee_apo",
|
||||
}
|
||||
formsemestre.from_dict({k: v for (k, v) in args.items() if k in editable_keys})
|
||||
try:
|
||||
db.session.commit()
|
||||
except sa.exc.StatementError as exc:
|
||||
return json_error(404, f"invalid argument(s): {exc.args[0]}")
|
||||
return formsemestre.to_dict_api()
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>/bulletins/<string:version>")
|
||||
@api_web_bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
|
||||
|
@ -468,13 +506,13 @@ def etat_evals(formsemestre_id: int):
|
|||
date_mediane = notes_sorted[len(notes_sorted) // 2].date
|
||||
|
||||
eval_dict["saisie_notes"] = {
|
||||
"datetime_debut": date_debut.isoformat()
|
||||
if date_debut is not None
|
||||
else None,
|
||||
"datetime_debut": (
|
||||
date_debut.isoformat() if date_debut is not None else None
|
||||
),
|
||||
"datetime_fin": date_fin.isoformat() if date_fin is not None else None,
|
||||
"datetime_mediane": date_mediane.isoformat()
|
||||
if date_mediane is not None
|
||||
else None,
|
||||
"datetime_mediane": (
|
||||
date_mediane.isoformat() if date_mediane is not None else None
|
||||
),
|
||||
}
|
||||
|
||||
list_eval.append(eval_dict)
|
||||
|
|
|
@ -23,9 +23,12 @@ from app.models.but_refcomp import (
|
|||
from app.scodoc.sco_exceptions import ScoFormatError, ScoValueError
|
||||
|
||||
|
||||
def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
||||
def orebut_import_refcomp(
|
||||
xml_data: str, dept_id: int, orig_filename=None
|
||||
) -> ApcReferentielCompetences:
|
||||
"""Importation XML Orébut
|
||||
peut lever TypeError ou ScoFormatError
|
||||
L'objet créé est ajouté et commité.
|
||||
Résultat: instance de ApcReferentielCompetences
|
||||
"""
|
||||
# Vérifie que le même fichier n'a pas déjà été chargé:
|
||||
|
@ -41,7 +44,7 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
|||
try:
|
||||
root = ElementTree.XML(xml_data)
|
||||
except ElementTree.ParseError as exc:
|
||||
raise ScoFormatError(f"fichier XML Orébut invalide (2): {exc.args}")
|
||||
raise ScoFormatError(f"fichier XML Orébut invalide (2): {exc.args}") from exc
|
||||
if root.tag != "referentiel_competence":
|
||||
raise ScoFormatError("élément racine 'referentiel_competence' manquant")
|
||||
args = ApcReferentielCompetences.attr_from_xml(root.attrib)
|
||||
|
@ -60,7 +63,8 @@ def orebut_import_refcomp(xml_data: str, dept_id: int, orig_filename=None):
|
|||
# ne devrait plus se produire car pas d'unicité de l'id: donc inutile
|
||||
db.session.rollback()
|
||||
raise ScoValueError(
|
||||
f"""Un référentiel a déjà été chargé avec les mêmes compétences ! ({competence.attrib["id"]})
|
||||
f"""Un référentiel a déjà été chargé avec les mêmes compétences ! ({
|
||||
competence.attrib["id"]})
|
||||
"""
|
||||
) from exc
|
||||
ref.competences.append(c)
|
||||
|
|
|
@ -72,7 +72,15 @@ class ModuleImplResults:
|
|||
les caches sont gérés par ResultatsSemestre.
|
||||
"""
|
||||
|
||||
def __init__(self, moduleimpl: ModuleImpl):
|
||||
def __init__(
|
||||
self, moduleimpl: ModuleImpl, etudids: list[int], etudids_actifs: set[int]
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
- etudids : liste des etudids, qui donne l'index du dataframe
|
||||
(doit être tous les étudiants inscrits au semestre incluant les DEM et DEF)
|
||||
- etudids_actifs l'ensemble des étudiants inscrits au semestre, non DEM/DEF.
|
||||
"""
|
||||
self.moduleimpl_id = moduleimpl.id
|
||||
self.module_id = moduleimpl.module.id
|
||||
self.etudids = None
|
||||
|
@ -105,14 +113,21 @@ class ModuleImplResults:
|
|||
"""
|
||||
self.evals_etudids_sans_note = {}
|
||||
"""dict: evaluation_id : set des etudids non notés dans cette eval, sans les démissions."""
|
||||
self.load_notes()
|
||||
self.load_notes(etudids, etudids_actifs)
|
||||
self.etuds_use_session2 = pd.Series(False, index=self.evals_notes.index)
|
||||
"""1 bool par etud, indique si sa moyenne de module vient de la session2"""
|
||||
self.etuds_use_rattrapage = pd.Series(False, index=self.evals_notes.index)
|
||||
"""1 bool par etud, indique si sa moyenne de module utilise la note de rattrapage"""
|
||||
|
||||
def load_notes(self): # ré-écriture de df_load_modimpl_notes
|
||||
def load_notes(
|
||||
self, etudids: list[int], etudids_actifs: set[int]
|
||||
): # ré-écriture de df_load_modimpl_notes
|
||||
"""Charge toutes les notes de toutes les évaluations du module.
|
||||
Args:
|
||||
- etudids : liste des etudids, qui donne l'index du dataframe
|
||||
(doit être tous les étudiants inscrits au semestre incluant les DEM et DEF)
|
||||
- etudids_actifs l'ensemble des étudiants inscrits au semestre, non DEM/DEF.
|
||||
|
||||
Dataframe evals_notes
|
||||
colonnes: le nom de la colonne est l'evaluation_id (int)
|
||||
index (lignes): etudid (int)
|
||||
|
@ -135,12 +150,12 @@ class ModuleImplResults:
|
|||
qui ont des notes ATT.
|
||||
"""
|
||||
moduleimpl = db.session.get(ModuleImpl, self.moduleimpl_id)
|
||||
self.etudids = self._etudids()
|
||||
self.etudids = etudids
|
||||
|
||||
# --- Calcul nombre d'inscrits pour déterminer les évaluations "completes":
|
||||
# on prend les inscrits au module ET au semestre (donc sans démissionnaires)
|
||||
inscrits_module = {ins.etud.id for ins in moduleimpl.inscriptions}.intersection(
|
||||
moduleimpl.formsemestre.etudids_actifs
|
||||
etudids_actifs
|
||||
)
|
||||
self.nb_inscrits_module = len(inscrits_module)
|
||||
|
||||
|
@ -148,6 +163,7 @@ class ModuleImplResults:
|
|||
evals_notes = pd.DataFrame(index=self.etudids, dtype=float)
|
||||
self.evaluations_completes = []
|
||||
self.evaluations_completes_dict = {}
|
||||
self.etudids_attente = set() # empty
|
||||
for evaluation in moduleimpl.evaluations:
|
||||
eval_df = self._load_evaluation_notes(evaluation)
|
||||
# is_complete ssi
|
||||
|
@ -155,13 +171,13 @@ class ModuleImplResults:
|
|||
# ou évaluation déclarée "à prise en compte immédiate"
|
||||
# ou rattrapage, 2eme session, bonus
|
||||
# ET pas bloquée par date (is_blocked)
|
||||
|
||||
is_blocked = evaluation.is_blocked()
|
||||
etudids_sans_note = inscrits_module - set(eval_df.index) # sans les dem.
|
||||
is_complete = (
|
||||
(evaluation.evaluation_type != Evaluation.EVALUATION_NORMALE)
|
||||
or (evaluation.publish_incomplete)
|
||||
or (not etudids_sans_note)
|
||||
) and not evaluation.is_blocked()
|
||||
) and not is_blocked
|
||||
self.evaluations_completes.append(is_complete)
|
||||
self.evaluations_completes_dict[evaluation.id] = is_complete
|
||||
self.evals_etudids_sans_note[evaluation.id] = etudids_sans_note
|
||||
|
@ -178,16 +194,21 @@ class ModuleImplResults:
|
|||
eval_notes_inscr = evals_notes[str(evaluation.id)][list(inscrits_module)]
|
||||
# Nombre de notes (non vides, incluant ATT etc) des inscrits:
|
||||
nb_notes = eval_notes_inscr.notna().sum()
|
||||
# Etudiants avec notes en attente:
|
||||
# = ceux avec note ATT
|
||||
eval_etudids_attente = set(
|
||||
eval_notes_inscr.iloc[
|
||||
(eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy()
|
||||
].index
|
||||
)
|
||||
if evaluation.publish_incomplete:
|
||||
# et en "immédiat", tous ceux sans note
|
||||
eval_etudids_attente |= etudids_sans_note
|
||||
|
||||
if is_blocked:
|
||||
eval_etudids_attente = set()
|
||||
else:
|
||||
# Etudiants avec notes en attente:
|
||||
# = ceux avec note ATT
|
||||
eval_etudids_attente = set(
|
||||
eval_notes_inscr.iloc[
|
||||
(eval_notes_inscr == scu.NOTES_ATTENTE).to_numpy()
|
||||
].index
|
||||
)
|
||||
if evaluation.publish_incomplete:
|
||||
# et en "immédiat", tous ceux sans note
|
||||
eval_etudids_attente |= etudids_sans_note
|
||||
|
||||
# Synthèse pour état du module:
|
||||
self.etudids_attente |= eval_etudids_attente
|
||||
self.evaluations_etat[evaluation.id] = EvaluationEtat(
|
||||
|
@ -229,17 +250,6 @@ class ModuleImplResults:
|
|||
eval_df[str(evaluation.id)] = pd.to_numeric(eval_df[str(evaluation.id)])
|
||||
return eval_df
|
||||
|
||||
def _etudids(self):
|
||||
"""L'index du dataframe est la liste de tous les étudiants inscrits au semestre
|
||||
(incluant les DEM et DEF)
|
||||
"""
|
||||
return [
|
||||
inscr.etudid
|
||||
for inscr in db.session.get(
|
||||
ModuleImpl, self.moduleimpl_id
|
||||
).formsemestre.inscriptions
|
||||
]
|
||||
|
||||
def get_evaluations_coefs(self, modimpl: ModuleImpl) -> np.array:
|
||||
"""Coefficients des évaluations.
|
||||
Les coefs des évals incomplètes, rattrapage, session 2, bonus sont forcés à zéro.
|
||||
|
|
|
@ -99,9 +99,11 @@ def df_load_module_coefs(formation_id: int, semestre_idx: int = None) -> pd.Data
|
|||
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
|
||||
# sur toutes les UE)
|
||||
default_poids = {
|
||||
mod.id: 1.0
|
||||
if (mod.module_type == ModuleType.STANDARD) and (mod.ue.type == UE_SPORT)
|
||||
else 0.0
|
||||
mod.id: (
|
||||
1.0
|
||||
if (mod.module_type == ModuleType.STANDARD) and (mod.ue.type == UE_SPORT)
|
||||
else 0.0
|
||||
)
|
||||
for mod in modules
|
||||
}
|
||||
|
||||
|
@ -148,10 +150,12 @@ def df_load_modimpl_coefs(
|
|||
# 0 pour modules normaux, 1. pour bonus (car par défaut, on veut qu'un bonus agisse
|
||||
# sur toutes les UE)
|
||||
default_poids = {
|
||||
modimpl.id: 1.0
|
||||
if (modimpl.module.module_type == ModuleType.STANDARD)
|
||||
and (modimpl.module.ue.type == UE_SPORT)
|
||||
else 0.0
|
||||
modimpl.id: (
|
||||
1.0
|
||||
if (modimpl.module.module_type == ModuleType.STANDARD)
|
||||
and (modimpl.module.ue.type == UE_SPORT)
|
||||
else 0.0
|
||||
)
|
||||
for modimpl in formsemestre.modimpls_sorted
|
||||
}
|
||||
|
||||
|
@ -200,8 +204,9 @@ def notes_sem_load_cube(formsemestre: FormSemestre) -> tuple:
|
|||
modimpls_results = {}
|
||||
modimpls_evals_poids = {}
|
||||
modimpls_notes = []
|
||||
etudids, etudids_actifs = formsemestre.etudids_actifs()
|
||||
for modimpl in formsemestre.modimpls_sorted:
|
||||
mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
|
||||
mod_results = moy_mod.ModuleImplResultsAPC(modimpl, etudids, etudids_actifs)
|
||||
evals_poids, _ = moy_mod.load_evaluations_poids(modimpl.id)
|
||||
etuds_moy_module = mod_results.compute_module_moy(evals_poids)
|
||||
modimpls_results[modimpl.id] = mod_results
|
||||
|
|
|
@ -256,8 +256,9 @@ def notes_sem_load_matrix(formsemestre: FormSemestre) -> tuple[np.ndarray, dict]
|
|||
"""
|
||||
modimpls_results = {}
|
||||
modimpls_notes = []
|
||||
etudids, etudids_actifs = formsemestre.etudids_actifs()
|
||||
for modimpl in formsemestre.modimpls_sorted:
|
||||
mod_results = moy_mod.ModuleImplResultsClassic(modimpl)
|
||||
mod_results = moy_mod.ModuleImplResultsClassic(modimpl, etudids, etudids_actifs)
|
||||
etuds_moy_module = mod_results.compute_module_moy()
|
||||
modimpls_results[modimpl.id] = mod_results
|
||||
modimpls_notes.append(etuds_moy_module)
|
||||
|
|
|
@ -209,6 +209,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||
"evalcomplete" : bool,
|
||||
"last_modif" : datetime.datetime | None, # saisie de note la plus récente
|
||||
"nb_notes" : int, # nb notes d'étudiants inscrits
|
||||
"nb_attente" : int, # nb de notes en ATTente (même si bloquée)
|
||||
},
|
||||
"evaluation_id" : int,
|
||||
"jour" : datetime.datetime, # e.date_debut or datetime.datetime(1900, 1, 1)
|
||||
|
@ -236,6 +237,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||
"etat": {
|
||||
"blocked": evaluation.is_blocked(),
|
||||
"evalcomplete": etat.is_complete,
|
||||
"nb_attente": etat.nb_attente,
|
||||
"nb_notes": etat.nb_notes,
|
||||
"last_modif": last_modif,
|
||||
},
|
||||
|
|
|
@ -249,11 +249,12 @@ class ScolarNews(db.Model):
|
|||
news_list = cls.last_news(n=n)
|
||||
if not news_list:
|
||||
return ""
|
||||
dept_news_url = url_for("scolar.dept_news", scodoc_dept=g.scodoc_dept)
|
||||
H = [
|
||||
f"""<div class="news"><span class="newstitle"><a href="{
|
||||
url_for("scolar.dept_news", scodoc_dept=g.scodoc_dept)
|
||||
f"""<div class="scobox news"><div class="scobox-title"><a href="{
|
||||
dept_news_url
|
||||
}">Dernières opérations</a>
|
||||
</span><ul class="newslist">"""
|
||||
</div><ul class="newslist">"""
|
||||
]
|
||||
|
||||
for news in news_list:
|
||||
|
@ -261,16 +262,22 @@ class ScolarNews(db.Model):
|
|||
f"""<li class="newslist"><span class="newsdate">{news.formatted_date()}</span><span
|
||||
class="newstext">{news}</span></li>"""
|
||||
)
|
||||
H.append(
|
||||
f"""<li class="newslist">
|
||||
<span class="newstext"><a href="{dept_news_url}" class="stdlink">...</a>
|
||||
</span>
|
||||
</li>"""
|
||||
)
|
||||
|
||||
H.append("</ul>")
|
||||
H.append("</ul></div>")
|
||||
|
||||
# Informations générales
|
||||
H.append(
|
||||
f"""<div><a class="discretelink" href="{scu.SCO_ANNONCES_WEBSITE}">
|
||||
Pour en savoir plus sur ScoDoc voir le site scodoc.org</a>.
|
||||
f"""<div>
|
||||
Pour en savoir plus sur ScoDoc voir
|
||||
<a class="stdlink" href="{scu.SCO_ANNONCES_WEBSITE}">scodoc.org</a>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
H.append("</div>")
|
||||
return "\n".join(H)
|
||||
|
|
|
@ -25,6 +25,7 @@ from sqlalchemy import func
|
|||
import app.scodoc.sco_utils as scu
|
||||
from app import db, log
|
||||
from app.auth.models import User
|
||||
from app import models
|
||||
from app.models import APO_CODE_STR_LEN, CODE_STR_LEN, SHORT_STR_LEN
|
||||
from app.models.but_refcomp import (
|
||||
ApcParcours,
|
||||
|
@ -54,7 +55,7 @@ from app.scodoc.sco_vdi import ApoEtapeVDI
|
|||
GROUPS_AUTO_ASSIGNMENT_DATA_MAX = 1024 * 1024 # bytes
|
||||
|
||||
|
||||
class FormSemestre(db.Model):
|
||||
class FormSemestre(models.ScoDocModel):
|
||||
"""Mise en oeuvre d'un semestre de formation"""
|
||||
|
||||
__tablename__ = "notes_formsemestre"
|
||||
|
@ -84,7 +85,7 @@ class FormSemestre(db.Model):
|
|||
bul_hide_xml = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
"ne publie pas le bulletin XML ou JSON"
|
||||
"ne publie pas le bulletin sur l'API"
|
||||
block_moyennes = db.Column(
|
||||
db.Boolean(), nullable=False, default=False, server_default="false"
|
||||
)
|
||||
|
@ -191,7 +192,8 @@ class FormSemestre(db.Model):
|
|||
def get_formsemestre(
|
||||
cls, formsemestre_id: int | str, dept_id: int = None
|
||||
) -> "FormSemestre":
|
||||
"""FormSemestre ou 404, cherche uniquement dans le département spécifié ou le courant"""
|
||||
"""FormSemestre ou 404, cherche uniquement dans le département spécifié
|
||||
ou le courant (g.scodoc_dept)"""
|
||||
if not isinstance(formsemestre_id, int):
|
||||
try:
|
||||
formsemestre_id = int(formsemestre_id)
|
||||
|
@ -245,12 +247,13 @@ class FormSemestre(db.Model):
|
|||
|
||||
def to_dict_api(self):
|
||||
"""
|
||||
Un dict avec les informations sur le semestre destiné à l'api
|
||||
Un dict avec les informations sur le semestre destinées à l'api
|
||||
"""
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
d.pop("groups_auto_assignment_data", None)
|
||||
d["annee_scolaire"] = self.annee_scolaire()
|
||||
d["bul_hide_xml"] = self.bul_hide_xml
|
||||
if self.date_debut:
|
||||
d["date_debut"] = self.date_debut.strftime("%d/%m/%Y")
|
||||
d["date_debut_iso"] = self.date_debut.isoformat()
|
||||
|
@ -873,7 +876,7 @@ class FormSemestre(db.Model):
|
|||
descr_sem += " " + self.modalite
|
||||
return descr_sem
|
||||
|
||||
def get_abs_count(self, etudid):
|
||||
def get_abs_count(self, etudid) -> tuple[int, int, int]:
|
||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||
tuple (nb abs non just, nb abs justifiées, nb abs total)
|
||||
Utilise un cache.
|
||||
|
@ -933,10 +936,14 @@ class FormSemestre(db.Model):
|
|||
partitions += [p for p in self.partitions if p.partition_name is None]
|
||||
return partitions
|
||||
|
||||
@cached_property
|
||||
def etudids_actifs(self) -> set:
|
||||
"Set des etudids inscrits non démissionnaires et non défaillants"
|
||||
return {ins.etudid for ins in self.inscriptions if ins.etat == scu.INSCRIT}
|
||||
def etudids_actifs(self) -> tuple[list[int], set[int]]:
|
||||
"""Liste les etudids inscrits (incluant DEM et DEF),
|
||||
qui ser al'index des dataframes de notes
|
||||
et donne l'ensemble des inscrits non DEM ni DEF.
|
||||
"""
|
||||
return [inscr.etudid for inscr in self.inscriptions], {
|
||||
ins.etudid for ins in self.inscriptions if ins.etat == scu.INSCRIT
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def etuds_inscriptions(self) -> dict:
|
||||
|
|
|
@ -303,13 +303,16 @@ def sco_header(
|
|||
# div pour affichage messages temporaires
|
||||
H.append('<div id="sco_msg" class="head_message"></div>')
|
||||
#
|
||||
H.append('<div class="sco-app-content">')
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def sco_footer():
|
||||
"""Main HTMl pages footer"""
|
||||
return (
|
||||
"""</div><!-- /gtrcontent -->""" + scu.CUSTOM_HTML_FOOTER + """</body></html>"""
|
||||
"""</div></div><!-- /gtrcontent -->"""
|
||||
+ scu.CUSTOM_HTML_FOOTER
|
||||
+ """</body></html>"""
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -114,10 +114,8 @@ def formsemestre_bulletinetud_published_dict(
|
|||
if etudid not in nt.identdict:
|
||||
abort(404, "etudiant non inscrit dans ce semestre")
|
||||
d = {"type": "classic", "version": "0"}
|
||||
if (not sem["bul_hide_xml"]) or force_publishing:
|
||||
published = True
|
||||
else:
|
||||
published = False
|
||||
|
||||
published = (not formsemestre.bul_hide_xml) or force_publishing
|
||||
if xml_nodate:
|
||||
docdate = ""
|
||||
else:
|
||||
|
|
|
@ -30,17 +30,18 @@
|
|||
|
||||
(coût théorique en heures équivalent TD)
|
||||
"""
|
||||
from flask import request
|
||||
from flask import request, Response
|
||||
|
||||
from app.models import FormSemestre
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
import app.scodoc.sco_utils as scu
|
||||
import sco_version
|
||||
|
||||
|
||||
def formsemestre_table_estim_cost(
|
||||
formsemestre_id,
|
||||
formsemestre: FormSemestre,
|
||||
n_group_td=1,
|
||||
n_group_tp=1,
|
||||
coef_tp=1,
|
||||
|
@ -55,8 +56,6 @@ def formsemestre_table_estim_cost(
|
|||
peut conduire à une sur-estimation du coût s'il y a des modules optionnels
|
||||
(dans ce cas, retoucher le tableau excel exporté).
|
||||
"""
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
|
||||
rows = []
|
||||
for modimpl in formsemestre.modimpls:
|
||||
rows.append(
|
||||
|
@ -76,14 +75,14 @@ def formsemestre_table_estim_cost(
|
|||
+ coef_cours * row["heures_cours"]
|
||||
+ coef_tp * row["heures_tp"]
|
||||
)
|
||||
sum_cours = sum([t["heures_cours"] for t in rows])
|
||||
sum_td = sum([t["heures_td"] for t in rows])
|
||||
sum_tp = sum([t["heures_tp"] for t in rows])
|
||||
sum_cours = sum(t["heures_cours"] for t in rows)
|
||||
sum_td = sum(t["heures_td"] for t in rows)
|
||||
sum_tp = sum(t["heures_tp"] for t in rows)
|
||||
sum_heqtd = sum_td + coef_cours * sum_cours + coef_tp * sum_tp
|
||||
assert abs(sum([t["HeqTD"] for t in rows]) - sum_heqtd) < 0.01, "%s != %s" % (
|
||||
sum([t["HeqTD"] for t in rows]),
|
||||
sum_heqtd,
|
||||
)
|
||||
# assert abs(sum(t["HeqTD"] for t in rows) - sum_heqtd) < 0.01, "%s != %s" % (
|
||||
# sum(t["HeqTD"] for t in rows),
|
||||
# sum_heqtd,
|
||||
# )
|
||||
|
||||
rows.append(
|
||||
{
|
||||
|
@ -117,7 +116,7 @@ def formsemestre_table_estim_cost(
|
|||
),
|
||||
rows=rows,
|
||||
html_sortable=True,
|
||||
preferences=sco_preferences.SemPreferences(formsemestre_id),
|
||||
preferences=sco_preferences.SemPreferences(formsemestre.id),
|
||||
html_class="table_leftalign table_listegroupe",
|
||||
xls_before_table=[
|
||||
[formsemestre.titre_annee()],
|
||||
|
@ -146,47 +145,45 @@ def formsemestre_table_estim_cost(
|
|||
return tab
|
||||
|
||||
|
||||
# view
|
||||
def formsemestre_estim_cost(
|
||||
formsemestre_id,
|
||||
n_group_td=1,
|
||||
n_group_tp=1,
|
||||
coef_tp=1,
|
||||
coef_cours=1.5,
|
||||
formsemestre_id: int,
|
||||
n_group_td: int | str = 1,
|
||||
n_group_tp: int | str = 1,
|
||||
coef_tp: float | str = 1.0,
|
||||
coef_cours: float | str = 1.5,
|
||||
fmt="html",
|
||||
):
|
||||
) -> str | Response:
|
||||
"""Page (formulaire) estimation coûts"""
|
||||
try:
|
||||
n_group_td = int(n_group_td)
|
||||
n_group_tp = int(n_group_tp)
|
||||
coef_tp = float(coef_tp)
|
||||
coef_cours = float(coef_cours)
|
||||
except ValueError as exc:
|
||||
raise ScoValueError("paramètre invalide: utiliser des nombres") from exc
|
||||
|
||||
n_group_td = int(n_group_td)
|
||||
n_group_tp = int(n_group_tp)
|
||||
coef_tp = float(coef_tp)
|
||||
coef_cours = float(coef_cours)
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
|
||||
tab = formsemestre_table_estim_cost(
|
||||
formsemestre_id,
|
||||
formsemestre,
|
||||
n_group_td=n_group_td,
|
||||
n_group_tp=n_group_tp,
|
||||
coef_tp=coef_tp,
|
||||
coef_cours=coef_cours,
|
||||
)
|
||||
h = """
|
||||
<form name="f" method="get" action="%s">
|
||||
<input type="hidden" name="formsemestre_id" value="%s"></input>
|
||||
Nombre de groupes de TD: <input type="text" name="n_group_td" value="%s" onchange="document.f.submit()"/><br>
|
||||
Nombre de groupes de TP: <input type="text" name="n_group_tp" value="%s" onchange="document.f.submit()"/>
|
||||
Coefficient heures TP: <input type="text" name="coef_tp" value="%s" onchange="document.f.submit()"/>
|
||||
tab.html_before_table = f"""
|
||||
<form name="f" method="get" action="{request.base_url}">
|
||||
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}"></input>
|
||||
Nombre de groupes de TD: <input type="text" name="n_group_td" value="{n_group_td}" onchange="document.f.submit()"/><br>
|
||||
Nombre de groupes de TP: <input type="text" name="n_group_tp" value="{n_group_tp}" onchange="document.f.submit()"/>
|
||||
Coefficient heures TP: <input type="text" name="coef_tp" value="{coef_tp}" onchange="document.f.submit()"/>
|
||||
<br>
|
||||
</form>
|
||||
""" % (
|
||||
request.base_url,
|
||||
formsemestre_id,
|
||||
n_group_td,
|
||||
n_group_tp,
|
||||
coef_tp,
|
||||
)
|
||||
tab.html_before_table = h
|
||||
"""
|
||||
tab.base_url = "%s?formsemestre_id=%s&n_group_td=%s&n_group_tp=%s&coef_tp=%s" % (
|
||||
request.base_url,
|
||||
formsemestre_id,
|
||||
formsemestre.id,
|
||||
n_group_td,
|
||||
n_group_tp,
|
||||
coef_tp,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
##############################################################################
|
||||
#
|
||||
# Gestion scolarite IUT
|
||||
# ScoDoc
|
||||
#
|
||||
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
||||
#
|
||||
|
@ -28,208 +28,158 @@
|
|||
"""Page accueil département (liste des semestres, etc)
|
||||
"""
|
||||
|
||||
from flask import g
|
||||
from flask import url_for
|
||||
from sqlalchemy import desc
|
||||
from flask import g, url_for, render_template
|
||||
from flask_login import current_user
|
||||
from flask_sqlalchemy.query import Query
|
||||
|
||||
import app
|
||||
from app import log
|
||||
from app.models import ScolarNews
|
||||
from app.models import FormSemestre, ScolarNews
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.gen_tables import GenTable
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import html_sco_header
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_modalites
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_users
|
||||
from app.views import ScoData
|
||||
|
||||
|
||||
def index_html(showcodes=0, showsemtable=0):
|
||||
def index_html(showcodes=0, showsemtable=0, export_table_formsemestres=False):
|
||||
"Page accueil département (liste des semestres)"
|
||||
showcodes = int(showcodes)
|
||||
showsemtable = int(showsemtable)
|
||||
H = []
|
||||
showsemtable = int(showsemtable) or export_table_formsemestres
|
||||
|
||||
# News:
|
||||
H.append(ScolarNews.scolar_news_summary_html())
|
||||
|
||||
# Avertissement de mise à jour:
|
||||
H.append("""<div id="update_warning"></div>""")
|
||||
|
||||
# Liste de toutes les sessions:
|
||||
sems = sco_formsemestre.do_formsemestre_list()
|
||||
cursems = [] # semestres "courants"
|
||||
othersems = [] # autres (verrouillés)
|
||||
# icon image:
|
||||
groupicon = scu.icontag("groupicon_img", title="Inscrits", border="0")
|
||||
emptygroupicon = scu.icontag(
|
||||
"emptygroupicon_img", title="Pas d'inscrits", border="0"
|
||||
# Liste tous les formsemestres du dept, le plus récent d'abord
|
||||
current_formsemestres = (
|
||||
FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id, etat=True)
|
||||
.filter(FormSemestre.modalite != "EXT")
|
||||
.order_by(desc(FormSemestre.date_debut))
|
||||
)
|
||||
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
|
||||
# Sélection sur l'etat du semestre
|
||||
for sem in sems:
|
||||
if sem["etat"] and sem["modalite"] != "EXT":
|
||||
sem["lockimg"] = ""
|
||||
cursems.append(sem)
|
||||
else:
|
||||
sem["lockimg"] = lockicon
|
||||
othersems.append(sem)
|
||||
# Responsable de formation:
|
||||
sco_formsemestre.sem_set_responsable_name(sem)
|
||||
|
||||
if showcodes:
|
||||
sem["tmpcode"] = f"<td><tt>{sem['formsemestre_id']}</tt></td>"
|
||||
else:
|
||||
sem["tmpcode"] = ""
|
||||
# Nombre d'inscrits:
|
||||
args = {"formsemestre_id": sem["formsemestre_id"]}
|
||||
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(args=args)
|
||||
nb = len(ins) # nb etudiants
|
||||
sem["nb_inscrits"] = nb
|
||||
if nb > 0:
|
||||
sem["groupicon"] = groupicon
|
||||
else:
|
||||
sem["groupicon"] = emptygroupicon
|
||||
|
||||
# S'il n'y a pas d'utilisateurs dans la base, affiche message
|
||||
if not sco_users.get_users_count(dept=g.scodoc_dept):
|
||||
H.append(
|
||||
"""<h2>Aucun utilisateur défini !</h2><p>Pour définir des utilisateurs
|
||||
<a href="Users">passez par la page Utilisateurs</a>.
|
||||
<br>
|
||||
Définissez au moins un utilisateur avec le rôle AdminXXX
|
||||
(le responsable du département XXX).
|
||||
</p>
|
||||
"""
|
||||
locked_formsemestres = (
|
||||
FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id, etat=False)
|
||||
.filter(FormSemestre.modalite != "EXT")
|
||||
.order_by(desc(FormSemestre.date_debut))
|
||||
)
|
||||
formsemestres = (
|
||||
FormSemestre.query.filter_by(dept_id=g.scodoc_dept_id)
|
||||
.filter(FormSemestre.modalite != "EXT")
|
||||
.order_by(desc(FormSemestre.date_debut))
|
||||
)
|
||||
if showsemtable: # table de tous les formsemestres
|
||||
table = _sem_table_gt(
|
||||
formsemestres,
|
||||
showcodes=showcodes,
|
||||
fmt="xlsx" if export_table_formsemestres else "html",
|
||||
)
|
||||
if export_table_formsemestres:
|
||||
return table # cas spécial: on renvoie juste cette table
|
||||
html_table_formsemestres = table.html()
|
||||
else:
|
||||
html_table_formsemestres = None
|
||||
|
||||
# Liste des formsemestres "courants"
|
||||
if cursems:
|
||||
H.append('<h2 class="listesems">Sessions en cours</h2>')
|
||||
H.append(_sem_table(cursems))
|
||||
return render_template(
|
||||
"scolar/index.j2",
|
||||
current_user=current_user,
|
||||
dept_name=sco_preferences.get_preference("DeptName"),
|
||||
formsemestres=formsemestres,
|
||||
html_current_formsemestres=_show_current_formsemestres(
|
||||
current_formsemestres, showcodes
|
||||
),
|
||||
html_table_formsemestres=html_table_formsemestres,
|
||||
locked_formsemestres=locked_formsemestres,
|
||||
nb_locked=locked_formsemestres.count(),
|
||||
nb_user_accounts=sco_users.get_users_count(dept=g.scodoc_dept),
|
||||
page_title=f"ScoDoc {g.scodoc_dept}",
|
||||
Permission=Permission,
|
||||
scolar_news_summary=ScolarNews.scolar_news_summary_html(),
|
||||
showsemtable=showsemtable,
|
||||
sco=ScoData(),
|
||||
)
|
||||
|
||||
|
||||
def _convert_formsemestres_to_dicts(
|
||||
formsemestres: Query, showcodes: bool, fmt: str = "html"
|
||||
) -> list[dict]:
|
||||
""" """
|
||||
if fmt == "html":
|
||||
# icon images:
|
||||
groupicon = scu.icontag("groupicon_img", title="Inscrits", border="0")
|
||||
emptygroupicon = scu.icontag(
|
||||
"emptygroupicon_img", title="Pas d'inscrits", border="0"
|
||||
)
|
||||
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
|
||||
else:
|
||||
groupicon = "X"
|
||||
emptygroupicon = ""
|
||||
lockicon = "X"
|
||||
# génère liste de dict
|
||||
sems = []
|
||||
for formsemestre in formsemestres:
|
||||
nb_inscrits = len(formsemestre.inscriptions)
|
||||
formation = formsemestre.formation
|
||||
sem = {
|
||||
"anneescolaire": formsemestre.annee_scolaire(),
|
||||
"anneescolaire_str": formsemestre.annee_scolaire_str(),
|
||||
"bul_hide_xml": formsemestre.bul_hide_xml,
|
||||
"dateord": formsemestre.date_debut,
|
||||
"elt_annee_apo": formsemestre.elt_annee_apo,
|
||||
"elt_sem_apo": formsemestre.elt_sem_apo,
|
||||
"etapes_apo_str": formsemestre.etapes_apo_str(),
|
||||
"formation": f"{formation.acronyme} v{formation.version}",
|
||||
"_formation_target": url_for(
|
||||
"notes.ue_table",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation.id,
|
||||
semestre_idx=formsemestre.semestre_id,
|
||||
),
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"groupicon": groupicon if nb_inscrits > 0 else emptygroupicon,
|
||||
"lockimg": lockicon,
|
||||
"modalite": formsemestre.modalite,
|
||||
"mois_debut": formsemestre.mois_debut(),
|
||||
"mois_fin": formsemestre.mois_fin(),
|
||||
"nb_inscrits": nb_inscrits,
|
||||
"responsable_name": formsemestre.responsables_str(),
|
||||
"semestre_id": formsemestre.semestre_id,
|
||||
"session_id": formsemestre.session_id(),
|
||||
"titre_num": formsemestre.titre_num(),
|
||||
"tmpcode": (f"<td><tt>{formsemestre.id}</tt></td>" if showcodes else ""),
|
||||
}
|
||||
sems.append(sem)
|
||||
return sems
|
||||
|
||||
|
||||
def _show_current_formsemestres(formsemestres: Query, showcodes: bool) -> str:
|
||||
"""html div avec les formsemestres courants de la page d'accueil"""
|
||||
|
||||
H = []
|
||||
if formsemestres.count():
|
||||
H.append("""<div class="scobox-title">Sessions en cours</div>""")
|
||||
H.append(_sem_table(_convert_formsemestres_to_dicts(formsemestres, showcodes)))
|
||||
else:
|
||||
# aucun semestre courant: affiche aide
|
||||
H.append(
|
||||
"""<h2 class="listesems">Aucune session en cours !</h2>
|
||||
"""
|
||||
<div class="scobox-title">Aucune session en cours !</div>
|
||||
<p>Pour ajouter une session, aller dans <a href="Notes" id="link-programmes">Formations</a>,
|
||||
choisissez une formation, puis suivez le lien "<em>UE, modules, semestres</em>".
|
||||
</p><p>
|
||||
Là, en bas de page, suivez le lien
|
||||
</p>
|
||||
<p>Là, en bas de page, suivez le lien
|
||||
"<em>Mettre en place un nouveau semestre de formation...</em>"
|
||||
</p>"""
|
||||
)
|
||||
|
||||
if showsemtable:
|
||||
H.append(
|
||||
f"""<hr>
|
||||
<h2>Semestres de {sco_preferences.get_preference("DeptName")}</h2>
|
||||
"""
|
||||
)
|
||||
H.append(_sem_table_gt(sems, showcodes=showcodes).html())
|
||||
H.append("</table>")
|
||||
if not showsemtable:
|
||||
H.append(
|
||||
f"""<hr>
|
||||
<p><a class="stdlink" href="{url_for('scolar.index_html',
|
||||
scodoc_dept=g.scodoc_dept, showsemtable=1)
|
||||
}">Voir table des semestres (dont {len(othersems)}
|
||||
verrouillé{'s' if len(othersems) else ''})</a>
|
||||
</p>"""
|
||||
)
|
||||
|
||||
H.append(
|
||||
f"""<p>
|
||||
<form action="{url_for('notes.view_formsemestre_by_etape', scodoc_dept=g.scodoc_dept)}">
|
||||
Chercher étape courante:
|
||||
<input name="etape_apo" type="text" size="8" spellcheck="false"></input>
|
||||
</form>
|
||||
</p>"""
|
||||
)
|
||||
#
|
||||
H.append(
|
||||
"""<hr>
|
||||
<h3>Gestion des étudiants</h3>
|
||||
<ul>
|
||||
"""
|
||||
)
|
||||
if current_user.has_permission(Permission.EtudInscrit):
|
||||
H.append(
|
||||
f"""
|
||||
<li><a class="stdlink" href="{
|
||||
url_for("scolar.etudident_create_form", scodoc_dept=g.scodoc_dept)
|
||||
}">créer <em>un</em> nouvel étudiant</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{
|
||||
url_for("scolar.form_students_import_excel", scodoc_dept=g.scodoc_dept)
|
||||
}">importer de nouveaux étudiants</a>
|
||||
(<em>ne pas utiliser</em> sauf cas particulier : utilisez plutôt le lien dans
|
||||
le tableau de bord semestre si vous souhaitez inscrire les
|
||||
étudiants importés à un semestre)
|
||||
</li>
|
||||
"""
|
||||
)
|
||||
H.append(
|
||||
f"""
|
||||
<li><a class="stdlink" href="{
|
||||
url_for("scolar.export_etudiants_courants", scodoc_dept=g.scodoc_dept)
|
||||
}">exporter tableau des étudiants des semestres en cours</a>
|
||||
</li>
|
||||
"""
|
||||
)
|
||||
if current_user.has_permission(
|
||||
Permission.EtudInscrit
|
||||
) and sco_preferences.get_preference("portal_url"):
|
||||
H.append(
|
||||
f"""
|
||||
<li><a class="stdlink" href="{
|
||||
url_for("scolar.formsemestre_import_etud_admission",
|
||||
scodoc_dept=g.scodoc_dept, tous_courants=1)
|
||||
}">resynchroniser les données étudiants des semestres en cours depuis le portail</a>
|
||||
</li>
|
||||
"""
|
||||
)
|
||||
H.append("</ul>")
|
||||
#
|
||||
if current_user.has_permission(Permission.EditApogee):
|
||||
H.append(
|
||||
f"""<hr>
|
||||
<h3>Exports Apogée</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{url_for('notes.semset_page', scodoc_dept=g.scodoc_dept)
|
||||
}">Années scolaires / exports Apogée</a></li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
#
|
||||
H.append(
|
||||
"""<hr>
|
||||
<h3>Assistance</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="https://scodoc.org/Contact" target="_blank"
|
||||
rel="noopener noreferrer">Contact (Discord)</a></li>
|
||||
<li><a class="stdlink" href="sco_dump_and_send_db">Envoyer données</a></li>
|
||||
</ul>
|
||||
"""
|
||||
)
|
||||
#
|
||||
return (
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"ScoDoc {g.scodoc_dept}", javascripts=["js/scolar_index.js"]
|
||||
)
|
||||
+ "\n".join(H)
|
||||
+ html_sco_header.sco_footer()
|
||||
)
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def _sem_table(sems):
|
||||
def _sem_table(sems: list[dict]) -> str:
|
||||
"""Affiche liste des semestres, utilisée pour semestres en cours"""
|
||||
tmpl = """<tr class="%(trclass)s">%(tmpcode)s
|
||||
<td class="semicon">%(lockimg)s <a href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
|
||||
tmpl = f"""<tr class="%(trclass)s">%(tmpcode)s
|
||||
<td class="semicon">%(lockimg)s <a href="{
|
||||
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept)}?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
|
||||
<td class="datesem">%(mois_debut)s <a title="%(session_id)s">-</a> %(mois_fin)s</td>
|
||||
<td><a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
<td class="titresem"><a class="stdlink" href="{url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept)}?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
<span class="respsem">(%(responsable_name)s)</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -251,25 +201,34 @@ def _sem_table(sems):
|
|||
cur_idx = sem["semestre_id"]
|
||||
else:
|
||||
sem["trclass"] = ""
|
||||
sem["notes_url"] = scu.NotesURL()
|
||||
H.append(tmpl % sem)
|
||||
H.append("</table>")
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def _sem_table_gt(sems, showcodes=False):
|
||||
"""Nouvelle version de la table des semestres
|
||||
def _sem_table_gt(formsemestres: Query, showcodes=False, fmt="html") -> GenTable:
|
||||
"""Table des semestres
|
||||
Utilise une datatables.
|
||||
"""
|
||||
_style_sems(sems)
|
||||
sems = _style_sems(
|
||||
_convert_formsemestres_to_dicts(formsemestres, showcodes, fmt=fmt), fmt=fmt
|
||||
)
|
||||
sems.sort(
|
||||
key=lambda s: (
|
||||
-s["anneescolaire"],
|
||||
s["semestre_id"] if s["semestre_id"] > 0 else -s["semestre_id"] * 1000,
|
||||
s["modalite"],
|
||||
)
|
||||
)
|
||||
columns_ids = (
|
||||
"lockimg",
|
||||
"published",
|
||||
"semestre_id_n",
|
||||
"modalite",
|
||||
#'mois_debut',
|
||||
"dash_mois_fin",
|
||||
"titre_resp",
|
||||
"nb_inscrits",
|
||||
"formation",
|
||||
"etapes_apo_str",
|
||||
"elt_annee_apo",
|
||||
"elt_sem_apo",
|
||||
|
@ -284,7 +243,7 @@ def _sem_table_gt(sems, showcodes=False):
|
|||
titles={
|
||||
"formsemestre_id": "id",
|
||||
"semestre_id_n": "S#",
|
||||
"modalite": "",
|
||||
"modalite": "" if fmt == "html" else "Modalité",
|
||||
"mois_debut": "Début",
|
||||
"dash_mois_fin": "Année",
|
||||
"titre_resp": "Semestre",
|
||||
|
@ -292,6 +251,7 @@ def _sem_table_gt(sems, showcodes=False):
|
|||
"etapes_apo_str": "Étape Apo.",
|
||||
"elt_annee_apo": "Elt. année Apo.",
|
||||
"elt_sem_apo": "Elt. sem. Apo.",
|
||||
"formation": "Formation",
|
||||
},
|
||||
columns_ids=columns_ids,
|
||||
rows=sems,
|
||||
|
@ -311,22 +271,47 @@ def _sem_table_gt(sems, showcodes=False):
|
|||
return tab
|
||||
|
||||
|
||||
def _style_sems(sems):
|
||||
def _style_sems(sems: list[dict], fmt="html") -> list[dict]:
|
||||
"""ajoute quelques attributs de présentation pour la table"""
|
||||
for sem in sems:
|
||||
sem["notes_url"] = scu.NotesURL()
|
||||
sem["_groupicon_target"] = (
|
||||
"%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s"
|
||||
% sem
|
||||
is_h = fmt == "html"
|
||||
if is_h:
|
||||
icon_published = scu.icontag(
|
||||
"eye_img",
|
||||
border="0",
|
||||
title="Bulletins publiés sur la passerelle étudiants",
|
||||
)
|
||||
icon_hidden = scu.icontag(
|
||||
"hide_img",
|
||||
border="0",
|
||||
title="Bulletins NON publiés sur la passerelle étudiants",
|
||||
)
|
||||
else:
|
||||
icon_published = "publié"
|
||||
icon_hidden = "non publié"
|
||||
for sem in sems:
|
||||
status_url = url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=sem["formsemestre_id"],
|
||||
)
|
||||
sem["_groupicon_target"] = status_url
|
||||
sem["_formsemestre_id_class"] = "blacktt"
|
||||
sem["dash_mois_fin"] = '<a title="%(session_id)s"></a> %(anneescolaire)s' % sem
|
||||
sem["dash_mois_fin"] = (
|
||||
(f"""<a title="{sem['session_id']}">{sem['anneescolaire_str']}</a>""")
|
||||
if is_h
|
||||
else sem["anneescolaire_str"]
|
||||
)
|
||||
sem["_dash_mois_fin_class"] = "datesem"
|
||||
sem["titre_resp"] = (
|
||||
"""<a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
<span class="respsem">(%(responsable_name)s)</span>"""
|
||||
% sem
|
||||
(
|
||||
f"""<a class="stdlink" href="{status_url}">{sem['titre_num']}</a>
|
||||
<span class="respsem">({sem['responsable_name']})</span>"""
|
||||
)
|
||||
if is_h
|
||||
else f"""{sem['titre_num']} ({sem["responsable_name"]})"""
|
||||
)
|
||||
sem["published"] = icon_hidden if sem["bul_hide_xml"] else icon_published
|
||||
|
||||
sem["_css_row_class"] = "css_S%d css_M%s" % (
|
||||
sem["semestre_id"],
|
||||
sem["modalite"],
|
||||
|
@ -347,6 +332,7 @@ def _style_sems(sems):
|
|||
sem["_elt_sem_apo_td_attrs"] = (
|
||||
f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """
|
||||
)
|
||||
return sems
|
||||
|
||||
|
||||
def delete_dept(dept_id: int) -> str:
|
||||
|
|
|
@ -279,11 +279,18 @@ def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
|
|||
nb_eval_completes (= prises en compte)
|
||||
nb_evals_en_cours (= avec des notes, mais pas complete)
|
||||
nb_evals_vides (= sans aucune note)
|
||||
nb_evals_attente (= avec des notes en ATTente et pas bloquée)
|
||||
date derniere modif
|
||||
|
||||
Une eval est "complete" ssi tous les etudiants *inscrits* ont une note.
|
||||
"""
|
||||
nb_evals_completes, nb_evals_en_cours, nb_evals_vides, nb_evals_blocked = 0, 0, 0, 0
|
||||
(
|
||||
nb_evals_completes,
|
||||
nb_evals_en_cours,
|
||||
nb_evals_vides,
|
||||
nb_evals_blocked,
|
||||
nb_evals_attente,
|
||||
) = (0, 0, 0, 0, 0)
|
||||
dates = []
|
||||
for e in etat_evals:
|
||||
if e["etat"]["blocked"]:
|
||||
|
@ -294,6 +301,8 @@ def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
|
|||
nb_evals_vides += 1
|
||||
elif not e["etat"]["blocked"]:
|
||||
nb_evals_en_cours += 1
|
||||
if e["etat"]["nb_attente"] and not e["etat"]["blocked"]:
|
||||
nb_evals_attente += 1
|
||||
last_modif = e["etat"]["last_modif"]
|
||||
if last_modif is not None:
|
||||
dates.append(e["etat"]["last_modif"])
|
||||
|
@ -303,6 +312,7 @@ def _summarize_evals_etats(etat_evals: list[dict]) -> dict:
|
|||
|
||||
return {
|
||||
"nb_evals": len(etat_evals),
|
||||
"nb_evals_attente": nb_evals_attente,
|
||||
"nb_evals_blocked": nb_evals_blocked,
|
||||
"nb_evals_completes": nb_evals_completes,
|
||||
"nb_evals_en_cours": nb_evals_en_cours,
|
||||
|
|
|
@ -494,7 +494,7 @@ def table_formsemestres(
|
|||
):
|
||||
"""Une table presentant des semestres"""
|
||||
for sem in sems:
|
||||
sem_set_responsable_name(sem)
|
||||
sem_set_responsable_name(sem) # TODO utiliser formsemestre.responsables_str()
|
||||
sem["_titre_num_target"] = url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
|
|
|
@ -798,7 +798,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
|||
'Tous les étudiants'}
|
||||
</div>
|
||||
<div class="sem-groups-partition-titre">{
|
||||
"Gestion de l'assiduité" if not partition_is_empty else ""
|
||||
"Assiduité" if not partition_is_empty else ""
|
||||
}</div>
|
||||
"""
|
||||
)
|
||||
|
@ -824,14 +824,14 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
|||
</div>
|
||||
<div class="sem-groups-assi">
|
||||
<div>
|
||||
<a class="btn" href="{
|
||||
<a class="stdlink" href="{
|
||||
url_for("assiduites.visu_assi_group",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
date_debut=formsemestre.date_debut.isoformat(),
|
||||
date_fin=formsemestre.date_fin.isoformat(),
|
||||
group_ids=group.id,
|
||||
)}">
|
||||
<button>Bilan assiduité</button></a>
|
||||
Bilan</a>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
@ -839,42 +839,42 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
|||
H.append(
|
||||
f"""
|
||||
<div>
|
||||
<a class="btn" href="{
|
||||
<a class="stdlink" href="{
|
||||
url_for("assiduites.visu_assiduites_group",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
jour = datetime.date.today().isoformat(),
|
||||
group_ids=group.id,
|
||||
)}">
|
||||
<button>Visualiser</button></a>
|
||||
Visualiser</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn" href="{
|
||||
<a class="stdlink" href="{
|
||||
url_for("assiduites.signal_assiduites_group",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
jour=datetime.date.today().isoformat(),
|
||||
formsemestre_id=formsemestre.id,
|
||||
group_ids=group.id,
|
||||
)}">
|
||||
<button>Saisie journalière</button></a>
|
||||
Saisie journalière</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn" href="{
|
||||
<a class="stdlink" href="{
|
||||
url_for("assiduites.signal_assiduites_diff",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
group_ids=group.id,
|
||||
)}">
|
||||
<button>Saisie différée</button></a>
|
||||
Saisie différée</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn" href="{
|
||||
<a class="stdlink" href="{
|
||||
url_for("assiduites.bilan_dept",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
group_ids=group.id,
|
||||
)}">
|
||||
<button>Justificatifs en attente</button></a>
|
||||
Justificatifs en attente</a>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
@ -1312,7 +1312,9 @@ def formsemestre_tableau_modules(
|
|||
if etat["attente"]:
|
||||
H.append(
|
||||
f""" <span><a class="redlink" href="{moduleimpl_status_url}"
|
||||
title="Il y a des notes en attente"><span class="evals_attente">en attente</span></a></span>"""
|
||||
title="Il y a des notes en attente"><span class="evals_attente">{
|
||||
etat["nb_evals_attente"]
|
||||
} en attente</span></a></span>"""
|
||||
)
|
||||
if not mod_is_conforme:
|
||||
H.append(
|
||||
|
|
|
@ -1210,7 +1210,9 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
|
|||
|
||||
<p class="help">Utiliser cette page pour enregistrer des UEs validées antérieurement,
|
||||
<em>dans un semestre hors ScoDoc</em>.</p>
|
||||
<p class="expl"><b>Les UE validées dans ScoDoc sont
|
||||
|
||||
<div class="scobox explanation">
|
||||
<p><b>Les UE validées dans ScoDoc sont
|
||||
automatiquement prises en compte</b>.
|
||||
</p>
|
||||
<p>Cette page est surtout utile pour les étudiants ayant
|
||||
|
@ -1227,11 +1229,12 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
|
|||
l'attribution des ECTS si le code jury est validant (ADM).
|
||||
</p>
|
||||
<p>On ne peut valider ici que les UEs du cursus <b>{formation.titre}</b></p>
|
||||
</div>
|
||||
|
||||
{_get_etud_ue_cap_html(etud, formsemestre)}
|
||||
|
||||
<div class="sco_box">
|
||||
<div class="sco_box_title">
|
||||
<div class="scobox">
|
||||
<div class="scobox-title">
|
||||
Enregistrer une UE antérieure
|
||||
</div>
|
||||
{tf[1]}
|
||||
|
|
|
@ -705,7 +705,8 @@ def _add_eval_columns(
|
|||
nb_att = 0
|
||||
sum_notes = 0
|
||||
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement
|
||||
inscrits = evaluation.moduleimpl.formsemestre.etudids_actifs # set d'etudids
|
||||
# actifs == inscrit au semestre, non DEM ni DEF:
|
||||
_, etudids_actifs = evaluation.moduleimpl.formsemestre.etudids_actifs()
|
||||
notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation.id)
|
||||
|
||||
if evaluation.date_debut:
|
||||
|
@ -734,7 +735,7 @@ def _add_eval_columns(
|
|||
nb_att += 1
|
||||
# calcul moyenne SANS LES ABSENTS ni les DEMISSIONNAIRES
|
||||
if (
|
||||
(etudid in inscrits)
|
||||
(etudid in etudids_actifs)
|
||||
and val is not None
|
||||
and val != scu.NOTES_NEUTRALISE
|
||||
and val != scu.NOTES_ATTENTE
|
||||
|
@ -758,7 +759,7 @@ def _add_eval_columns(
|
|||
comment,
|
||||
)
|
||||
else:
|
||||
if (etudid in inscrits) and evaluation.publish_incomplete:
|
||||
if (etudid in etudids_actifs) and evaluation.publish_incomplete:
|
||||
# Note manquante mais prise en compte immédiate: affiche ATT
|
||||
val = scu.NOTES_ATTENTE
|
||||
val_fmt = "ATT"
|
||||
|
@ -875,8 +876,7 @@ def _add_moymod_column(
|
|||
col_id = "moymod"
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
inscrits = formsemestre.etudids_actifs
|
||||
|
||||
_, etudids_actifs = formsemestre.etudids_actifs()
|
||||
nb_notes = 0
|
||||
sum_notes = 0
|
||||
notes = [] # liste des notes numeriques, pour calcul histogramme uniquement
|
||||
|
@ -885,7 +885,7 @@ def _add_moymod_column(
|
|||
val = nt.get_etud_mod_moy(moduleimpl_id, etudid) # note sur 20, ou 'NA','NI'
|
||||
row[col_id] = scu.fmt_note(val, keep_numeric=keep_numeric)
|
||||
row["_" + col_id + "_td_attrs"] = ' class="moyenne" '
|
||||
if etudid in inscrits and not isinstance(val, str):
|
||||
if etudid in etudids_actifs and not isinstance(val, str):
|
||||
notes.append(val)
|
||||
if not np.isnan(val):
|
||||
nb_notes = nb_notes + 1
|
||||
|
@ -928,7 +928,7 @@ def _add_apc_columns(
|
|||
# on va y ajouter une clé par UE du semestre
|
||||
nt: ResultatsSemestreBUT = res_sem.load_formsemestre_results(modimpl.formsemestre)
|
||||
modimpl_results: ModuleImplResults = nt.modimpls_results[modimpl.id]
|
||||
inscrits = modimpl.formsemestre.etudids_actifs
|
||||
_, etudids_actifs = modimpl.formsemestre.etudids_actifs()
|
||||
# les UE dans lesquelles ce module a un coef non nul:
|
||||
ues_with_coef = nt.modimpl_coefs_df[modimpl.id][
|
||||
nt.modimpl_coefs_df[modimpl.id] > 0
|
||||
|
@ -946,7 +946,7 @@ def _add_apc_columns(
|
|||
if (
|
||||
isinstance(moy_ue, float)
|
||||
and not np.isnan(moy_ue)
|
||||
and row["etudid"] in inscrits
|
||||
and row["etudid"] in etudids_actifs
|
||||
):
|
||||
sum_by_ue[ue.id] += moy_ue
|
||||
nb_notes_by_ue[ue.id] += 1
|
||||
|
|
|
@ -50,8 +50,8 @@ def list_formsemestres_modalites(sems):
|
|||
return modalites
|
||||
|
||||
|
||||
def group_sems_by_modalite(sems):
|
||||
"""Given the list of fromsemestre, group them by modalite,
|
||||
def group_sems_by_modalite(sems: list[dict]):
|
||||
"""Given the list of formsemestre, group them by modalite,
|
||||
sorted in each one by semestre id and date
|
||||
"""
|
||||
sems_by_mod = collections.defaultdict(list)
|
||||
|
|
|
@ -332,28 +332,29 @@ def fiche_etud(etudid=None):
|
|||
)
|
||||
|
||||
# fiche admission
|
||||
infos_admission = _infos_admission(etud, restrict_etud_data)
|
||||
has_adm_notes = any(
|
||||
infos_admission[k] for k in ("math", "physique", "anglais", "francais")
|
||||
)
|
||||
has_bac_info = any(
|
||||
infos_admission[k]
|
||||
for k in (
|
||||
"bac_specialite",
|
||||
"annee_bac",
|
||||
"rapporteur",
|
||||
"commentaire",
|
||||
"classement",
|
||||
"type_admission",
|
||||
"rap",
|
||||
if etud.admission:
|
||||
infos_admission = _infos_admission(etud, restrict_etud_data)
|
||||
has_adm_notes = any(
|
||||
infos_admission[k] for k in ("math", "physique", "anglais", "francais")
|
||||
)
|
||||
)
|
||||
if has_bac_info or has_adm_notes:
|
||||
adm_tmpl = """<!-- Donnees admission -->
|
||||
<div class="fichetitre">Informations admission</div>
|
||||
"""
|
||||
if has_adm_notes:
|
||||
adm_tmpl += """
|
||||
has_bac_info = any(
|
||||
infos_admission[k]
|
||||
for k in (
|
||||
"bac_specialite",
|
||||
"annee_bac",
|
||||
"rapporteur",
|
||||
"commentaire",
|
||||
"classement",
|
||||
"type_admission",
|
||||
"rap",
|
||||
)
|
||||
)
|
||||
if has_bac_info or has_adm_notes:
|
||||
adm_tmpl = """<!-- Donnees admission -->
|
||||
<div class="fichetitre">Informations admission</div>
|
||||
"""
|
||||
if has_adm_notes:
|
||||
adm_tmpl += """
|
||||
<table>
|
||||
<tr><th>Bac</th><th>Année</th><th>Rg</th>
|
||||
<th>Math</th><th>Physique</th><th>Anglais</th><th>Français</th></tr>
|
||||
|
@ -364,24 +365,26 @@ def fiche_etud(etudid=None):
|
|||
<td>%(math)s</td><td>%(physique)s</td><td>%(anglais)s</td><td>%(francais)s</td>
|
||||
</tr>
|
||||
</table>
|
||||
"""
|
||||
adm_tmpl += """
|
||||
<div>Bac %(bac_specialite)s obtenu en %(annee_bac)s </div>
|
||||
<div class="info_lycee">%(info_lycee)s</div>"""
|
||||
if infos_admission["type_admission"] or infos_admission["classement"]:
|
||||
adm_tmpl += """<div class="vadmission">"""
|
||||
if infos_admission["type_admission"]:
|
||||
adm_tmpl += """<span>Voie d'admission: <span class="etud_type_admission">%(type_admission)s</span></span> """
|
||||
if infos_admission["classement"]:
|
||||
adm_tmpl += """<span>Rang admission: <span class="etud_type_admission">%(classement)s</span></span>"""
|
||||
if infos_admission["type_admission"] or infos_admission["classement"]:
|
||||
adm_tmpl += "</div>"
|
||||
if infos_admission["rap"]:
|
||||
adm_tmpl += """<div class="note_rapporteur">%(rap)s</div>"""
|
||||
adm_tmpl += """</div>"""
|
||||
"""
|
||||
adm_tmpl += """
|
||||
<div>Bac %(bac_specialite)s obtenu en %(annee_bac)s </div>
|
||||
<div class="info_lycee">%(info_lycee)s</div>"""
|
||||
if infos_admission["type_admission"] or infos_admission["classement"]:
|
||||
adm_tmpl += """<div class="vadmission">"""
|
||||
if infos_admission["type_admission"]:
|
||||
adm_tmpl += """<span>Voie d'admission: <span class="etud_type_admission">%(type_admission)s</span></span> """
|
||||
if infos_admission["classement"]:
|
||||
adm_tmpl += """<span>Rang admission: <span class="etud_type_admission">%(classement)s</span></span>"""
|
||||
if infos_admission["type_admission"] or infos_admission["classement"]:
|
||||
adm_tmpl += "</div>"
|
||||
if infos_admission["rap"]:
|
||||
adm_tmpl += """<div class="note_rapporteur">%(rap)s</div>"""
|
||||
adm_tmpl += """</div>"""
|
||||
else:
|
||||
adm_tmpl = "" # pas de boite "info admission"
|
||||
info["adm_data"] = adm_tmpl % infos_admission
|
||||
else:
|
||||
adm_tmpl = "" # pas de boite "info admission"
|
||||
info["adm_data"] = adm_tmpl % infos_admission
|
||||
info["adm_data"] = ""
|
||||
|
||||
# Fichiers archivés:
|
||||
info["fichiers_archive_htm"] = (
|
||||
|
@ -654,7 +657,7 @@ def _format_adresse(adresse: Adresse | None) -> dict:
|
|||
|
||||
|
||||
def _infos_admission(etud: Identite, restrict_etud_data: bool) -> dict:
|
||||
"""dict with adminission data, restricted or not"""
|
||||
"""dict with admission data, restricted or not"""
|
||||
# info sur rapporteur et son commentaire
|
||||
rap = ""
|
||||
if not restrict_etud_data:
|
||||
|
@ -799,8 +802,11 @@ def etud_info_html(etudid, with_photo="1", debug=False):
|
|||
code_cursus, _ = sco_report.get_code_cursus_etud(
|
||||
etud, formsemestres=etud.get_formsemestres(), prefix="S", separator=", "
|
||||
)
|
||||
bac = sco_bac.Baccalaureat(etud.admission.bac, etud.admission.specialite)
|
||||
bac_abbrev = bac.abbrev()
|
||||
if etud.admission:
|
||||
bac = sco_bac.Baccalaureat(etud.admission.bac, etud.admission.specialite)
|
||||
bac_abbrev = bac.abbrev()
|
||||
else:
|
||||
bac_abbrev = "-"
|
||||
H = f"""<div class="etud_info_div">
|
||||
<div class="eid_left">
|
||||
<div class="eid_nom"><div><a class="stdlink" target="_blank" href="{
|
||||
|
|
|
@ -68,7 +68,6 @@ from app.scodoc import sco_excel
|
|||
from app.scodoc import sco_formsemestre_inscriptions
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_groups_view
|
||||
from app.scodoc import sco_permissions_check
|
||||
from app.scodoc import sco_undo_notes
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
|
||||
|
@ -531,7 +530,7 @@ def notes_add(
|
|||
|
||||
Return: tuple (etudids_changed, nb_suppress, etudids_with_decision)
|
||||
"""
|
||||
assert evaluation_id is not None
|
||||
evaluation = Evaluation.get_evaluation(evaluation_id)
|
||||
now = psycopg2.Timestamp(*time.localtime()[:6])
|
||||
|
||||
# Vérifie inscription et valeur note
|
||||
|
@ -541,10 +540,16 @@ def notes_add(
|
|||
evaluation_id, getallstudents=True, include_demdef=True
|
||||
)
|
||||
}
|
||||
# Les étudiants inscrits au semestre ni DEM ni DEF
|
||||
_, etudids_actifs = evaluation.moduleimpl.formsemestre.etudids_actifs()
|
||||
for etudid, value in notes:
|
||||
if check_inscription and (etudid not in inscrits):
|
||||
if check_inscription and (
|
||||
(etudid not in inscrits) or (etudid not in etudids_actifs)
|
||||
):
|
||||
log(f"notes_add: {etudid} non inscrit ou DEM/DEF: aborting")
|
||||
raise NoteProcessError(f"étudiant {etudid} non inscrit dans ce module")
|
||||
if (value is not None) and not isinstance(value, float):
|
||||
log(f"notes_add: {etudid} valeur de note invalide ({value}): aborting")
|
||||
raise NoteProcessError(
|
||||
f"etudiant {etudid}: valeur de note invalide ({value})"
|
||||
)
|
||||
|
@ -555,7 +560,6 @@ def notes_add(
|
|||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||
etudids_changed = []
|
||||
nb_suppress = 0
|
||||
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||
formsemestre: FormSemestre = evaluation.moduleimpl.formsemestre
|
||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
# etudids pour lesquels il y a une decision de jury et que la note change:
|
||||
|
|
|
@ -1442,7 +1442,7 @@ def icontag(name, file_format="png", no_size=False, **attrs):
|
|||
|
||||
|
||||
ICON_PDF = icontag("pdficon16x20_img", title="Version PDF")
|
||||
ICON_XLS = icontag("xlsicon_img", title="Version tableur")
|
||||
ICON_XLS = icontag("xlsicon_img", title="Export tableur (xlsx)")
|
||||
|
||||
# HTML emojis
|
||||
EMO_WARNING = "⚠️" # warning /!\
|
||||
|
|
|
@ -577,13 +577,6 @@
|
|||
border: solid 1px #333;
|
||||
}
|
||||
|
||||
.assi-liste {
|
||||
border: 1px solid gray;
|
||||
border-radius: 12px;
|
||||
margin-right: 24px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#options-tableau label {
|
||||
font-weight: normal;
|
||||
margin-right: 12px;
|
||||
|
@ -651,7 +644,7 @@ tr.row-justificatif.non_valide td.assi-type {
|
|||
background-color: var(--color-justi-invalide);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
|
||||
<== Loader ==>
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
--sco-content-max-width: 1024px;
|
||||
--sco-color-explication: rgb(10, 58, 140);
|
||||
--sco-color-background: rgb(242, 242, 238);
|
||||
--sco-color-box-bg: rgb(243, 240, 228);
|
||||
--sco-color-mod-std: #afafc2;
|
||||
--sco-color-ressources: #f8c844;
|
||||
--sco-color-saes: #c6ffab;
|
||||
|
@ -28,10 +29,6 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
div.container {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
|
@ -43,6 +40,51 @@ h3 {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
details > summary:first-of-type {
|
||||
display: list-item!important;
|
||||
}
|
||||
|
||||
div.container {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
div.sco-app-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
div.scobox {
|
||||
flex: 1 0 0; /* Equal width for all boxes */
|
||||
max-width: var(--sco-content-max-width);
|
||||
/* margin: 5px; Optional: Add margin between boxes */
|
||||
|
||||
background-color: var(--sco-color-box-bg);
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
margin-right: 12px;
|
||||
padding: 8px;
|
||||
border: 1px solid #c5b4b2;
|
||||
border-radius: 8px;
|
||||
}
|
||||
div.scobox.explanation {
|
||||
background-color: var(--sco-color-background);
|
||||
}
|
||||
div.scobox div.scobox-title {
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
div.scobox-buttons {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
div.scobox-buttons input {
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
div.scobox-etud {
|
||||
background-color: var(--sco-color-background);
|
||||
}
|
||||
|
||||
/* customization of multiselect style */
|
||||
.multiselect-container.dropdown-menu {
|
||||
background-color: #e9e9e9;
|
||||
|
@ -554,10 +596,6 @@ table.listesems tr.firstsem td {
|
|||
padding-top: 0.8em;
|
||||
}
|
||||
|
||||
td.datesem {
|
||||
font-size: 80%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
h2.listesems {
|
||||
padding-top: 10px;
|
||||
|
@ -645,60 +683,51 @@ table.semlist tbody tr td.modalite {
|
|||
}
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S-1 td {
|
||||
background-color: rgb(251, 250, 216);
|
||||
background-color: rgb(211, 213, 255);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S1 td {
|
||||
background-color: rgb(92%, 95%, 94%);
|
||||
background-color:#e9efef;
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S2 td {
|
||||
background-color: rgb(214, 223, 236);
|
||||
background-color: #d4ebd7;
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S3 td {
|
||||
background-color: rgb(167, 216, 201);
|
||||
background-color: #bedebe;
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S4 td {
|
||||
background-color: rgb(131, 225, 140);
|
||||
background-color: #afd7ad;
|
||||
}
|
||||
div#gtrcontent table.semlist tbody tr.css_S5 td {
|
||||
background-color: #a0cd9a;
|
||||
}
|
||||
div#gtrcontent table.semlist tbody tr.css_S6 td {
|
||||
background-color: #7dcf78;
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_MEXT td {
|
||||
color: #0b6e08;
|
||||
color: #fefcdf;
|
||||
}
|
||||
|
||||
/* ----- Liste des news ----- */
|
||||
|
||||
div.news {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
div.scobox.news {
|
||||
font-size: 10pt;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0px;
|
||||
margin-right: 16px;
|
||||
margin-left: 16px;
|
||||
padding: 0.5em;
|
||||
background-color: rgb(255, 235, 170);
|
||||
-moz-border-radius: 8px;
|
||||
-khtml-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
div.news a,
|
||||
div.news a.stdlink {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div.news a:hover {
|
||||
color: rgb(153, 51, 51);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
span.newstitle {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.newslist {
|
||||
padding-left: 1em;
|
||||
padding-bottom: 0em;
|
||||
|
@ -713,6 +742,21 @@ span.newsdate {
|
|||
span.newstext {
|
||||
font-style: normal;
|
||||
}
|
||||
/* div.news {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 10pt;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0px;
|
||||
margin-right: 16px;
|
||||
margin-left: 16px;
|
||||
padding: 0.5em;
|
||||
background-color: rgb(255, 235, 170);
|
||||
-moz-border-radius: 8px;
|
||||
-khtml-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
} */
|
||||
|
||||
|
||||
|
||||
span.gt_export_icons {
|
||||
margin-left: 1.5em;
|
||||
|
@ -1184,7 +1228,6 @@ a.discretelink:hover {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.expl,
|
||||
.help {
|
||||
max-width: var(--sco-content-max-width);
|
||||
}
|
||||
|
@ -1980,9 +2023,18 @@ ul.ue_inscr_list li.etud {
|
|||
grid-template-columns: 240px auto;
|
||||
}
|
||||
|
||||
.sem-groups-partition .stdlink, .sem-groups-partition .stdlink:visited {
|
||||
color: black;
|
||||
text-decoration-style: dotted;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
.sem-groups-list .stdlink, .sem-groups-list .stdlink:visited {
|
||||
color:rgb(0, 0, 192);
|
||||
}
|
||||
|
||||
.sem-groups-list,
|
||||
.sem-groups-assi {
|
||||
background-color: white;
|
||||
background-color: #ebebeb;
|
||||
border-radius: 6px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
@ -4102,22 +4154,18 @@ div.othersemlist input {
|
|||
margin-left: 20px;
|
||||
}
|
||||
|
||||
div#update_warning {
|
||||
div.scobox.update_warning {
|
||||
display: none;
|
||||
border: 1px solid red;
|
||||
background-color: rgb(250, 220, 220);
|
||||
margin: 3ex;
|
||||
padding-left: 1ex;
|
||||
padding-right: 1ex;
|
||||
padding-bottom: 1ex;
|
||||
}
|
||||
|
||||
div#update_warning > div:first-child:before {
|
||||
div.scobox.update_warning > div:first-child:before {
|
||||
content: url(/ScoDoc/static/icons/warning_img.png);
|
||||
vertical-align: -80%;
|
||||
}
|
||||
|
||||
div#update_warning > div:nth-child(2) {
|
||||
div.scobox.update_warning > div:nth-child(2) {
|
||||
font-size: 80%;
|
||||
padding-left: 8ex;
|
||||
}
|
||||
|
|
|
@ -114,7 +114,9 @@ function paste_text(e) {
|
|||
.classList.contains("masquer_DEM");
|
||||
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
currentInput.value = list[i];
|
||||
if (!currentInput.disabled) { // skip DEM
|
||||
currentInput.value = list[i];
|
||||
}
|
||||
var evt = document.createEvent("HTMLEvents");
|
||||
evt.initEvent("blur", false, true);
|
||||
currentInput.dispatchEvent(evt);
|
||||
|
|
|
@ -110,9 +110,9 @@ div.submit > input {
|
|||
</div>
|
||||
</form>
|
||||
|
||||
<section class="assi-liste">
|
||||
<div class="scobox assi-liste">
|
||||
{{tableau | safe }}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -137,9 +137,9 @@ div.submit > input {
|
|||
</form>
|
||||
</section>
|
||||
{% if tableau %}
|
||||
<section class="assi-liste">
|
||||
<div class="scobox assi-liste">
|
||||
{{tableau | safe }}
|
||||
</section>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -7,24 +7,29 @@
|
|||
{{ super() }}
|
||||
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||
{% endblock scripts %}
|
||||
|
||||
{% block app_content %}
|
||||
<h2>Traitement de l'assiduité</h2>
|
||||
|
||||
<h1>Traitement de l'assiduité</h1>
|
||||
<p class="help">
|
||||
Pour saisir l'assiduité ou consulter les états, il est recommandé de passer par
|
||||
le semestre concerné (saisie par jour ou saisie différée).
|
||||
</p>
|
||||
<p class="help">Pour signaler, annuler ou justifier l'assiduité d'un seul étudiant,
|
||||
choisissez d'abord la personne concernée :</p>
|
||||
<br>
|
||||
{{search_etud | safe}}
|
||||
<br>
|
||||
{{billets | safe}}
|
||||
|
||||
<br>
|
||||
<div class="scobox scobox-etud">
|
||||
<p class="help">Pour signaler, annuler ou justifier l'assiduité d'un seul étudiant,
|
||||
choisissez d'abord la personne concernée :</p>
|
||||
{{search_etud | safe}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Télécharger l'assiduité</h3>
|
||||
|
||||
{{billets | safe}}
|
||||
<div>
|
||||
|
||||
|
||||
<div class="scobox">
|
||||
<div class="scobox-title">Télécharger tous les enregistrements d'assiduité</div>
|
||||
|
||||
<form action="{{url_for('assiduites.recup_assiduites_plage', scodoc_dept=g.scodoc_dept)}}" method="post">
|
||||
<label for="datedeb">
|
||||
Du :
|
||||
|
@ -36,7 +41,7 @@ le semestre concerné (saisie par jour ou saisie différée).
|
|||
<input type="text" class="datepicker" id="datefin" name="datefin">
|
||||
</label>
|
||||
<br>
|
||||
<label for="formsemestre_id">Télécharger l'assiduité de </label>
|
||||
<label for="formsemestre_id">Origine :</label>
|
||||
<select name="formsemestre_id" id="formsemestre_id">
|
||||
<option value="">Tout le département</option>
|
||||
{% for id, titre in formsemestres.items() %}
|
||||
|
@ -47,14 +52,17 @@ le semestre concerné (saisie par jour ou saisie différée).
|
|||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<br>
|
||||
<input type="submit" value="Télécharger" name="telecharger">
|
||||
<div class="scobox-buttons">
|
||||
<input type="submit" value="Télécharger" name="telecharger">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<section class="nonvalide">
|
||||
{{tableau | safe }}
|
||||
<div class="scobox">
|
||||
{{tableau | safe }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock app_content %}
|
|
@ -37,16 +37,10 @@ Bilan assiduité de {{sco.etud.nomprenom}}
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.alerte {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
.scobox.alerte {
|
||||
text-align: center;
|
||||
border-radius: 7px;
|
||||
|
||||
background-color: var(--color-error);
|
||||
|
||||
}
|
||||
|
||||
.alerte.invisible {
|
||||
|
@ -70,11 +64,11 @@ Bilan assiduité de {{sco.etud.nomprenom}}
|
|||
|
||||
<h2>Bilan de l'assiduité de {{sco.etud.html_link_fiche()|safe}}</span></h2>
|
||||
|
||||
<section class="alerte invisible">
|
||||
<div class="scobox alerte invisible">
|
||||
<p>Attention, cet étudiant a trop d'absences</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="stats">
|
||||
<div class="scobox">
|
||||
<!-- Statistiques d'assiduité (nb pres, nb retard, nb absence) + nb justifié -->
|
||||
<h4>Statistiques d'assiduité</h4>
|
||||
<div class="stats-inputs">
|
||||
|
@ -88,13 +82,16 @@ Bilan assiduité de {{sco.etud.nomprenom}}
|
|||
<div class="stats-values">
|
||||
|
||||
</div>
|
||||
</section>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<div class="scobox">
|
||||
<section class="nonvalide">
|
||||
<div>Le tableau n'affiche que les assiduités non justifiées et les justificatifs soumis / modifiés</div>
|
||||
<div class="help">Le tableau n'affiche que les assiduités non justifiées
|
||||
et les justificatifs soumis / modifiés
|
||||
</div>
|
||||
{{tableau | safe }}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="legende">
|
||||
<h3>Statistiques</h3>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div>
|
||||
<div class="sco_box_title">{{ titre }}</div>
|
||||
<div class="assi-tableau">
|
||||
<div class="scobox-title">{{ titre }}</div>
|
||||
<div class="options-tableau">
|
||||
{% if afficher_options != false %}
|
||||
<input type="checkbox" id="show_pres" name="show_pres"
|
||||
|
@ -17,7 +17,7 @@
|
|||
<a style="margin-left:32px;" href="{{request.url}}&fmt=xlsx">{{scu.ICON_XLS|safe}}</a>
|
||||
<br>
|
||||
{% endif %}
|
||||
<label for="nb_ligne_page">Nombre de lignes par page :</label>
|
||||
<label for="nb_ligne_page">Nombre de lignes par page :</label>
|
||||
<select name="nb_ligne_page" id="nb_ligne_page" onchange="updateTableau()">
|
||||
{% for i in [25,50,100,1000] %}
|
||||
{% if i == options.nb_ligne_page %}
|
||||
|
@ -182,14 +182,14 @@
|
|||
// récupération de la colonne à ordonner
|
||||
// il faut avoir une classe `external-type:<NOM COL>`
|
||||
let order_col = e.className.split(" ").find((e)=>e.indexOf("external-type:") != -1);
|
||||
|
||||
|
||||
//Création de la nouvelle url avec le tri
|
||||
const url = new URL(location.href);
|
||||
url.searchParams.set("order", order);
|
||||
url.searchParams.set("order_col", order_col.split(":")[1]);
|
||||
|
||||
location.href = url.href
|
||||
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
|
@ -33,9 +33,11 @@
|
|||
{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
{% block app_content %}
|
||||
page vide
|
||||
{% endblock %}
|
||||
<div class="sco-app-content">
|
||||
{% block app_content %}
|
||||
page vide
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
{# page accueil département #}
|
||||
|
||||
{% extends "sco_page.j2" %}
|
||||
|
||||
{% block app_content %}
|
||||
<style>
|
||||
|
||||
table.listesems tr td.datesem {
|
||||
white-space: nowrap;
|
||||
padding-left: 8px;
|
||||
}
|
||||
table.listesems tr td.titresem {
|
||||
padding-left: 6px;
|
||||
}
|
||||
.table-formsemestres-titre {
|
||||
font-weight: bold;
|
||||
font-size: 110%;
|
||||
}
|
||||
table.semlist tr td.datesem {
|
||||
font-size: 80%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table.semlist tr td.semestre_id_n {
|
||||
text-align: center;
|
||||
}
|
||||
table.semlist tr td.nb_inscrits {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
{# News #}
|
||||
{{scolar_news_summary|safe}}
|
||||
|
||||
{# Avertissement de mise à jour: #}
|
||||
<div id="update_warning" class="scobox update_warning"></div>
|
||||
|
||||
{% if nb_user_accounts == 0 %}
|
||||
<h2>Aucun utilisateur défini !</h2>
|
||||
<p>Pour définir des utilisateurs <a href="{{
|
||||
url_for('users.index_html', scodoc_dept=g.scodoc_dept)
|
||||
}}">passez par la page Utilisateurs</a>.<br>
|
||||
Définissez au moins un utilisateur avec le rôle <tt>AdminXXX</tt>
|
||||
(le responsable du département <tt>XXX</tt>).
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{# Les semestres courants (cad non verrouillés) #}
|
||||
<div class="scobox">
|
||||
{{html_current_formsemestres|safe}}
|
||||
</div>
|
||||
|
||||
{# Table de tous les semestres #}
|
||||
|
||||
{% if html_table_formsemestres %}
|
||||
<details open>
|
||||
<summary class="table-formsemestres-titre">
|
||||
Les {{formsemestres.count()}} semestres de {{dept_name}}
|
||||
|
||||
<a href="{{
|
||||
url_for('scolar.export_table_dept_formsemestres', scodoc_dept=g.scodoc_dept)
|
||||
}}">{{scu.ICON_XLS|safe}}</a>
|
||||
</summary>
|
||||
{{ html_table_formsemestres|safe }}
|
||||
</details>
|
||||
{% else %}
|
||||
<p><a class="stdlink" href="{{
|
||||
url_for('scolar.index_html', scodoc_dept=g.scodoc_dept, showsemtable=1)
|
||||
}}">Voir table des {{formsemestres.count()}} semestres
|
||||
{% if nb_locked %}
|
||||
(dont {{nb_locked}} verrouillé{{'s' if nb_locked > 1 else ''}})
|
||||
{%endif%}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Recherche d'un semestre par code Apogée #}
|
||||
<form action="{{
|
||||
url_for('notes.view_formsemestre_by_etape', scodoc_dept=g.scodoc_dept)
|
||||
}}">
|
||||
Chercher étape courante:
|
||||
<input name="etape_apo" type="text" size="8" spellcheck="false"></input>
|
||||
</form>
|
||||
|
||||
{# Gestion des étudiants #}
|
||||
<div class="scobox">
|
||||
<div class="scobox-title">Gestion des étudiants</div>
|
||||
<ul>
|
||||
{% if current_user.has_permission(Permission.EtudInscrit) %}
|
||||
<li><a class="stdlink" href="{{
|
||||
url_for('scolar.etudident_create_form', scodoc_dept=g.scodoc_dept)
|
||||
}}">créer <em>un</em> nouvel étudiant</a>
|
||||
</li>
|
||||
<li><a class="stdlink" href="{{
|
||||
url_for('scolar.form_students_import_excel', scodoc_dept=g.scodoc_dept)
|
||||
}}">importer de nouveaux étudiants</a>
|
||||
(<em>ne pas utiliser</em> sauf cas particulier : utilisez plutôt le lien dans
|
||||
le tableau de bord semestre si vous souhaitez inscrire les
|
||||
étudiants importés à un semestre)
|
||||
</li>
|
||||
{% endif %}
|
||||
<li><a class="stdlink" href="{{
|
||||
url_for('scolar.export_etudiants_courants', scodoc_dept=g.scodoc_dept)
|
||||
}}">exporter tableau des étudiants des semestres en cours</a>
|
||||
</li>
|
||||
{% if current_user.has_permission(Permission.EtudInscrit) and sco.prefs["portal_url"] %}
|
||||
<li><a class="stdlink" href="{{
|
||||
url_for('scolar.formsemestre_import_etud_admission', scodoc_dept=g.scodoc_dept, tous_courants=1)
|
||||
}}">resynchroniser les données étudiants des semestres en cours depuis le portail</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{# Apogée #}
|
||||
{% if current_user.has_permission(Permission.EditApogee) %}
|
||||
<div class="scobox">
|
||||
<div class="scobox-title">Exports Apogée</div>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="{{
|
||||
url_for('notes.semset_page', scodoc_dept=g.scodoc_dept)
|
||||
}}">Années scolaires / exports Apogée</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Assistance #}
|
||||
<div class="scobox">
|
||||
<div class="scobox-title">Assistance</div>
|
||||
<ul>
|
||||
<li>
|
||||
<a class="stdlink" href="https://scodoc.org/Contact" target="_blank"
|
||||
rel="noopener noreferrer">Contact (Discord)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="stdlink" href="sco_dump_and_send_db">Envoyer données</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% endblock app_content %}
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{scu.STATIC_DIR}}/js/scolar_index.js"></script>
|
||||
{% endblock scripts %}
|
|
@ -349,6 +349,20 @@ def index_html(showcodes=0, showsemtable=0):
|
|||
return sco_dept.index_html(showcodes=showcodes, showsemtable=showsemtable)
|
||||
|
||||
|
||||
@bp.route("/export_table_dept_formsemestres")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
def export_table_dept_formsemestres():
|
||||
"""La table de tous les semestres non EXt du département, en excel"""
|
||||
table = sco_dept.index_html(showcodes=True, export_table_formsemestres=True)
|
||||
return scu.send_file(
|
||||
table.excel(),
|
||||
f"semestres_{g.scodoc_dept}",
|
||||
suffix=scu.XLSX_SUFFIX,
|
||||
mime=scu.XLSX_MIMETYPE,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/install_info")
|
||||
@scodoc
|
||||
@permission_required(Permission.ScoView)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.951"
|
||||
SCOVERSION = "9.6.953"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
|
|
@ -79,10 +79,11 @@ if pytest:
|
|||
return get_auth_headers(API_USER_ADMIN, API_PASSWORD_ADMIN)
|
||||
|
||||
|
||||
def GET(path: str, headers: dict = None, errmsg=None, dept=None):
|
||||
"""Get and returns as JSON
|
||||
def GET(path: str, headers: dict = None, errmsg=None, dept=None, raw=False):
|
||||
"""Get and optionaly returns as JSON
|
||||
Special case for non json result (image or pdf):
|
||||
return Content-Disposition string (inline or attachment)
|
||||
If raw, return a requests.Response
|
||||
"""
|
||||
if dept:
|
||||
url = SCODOC_URL + f"/ScoDoc/{dept}/api" + path
|
||||
|
@ -101,10 +102,11 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None):
|
|||
raise APIError(
|
||||
errmsg or f"""erreur status={reply.status_code} !""", reply.json()
|
||||
)
|
||||
|
||||
if raw:
|
||||
return reply
|
||||
if reply.headers.get("Content-Type", None) == "application/json":
|
||||
return reply.json() # decode la reponse JSON
|
||||
elif reply.headers.get("Content-Type", None) in [
|
||||
if reply.headers.get("Content-Type", None) in [
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"application/pdf",
|
||||
|
|
|
@ -823,16 +823,13 @@ def test_etudiant_bulletin_semestre(api_headers):
|
|||
assert r.content[:4] == b"%PDF"
|
||||
|
||||
######## Bulletin BUT format intermédiaire en pdf #########
|
||||
r = requests.get(
|
||||
API_URL
|
||||
+ "/etudiant/ine/"
|
||||
+ str(INE)
|
||||
+ "/formsemestre/1/bulletin/selectedevals/pdf",
|
||||
r = GET(
|
||||
f"/etudiant/ine/{INE}/formsemestre/1/bulletin/selectedevals/pdf",
|
||||
headers=api_headers,
|
||||
verify=CHECK_CERTIFICATE,
|
||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||
raw=True, # get response, do not convert to json
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert r.headers.get("Content-Type", None) == "application/pdf"
|
||||
assert r.content[:4] == b"%PDF"
|
||||
|
||||
################### LONG + PDF #####################
|
||||
|
@ -869,37 +866,17 @@ def test_etudiant_bulletin_semestre(api_headers):
|
|||
################### SHORT #####################
|
||||
|
||||
######### Test etudid #########
|
||||
r = requests.get(
|
||||
API_URL + "/etudiant/etudid/" + str(ETUDID) + "/formsemestre/1/bulletin/short",
|
||||
headers=api_headers,
|
||||
verify=CHECK_CERTIFICATE,
|
||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||
bul = GET(
|
||||
f"/etudiant/etudid/{ETUDID}/formsemestre/1/bulletin/short", headers=api_headers
|
||||
)
|
||||
assert r.status_code == 200
|
||||
bul = r.json()
|
||||
assert len(bul) == 14 # HARDCODED
|
||||
|
||||
######### Test code nip #########
|
||||
|
||||
r = requests.get(
|
||||
API_URL + "/etudiant/nip/" + str(NIP) + "/formsemestre/1/bulletin/short",
|
||||
headers=api_headers,
|
||||
verify=CHECK_CERTIFICATE,
|
||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||
)
|
||||
assert r.status_code == 200
|
||||
bul = r.json()
|
||||
bul = GET(f"/etudiant/nip/{NIP}/formsemestre/1/bulletin/short", headers=api_headers)
|
||||
assert len(bul) == 14 # HARDCODED
|
||||
|
||||
######### Test code ine #########
|
||||
r = requests.get(
|
||||
API_URL + "/etudiant/ine/" + str(INE) + "/formsemestre/1/bulletin/short",
|
||||
headers=api_headers,
|
||||
verify=CHECK_CERTIFICATE,
|
||||
timeout=scu.SCO_TEST_API_TIMEOUT,
|
||||
)
|
||||
assert r.status_code == 200
|
||||
bul = r.json()
|
||||
bul = GET(f"/etudiant/ine/{INE}/formsemestre/1/bulletin/short", headers=api_headers)
|
||||
assert len(bul) == 14 # HARDCODED
|
||||
|
||||
################### SHORT + PDF #####################
|
||||
|
@ -941,6 +918,23 @@ def test_etudiant_bulletin_semestre(api_headers):
|
|||
)
|
||||
assert r.status_code == 404
|
||||
|
||||
### -------- Modifie publication bulletins
|
||||
admin_header = get_auth_headers(API_USER_ADMIN, API_PASSWORD_ADMIN)
|
||||
formsemestre = POST_JSON(
|
||||
f"/formsemestre/{1}/edit", {"bul_hide_xml": True}, headers=admin_header
|
||||
)
|
||||
assert formsemestre["bul_hide_xml"] is True
|
||||
# La forme utilisée par la passerelle:
|
||||
bul = GET(f"/etudiant/nip/{NIP}/formsemestre/1/bulletin", headers=api_headers)
|
||||
assert len(bul) == 9 # version raccourcie, longueur HARDCODED
|
||||
# TODO forme utilisée par la passerelle pour les PDF
|
||||
# /ScoDoc/api/etudiant/nip/12345/formsemestre/123/bulletin/long/pdf/nosi
|
||||
# TODO voir forme utilisée par ScoDoc en interne:
|
||||
# formsemestre_bulletinetud?formsemestre_id=1263&etudid=16387
|
||||
formsemestre = POST_JSON(
|
||||
f"/formsemestre/{1}/edit", {"bul_hide_xml": False}, headers=admin_header
|
||||
)
|
||||
|
||||
|
||||
def test_etudiant_groups(api_headers):
|
||||
"""
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
Test modèles évaluations avec poids BUT
|
||||
et calcul moyennes modules
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
@ -215,7 +216,8 @@ def test_module_moy(test_client):
|
|||
etud = G.create_etud(nom="test")
|
||||
G.inscrit_etudiant(formsemestre_id, etud)
|
||||
etudid = etud["etudid"]
|
||||
evaluation1 = db.session.get(Evaluation, evaluation1_ids[0])
|
||||
evaluation1: Evaluation = db.session.get(Evaluation, evaluation1_ids[0])
|
||||
formsemestre = evaluation1.moduleimpl.formsemestre
|
||||
# Crée une deuxième évaluation dans le même moduleimpl:
|
||||
evaluation2_id = G.create_evaluation(
|
||||
moduleimpl_id=evaluation1.moduleimpl_id,
|
||||
|
@ -245,10 +247,10 @@ def test_module_moy(test_client):
|
|||
_ = sco_saisie_notes.notes_add(G.default_user, evaluation1.id, [(etudid, n1)])
|
||||
_ = sco_saisie_notes.notes_add(G.default_user, evaluation2.id, [(etudid, n2)])
|
||||
# Calcul de la moyenne du module
|
||||
evals_poids, ues = moy_mod.load_evaluations_poids(moduleimpl_id)
|
||||
evals_poids, _ = moy_mod.load_evaluations_poids(moduleimpl_id)
|
||||
assert evals_poids.shape == (nb_evals, nb_ues)
|
||||
|
||||
mod_results = moy_mod.ModuleImplResultsAPC(modimpl)
|
||||
etudids, etudids_actifs = formsemestre.etudids_actifs()
|
||||
mod_results = moy_mod.ModuleImplResultsAPC(modimpl, etudids, etudids_actifs)
|
||||
evals_notes = mod_results.evals_notes
|
||||
assert evals_notes[evaluation1.id].dtype == np.float64
|
||||
|
||||
|
|
Loading…
Reference in New Issue