forked from ScoDoc/DocScoDoc
Merge branch 'dev92' of https://scodoc.org/git/ScoDoc/ScoDoc into api
This commit is contained in:
commit
28ec8a482a
@ -20,10 +20,10 @@ Flask, SQLAlchemy, au lien de Python2/Zope dans les versions précédentes).
|
||||
|
||||
### État actuel (26 jan 22)
|
||||
|
||||
- 9.1 (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
|
||||
- 9.1.5x (master) reproduit l'ensemble des fonctions de ScoDoc 7 (donc pas de BUT), sauf:
|
||||
- ancien module "Entreprises" (obsolète) et ajoute la gestion du BUT.
|
||||
|
||||
- 9.2 (branche refactor_nt) est la version de développement.
|
||||
- 9.2 (branche dev92) est la version de développement.
|
||||
|
||||
|
||||
### Lignes de commandes
|
||||
|
@ -295,10 +295,12 @@ def create_app(config_class=DevConfig):
|
||||
|
||||
from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy
|
||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||
from app.but.bulletin_but_pdf import BulletinGeneratorStandardBUT
|
||||
from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC
|
||||
|
||||
# l'ordre est important, le premier sera le "défaut" pour les nouveaux départements.
|
||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
|
||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandardBUT)
|
||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
|
||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
|
||||
if app.testing or app.debug:
|
||||
|
@ -9,14 +9,15 @@
|
||||
|
||||
import datetime
|
||||
from flask import url_for, g
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
from app.scodoc.sco_utils import fmt_note
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
from app.models import FormSemestre, Identite, formsemestre
|
||||
from app.scodoc import sco_bulletins, sco_utils as scu
|
||||
from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT, DEF
|
||||
from app.scodoc.sco_utils import fmt_note
|
||||
|
||||
|
||||
class BulletinBUT:
|
||||
@ -28,6 +29,7 @@ class BulletinBUT:
|
||||
def __init__(self, formsemestre: FormSemestre):
|
||||
""" """
|
||||
self.res = ResultatsSemestreBUT(formsemestre)
|
||||
self.prefs = sco_preferences.SemPreferences(formsemestre.id)
|
||||
|
||||
def etud_ue_mod_results(self, etud, ue, modimpls) -> dict:
|
||||
"dict synthèse résultats dans l'UE pour les modules indiqués"
|
||||
@ -84,7 +86,7 @@ class BulletinBUT:
|
||||
"saes": self.etud_ue_mod_results(etud, ue, res.saes),
|
||||
}
|
||||
if ue.type != UE_SPORT:
|
||||
if sco_preferences.get_preference("bul_show_ue_rangs", res.formsemestre.id):
|
||||
if self.prefs["bul_show_ue_rangs"]:
|
||||
rangs, effectif = res.ue_rangs[ue.id]
|
||||
rang = rangs[etud.id]
|
||||
else:
|
||||
@ -109,9 +111,10 @@ class BulletinBUT:
|
||||
d["modules"] = self.etud_mods_results(etud, modimpls_spo)
|
||||
return d
|
||||
|
||||
def etud_mods_results(self, etud, modimpls) -> dict:
|
||||
def etud_mods_results(self, etud, modimpls, version="long") -> dict:
|
||||
"""dict synthèse résultats des modules indiqués,
|
||||
avec évaluations de chacun."""
|
||||
avec évaluations de chacun (sauf si version == "short")
|
||||
"""
|
||||
res = self.res
|
||||
d = {}
|
||||
# etud_idx = self.etud_index[etud.id]
|
||||
@ -152,14 +155,14 @@ class BulletinBUT:
|
||||
"evaluations": [
|
||||
self.etud_eval_results(etud, e)
|
||||
for e in modimpl.evaluations
|
||||
if e.visibulletin
|
||||
if (e.visibulletin or version == "long")
|
||||
and (
|
||||
modimpl_results.evaluations_etat[e.id].is_complete
|
||||
or sco_preferences.get_preference(
|
||||
"bul_show_all_evals", res.formsemestre.id
|
||||
)
|
||||
or self.prefs["bul_show_all_evals"]
|
||||
)
|
||||
],
|
||||
]
|
||||
if version != "short"
|
||||
else [],
|
||||
}
|
||||
return d
|
||||
|
||||
@ -216,13 +219,23 @@ class BulletinBUT:
|
||||
else:
|
||||
return f"Bonus de {fmt_note(bonus_vect.iloc[0])}"
|
||||
|
||||
def bulletin_etud(self, etud, formsemestre, force_publishing=False) -> dict:
|
||||
"""Le bulletin de l'étudiant dans ce semestre.
|
||||
Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
|
||||
def bulletin_etud(
|
||||
self,
|
||||
etud: Identite,
|
||||
formsemestre: FormSemestre,
|
||||
force_publishing=False,
|
||||
version="long",
|
||||
) -> dict:
|
||||
"""Le bulletin de l'étudiant dans ce semestre: dict pour la version JSON / HTML.
|
||||
- version:
|
||||
"long", "selectedevals": toutes les infos (notes des évaluations)
|
||||
"short" : ne descend pas plus bas que les modules.
|
||||
|
||||
- Si force_publishing, rempli le bulletin même si bul_hide_xml est vrai
|
||||
(bulletins non publiés).
|
||||
"""
|
||||
res = self.res
|
||||
etat_inscription = etud.etat_inscription(formsemestre.id)
|
||||
etat_inscription = etud.inscription_etat(formsemestre.id)
|
||||
nb_inscrits = self.res.get_inscriptions_counts()[scu.INSCRIT]
|
||||
published = (not formsemestre.bul_hide_xml) or force_publishing
|
||||
d = {
|
||||
@ -239,7 +252,9 @@ class BulletinBUT:
|
||||
},
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etat_inscription": etat_inscription,
|
||||
"options": sco_preferences.bulletin_option_affichage(formsemestre.id),
|
||||
"options": sco_preferences.bulletin_option_affichage(
|
||||
formsemestre.id, self.prefs
|
||||
),
|
||||
}
|
||||
if not published:
|
||||
return d
|
||||
@ -278,8 +293,10 @@ class BulletinBUT:
|
||||
)
|
||||
d.update(
|
||||
{
|
||||
"ressources": self.etud_mods_results(etud, res.ressources),
|
||||
"saes": self.etud_mods_results(etud, res.saes),
|
||||
"ressources": self.etud_mods_results(
|
||||
etud, res.ressources, version=version
|
||||
),
|
||||
"saes": self.etud_mods_results(etud, res.saes, version=version),
|
||||
"ues": {
|
||||
ue.acronyme: self.etud_ue_results(etud, ue)
|
||||
for ue in res.ues
|
||||
@ -312,3 +329,54 @@ class BulletinBUT:
|
||||
)
|
||||
|
||||
return d
|
||||
|
||||
def bulletin_etud_complet(self, etud: Identite) -> dict:
|
||||
"""Bulletin dict complet avec toutes les infos pour les bulletins BUT pdf
|
||||
Résultat compatible avec celui de sco_bulletins.formsemestre_bulletinetud_dict
|
||||
"""
|
||||
d = self.bulletin_etud(etud, self.res.formsemestre, force_publishing=True)
|
||||
d["etudid"] = etud.id
|
||||
d["etud"] = d["etudiant"]
|
||||
d["etud"]["nomprenom"] = etud.nomprenom
|
||||
d.update(self.res.sem)
|
||||
etud_etat = self.res.get_etud_etat(etud.id)
|
||||
d["filigranne"] = sco_bulletins_pdf.get_filigranne(
|
||||
etud_etat,
|
||||
self.prefs,
|
||||
decision_sem=d["semestre"].get("decision_sem"),
|
||||
)
|
||||
if etud_etat == scu.DEMISSION:
|
||||
d["demission"] = "(Démission)"
|
||||
elif etud_etat == DEF:
|
||||
d["demission"] = "(Défaillant)"
|
||||
else:
|
||||
d["demission"] = ""
|
||||
|
||||
# --- Absences
|
||||
d["nbabs"], d["nbabsjust"] = self.res.formsemestre.get_abs_count(etud.id)
|
||||
|
||||
# --- Decision Jury
|
||||
infos, dpv = sco_bulletins.etud_descr_situation_semestre(
|
||||
etud.id,
|
||||
self.res.formsemestre.id,
|
||||
format="html",
|
||||
show_date_inscr=self.prefs["bul_show_date_inscr"],
|
||||
show_decisions=self.prefs["bul_show_decision"],
|
||||
show_uevalid=self.prefs["bul_show_uevalid"],
|
||||
show_mention=self.prefs["bul_show_mention"],
|
||||
)
|
||||
|
||||
d.update(infos)
|
||||
# --- Rangs
|
||||
d[
|
||||
"rang_nt"
|
||||
] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
||||
d["rang_txt"] = "Rang " + d["rang_nt"]
|
||||
|
||||
# --- Appréciations
|
||||
d.update(
|
||||
sco_bulletins.get_appreciations_list(self.res.formsemestre.id, etud.id)
|
||||
)
|
||||
d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"]))
|
||||
|
||||
return d
|
||||
|
116
app/but/bulletin_but_pdf.py
Normal file
116
app/but/bulletin_but_pdf.py
Normal file
@ -0,0 +1,116 @@
|
||||
##############################################################################
|
||||
# ScoDoc
|
||||
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
||||
# See LICENSE
|
||||
##############################################################################
|
||||
|
||||
"""Génération bulletin BUT au format PDF standard
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from app.scodoc.sco_pdf import blue, cm, mm
|
||||
|
||||
from flask import url_for, g
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
from app.scodoc import gen_tables
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
from app.scodoc.sco_utils import fmt_note
|
||||
from app.comp.res_but import ResultatsSemestreBUT
|
||||
|
||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||
|
||||
|
||||
class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
||||
"""Génération du bulletin de BUT au format PDF.
|
||||
|
||||
self.infos est le dict issu de BulletinBUT.bulletin_etud_complet()
|
||||
"""
|
||||
|
||||
list_in_menu = False # spécialisation du BulletinGeneratorStandard, ne pas présenter à l'utilisateur
|
||||
|
||||
def bul_table(self, format="html"):
|
||||
"""Génère la table centrale du bulletin de notes
|
||||
Renvoie:
|
||||
- en HTML: une chaine
|
||||
- en PDF: une liste d'objets PLATYPUS (eg instance de Table).
|
||||
"""
|
||||
formsemestre_id = self.infos["formsemestre_id"]
|
||||
(
|
||||
synth_col_keys,
|
||||
synth_P,
|
||||
synth_pdf_style,
|
||||
synth_col_widths,
|
||||
) = self.but_table_synthese()
|
||||
#
|
||||
table_synthese = gen_tables.GenTable(
|
||||
rows=synth_P,
|
||||
columns_ids=synth_col_keys,
|
||||
pdf_table_style=synth_pdf_style,
|
||||
pdf_col_widths=[synth_col_widths[k] for k in synth_col_keys],
|
||||
preferences=self.preferences,
|
||||
html_class="notes_bulletin",
|
||||
html_class_ignore_default=True,
|
||||
html_with_td_classes=True,
|
||||
)
|
||||
# Ici on ajoutera table des ressources, tables des UE
|
||||
# TODO
|
||||
|
||||
# XXX à modifier pour générer plusieurs tables:
|
||||
return table_synthese.gen(format=format)
|
||||
|
||||
def but_table_synthese(self):
|
||||
"""La table de synthèse; pour chaque UE, liste des ressources et SAÉs avec leurs notes
|
||||
et leurs coefs.
|
||||
Renvoie: colkeys, P, pdf_style, colWidths
|
||||
- colkeys: nom des colonnes de la table (clés)
|
||||
- P : table (liste de dicts de chaines de caracteres)
|
||||
- pdf_style : commandes table Platypus
|
||||
- largeurs de colonnes pour PDF
|
||||
"""
|
||||
col_widths = {
|
||||
"titre": None,
|
||||
"moyenne": 2 * cm,
|
||||
"coef": 2 * cm,
|
||||
}
|
||||
P = [] # elems pour générer table avec gen_table (liste de dicts)
|
||||
col_keys = ["titre", "moyenne"] # noms des colonnes à afficher
|
||||
for ue_acronym, ue in self.infos["ues"].items():
|
||||
# 1er ligne titre UE
|
||||
moy_ue = ue.get("moyenne")
|
||||
t = {
|
||||
"titre": f"{ue_acronym} - {ue['titre']}",
|
||||
"moyenne": moy_ue.get("value", "-") if moy_ue is not None else "-",
|
||||
"_css_row_class": "note_bold",
|
||||
"_pdf_row_markup": ["b"],
|
||||
"_pdf_style": [],
|
||||
}
|
||||
P.append(t)
|
||||
# 2eme ligne titre UE (bonus/malus/ects)
|
||||
t = {
|
||||
"titre": "",
|
||||
"moyenne": f"""Bonus: {ue['bonus']} - Malus: {
|
||||
ue["malus"]} - ECTS: {ue["ECTS"]["acquis"]} / {ue["ECTS"]["total"]}""",
|
||||
"_css_row_class": "note_bold",
|
||||
"_pdf_row_markup": ["b"],
|
||||
"_pdf_style": [
|
||||
(
|
||||
"LINEBELOW",
|
||||
(0, 0),
|
||||
(-1, 0),
|
||||
self.PDF_LINEWIDTH,
|
||||
self.PDF_LINECOLOR,
|
||||
)
|
||||
],
|
||||
}
|
||||
P.append(t)
|
||||
|
||||
# Global pdf style commands:
|
||||
pdf_style = [
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu:
|
||||
]
|
||||
return col_keys, P, pdf_style, col_widths
|
@ -72,7 +72,7 @@ def bulletin_but_xml_compat(
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
results = bulletin_but.ResultatsSemestreBUT(formsemestre)
|
||||
nb_inscrits = results.get_inscriptions_counts()[scu.INSCRIT]
|
||||
# etat_inscription = etud.etat_inscription(formsemestre.id)
|
||||
# etat_inscription = etud.inscription_etat(formsemestre.id)
|
||||
etat_inscription = results.formsemestre.etuds_inscriptions[etudid].etat
|
||||
if (not formsemestre.bul_hide_xml) or force_publishing:
|
||||
published = 1
|
||||
|
@ -4,12 +4,14 @@
|
||||
et données rattachées (adresses, annotations, ...)
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from functools import cached_property
|
||||
from flask import abort, url_for
|
||||
from flask import g, request
|
||||
import sqlalchemy
|
||||
from sqlalchemy import desc, text
|
||||
|
||||
from app import db
|
||||
from app import db, log
|
||||
from app import models
|
||||
|
||||
from app.scodoc import notesdb as ndb
|
||||
@ -82,6 +84,11 @@ class Identite(db.Model):
|
||||
return scu.suppress_accents(s)
|
||||
return s
|
||||
|
||||
@property
|
||||
def e(self):
|
||||
"terminaison en français: 'ne', '', 'ou '(e)'"
|
||||
return {"M": "", "F": "e"}.get(self.civilite, "(e)")
|
||||
|
||||
def nom_disp(self) -> str:
|
||||
"Nom à afficher"
|
||||
if self.nom_usuel:
|
||||
@ -123,7 +130,7 @@ class Identite(db.Model):
|
||||
|
||||
def get_first_email(self, field="email") -> str:
|
||||
"Le mail associé à la première adrese de l'étudiant, ou None"
|
||||
return self.adresses[0].email or None if self.adresses.count() > 0 else None
|
||||
return getattr(self.adresses[0], field) if self.adresses.count() > 0 else None
|
||||
|
||||
def to_dict_scodoc7(self):
|
||||
"""Représentation dictionnaire,
|
||||
@ -134,7 +141,7 @@ class Identite(db.Model):
|
||||
# ScoDoc7 output_formators: (backward compat)
|
||||
e["etudid"] = self.id
|
||||
e["date_naissance"] = ndb.DateISOtoDMY(e["date_naissance"])
|
||||
e["ne"] = {"M": "", "F": "ne"}.get(self.civilite, "(e)")
|
||||
e["ne"] = self.e
|
||||
return {k: e[k] or "" for k in e} # convert_null_outputs_to_empty
|
||||
|
||||
def to_dict_bul(self, include_urls=True):
|
||||
@ -153,6 +160,7 @@ class Identite(db.Model):
|
||||
"etudid": self.id,
|
||||
"nom": self.nom_disp(),
|
||||
"prenom": self.prenom,
|
||||
"nomprenom": self.nomprenom,
|
||||
}
|
||||
if include_urls:
|
||||
d["fiche_url"] = url_for(
|
||||
@ -172,6 +180,23 @@ class Identite(db.Model):
|
||||
]
|
||||
return r[0] if r else None
|
||||
|
||||
def inscriptions_courantes(self) -> list: # -> list[FormSemestreInscription]:
|
||||
"""Liste des inscriptions à des semestres _courants_
|
||||
(il est rare qu'il y en ai plus d'une, mais c'est possible).
|
||||
Triées par date de début de semestre décroissante (le plus récent en premier).
|
||||
"""
|
||||
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
||||
|
||||
return (
|
||||
FormSemestreInscription.query.join(FormSemestreInscription.formsemestre)
|
||||
.filter(
|
||||
FormSemestreInscription.etudid == self.id,
|
||||
text("date_debut < now() and date_fin > now()"),
|
||||
)
|
||||
.order_by(desc(FormSemestre.date_debut))
|
||||
.all()
|
||||
)
|
||||
|
||||
def inscription_courante_date(self, date_debut, date_fin):
|
||||
"""La première inscription à un formsemestre incluant la
|
||||
période [date_debut, date_fin]
|
||||
@ -183,8 +208,8 @@ class Identite(db.Model):
|
||||
]
|
||||
return r[0] if r else None
|
||||
|
||||
def etat_inscription(self, formsemestre_id):
|
||||
"""etat de l'inscription de cet étudiant au semestre:
|
||||
def inscription_etat(self, formsemestre_id):
|
||||
"""État de l'inscription de cet étudiant au semestre:
|
||||
False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF
|
||||
"""
|
||||
# voir si ce n'est pas trop lent:
|
||||
@ -195,6 +220,110 @@ class Identite(db.Model):
|
||||
return ins.etat
|
||||
return False
|
||||
|
||||
def inscription_descr(self) -> dict:
|
||||
"""Description de l'état d'inscription"""
|
||||
inscription_courante = self.inscription_courante()
|
||||
if inscription_courante:
|
||||
titre_sem = inscription_courante.formsemestre.titre_mois()
|
||||
return {
|
||||
"etat_in_cursem": inscription_courante.etat,
|
||||
"inscription_courante": inscription_courante,
|
||||
"inscription": titre_sem,
|
||||
"inscription_str": "Inscrit en " + titre_sem,
|
||||
"situation": self.descr_situation_etud(),
|
||||
}
|
||||
else:
|
||||
if self.formsemestre_inscriptions:
|
||||
# cherche l'inscription la plus récente:
|
||||
fin_dernier_sem = max(
|
||||
[
|
||||
inscr.formsemestre.date_debut
|
||||
for inscr in self.formsemestre_inscriptions
|
||||
]
|
||||
)
|
||||
if fin_dernier_sem > datetime.date.today():
|
||||
inscription = "futur"
|
||||
situation = "futur élève"
|
||||
else:
|
||||
inscription = "ancien"
|
||||
situation = "ancien élève"
|
||||
else:
|
||||
inscription = ("non inscrit",)
|
||||
situation = inscription
|
||||
return {
|
||||
"etat_in_cursem": "?",
|
||||
"inscription_courante": None,
|
||||
"inscription": inscription,
|
||||
"inscription_str": inscription,
|
||||
"situation": situation,
|
||||
}
|
||||
|
||||
def descr_situation_etud(self) -> str:
|
||||
"""Chaîne décrivant la situation _actuelle_ de l'étudiant.
|
||||
Exemple:
|
||||
"inscrit en BUT R&T semestre 2 FI (Jan 2022 - Jul 2022) le 16/01/2022"
|
||||
ou
|
||||
"non inscrit"
|
||||
"""
|
||||
inscriptions_courantes = self.inscriptions_courantes()
|
||||
if inscriptions_courantes:
|
||||
inscr = inscriptions_courantes[0]
|
||||
if inscr.etat == scu.INSCRIT:
|
||||
situation = f"inscrit{self.e} en {inscr.formsemestre.titre_mois()}"
|
||||
# Cherche la date d'inscription dans scolar_events:
|
||||
events = models.ScolarEvent.query.filter_by(
|
||||
etudid=self.id,
|
||||
formsemestre_id=inscr.formsemestre.id,
|
||||
event_type="INSCRIPTION",
|
||||
).all()
|
||||
if not events:
|
||||
log(
|
||||
f"*** situation inconsistante pour {self} (inscrit mais pas d'event)"
|
||||
)
|
||||
date_ins = "???" # ???
|
||||
else:
|
||||
date_ins = events[0].event_date
|
||||
situation += date_ins.strftime(" le %d/%m/%Y")
|
||||
else:
|
||||
situation = f"démission de {inscr.formsemestre.titre_mois()}"
|
||||
# Cherche la date de demission dans scolar_events:
|
||||
events = models.ScolarEvent.query.filter_by(
|
||||
etudid=self.id,
|
||||
formsemestre_id=inscr.formsemestre.id,
|
||||
event_type="DEMISSION",
|
||||
).all()
|
||||
if not events:
|
||||
log(
|
||||
f"*** situation inconsistante pour {self} (demission mais pas d'event)"
|
||||
)
|
||||
date_dem = "???" # ???
|
||||
else:
|
||||
date_dem = events[0].event_date
|
||||
situation += date_dem.strftime(" le %d/%m/%Y")
|
||||
else:
|
||||
situation = "non inscrit" + self.e
|
||||
|
||||
return situation
|
||||
|
||||
def photo_html(self, title=None, size="small") -> str:
|
||||
"""HTML img tag for the photo, either in small size (h90)
|
||||
or original size (size=="orig")
|
||||
"""
|
||||
from app.scodoc import sco_photos
|
||||
|
||||
# sco_photo traite des dicts:
|
||||
return sco_photos.etud_photo_html(
|
||||
etud=dict(
|
||||
etudid=self.id,
|
||||
code_nip=self.code_nip,
|
||||
nomprenom=self.nomprenom,
|
||||
nom_disp=self.nom_disp(),
|
||||
photo_filename=self.photo_filename,
|
||||
),
|
||||
title=title,
|
||||
size=size,
|
||||
)
|
||||
|
||||
|
||||
def make_etud_args(
|
||||
etudid=None, code_nip=None, use_request=True, raise_exc=False, abort_404=True
|
||||
|
@ -12,7 +12,6 @@ from app import log
|
||||
from app.models import APO_CODE_STR_LEN
|
||||
from app.models import SHORT_STR_LEN
|
||||
from app.models import CODE_STR_LEN
|
||||
from app.models import UniteEns
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.models.ues import UniteEns
|
||||
@ -23,6 +22,7 @@ from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.sco_vdi import ApoEtapeVDI
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_utils import MONTH_NAMES_ABBREV
|
||||
|
||||
|
||||
class FormSemestre(db.Model):
|
||||
@ -122,6 +122,7 @@ class FormSemestre(db.Model):
|
||||
return f"<{self.__class__.__name__} {self.id} {self.titre_num()}>"
|
||||
|
||||
def to_dict(self):
|
||||
"dict (compatible ScoDoc7)"
|
||||
d = dict(self.__dict__)
|
||||
d.pop("_sa_instance_state", None)
|
||||
# ScoDoc7 output_formators: (backward compat)
|
||||
@ -162,8 +163,8 @@ class FormSemestre(db.Model):
|
||||
d["periode"] = 2 # typiquement, début en février: S2, S4...
|
||||
d["titre_num"] = self.titre_num()
|
||||
d["titreannee"] = self.titre_annee()
|
||||
d["mois_debut"] = f"{self.date_debut.month} {self.date_debut.year}"
|
||||
d["mois_fin"] = f"{self.date_fin.month} {self.date_fin.year}"
|
||||
d["mois_debut"] = self.mois_debut()
|
||||
d["mois_fin"] = self.mois_fin()
|
||||
d["titremois"] = "%s %s (%s - %s)" % (
|
||||
d["titre_num"],
|
||||
self.modalite or "",
|
||||
@ -293,6 +294,7 @@ class FormSemestre(db.Model):
|
||||
"""chaîne "J. Dupond, X. Martin"
|
||||
ou "Jacques Dupond, Xavier Martin"
|
||||
"""
|
||||
# was "nomcomplet"
|
||||
if not self.responsables:
|
||||
return ""
|
||||
if abbrev_prenom:
|
||||
@ -304,6 +306,14 @@ class FormSemestre(db.Model):
|
||||
"2021 - 2022"
|
||||
return scu.annee_scolaire_repr(self.date_debut.year, self.date_debut.month)
|
||||
|
||||
def mois_debut(self) -> str:
|
||||
"Oct 2021"
|
||||
return f"{MONTH_NAMES_ABBREV[self.date_debut.month - 1]} {self.date_debut.year}"
|
||||
|
||||
def mois_fin(self) -> str:
|
||||
"Jul 2022"
|
||||
return f"{MONTH_NAMES_ABBREV[self.date_fin.month - 1]} {self.date_debut.year}"
|
||||
|
||||
def session_id(self) -> str:
|
||||
"""identifiant externe de semestre de formation
|
||||
Exemple: RT-DUT-FI-S1-ANNEE
|
||||
@ -364,7 +374,7 @@ class FormSemestre(db.Model):
|
||||
|
||||
def get_abs_count(self, etudid):
|
||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||
tuple (nb abs non justifiées, nb abs justifiées)
|
||||
tuple (nb abs, nb abs justifiées)
|
||||
Utilise un cache.
|
||||
"""
|
||||
from app.scodoc import sco_abs
|
||||
|
@ -6,7 +6,8 @@ import flask_sqlalchemy
|
||||
|
||||
from app import db
|
||||
from app.comp import df_cache
|
||||
from app.models import Identite, Module
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.modules import Module
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import sco_utils as scu
|
||||
|
@ -233,7 +233,10 @@ class GenTable(object):
|
||||
colspan_count -= 1
|
||||
# if colspan_count > 0:
|
||||
# continue # skip cells after a span
|
||||
content = row.get(cid, "") or "" # nota: None converted to ''
|
||||
if pdf_mode:
|
||||
content = row.get(f"_{cid}_pdf", "") or row.get(cid, "") or ""
|
||||
else:
|
||||
content = row.get(cid, "") or "" # nota: None converted to ''
|
||||
colspan = row.get("_%s_colspan" % cid, 0)
|
||||
if colspan > 1:
|
||||
pdf_style_list.append(
|
||||
@ -547,9 +550,16 @@ class GenTable(object):
|
||||
omit_hidden_lines=True,
|
||||
)
|
||||
try:
|
||||
Pt = [
|
||||
[Paragraph(SU(str(x)), CellStyle) for x in line] for line in data_list
|
||||
]
|
||||
Pt = []
|
||||
for line in data_list:
|
||||
Pt.append(
|
||||
[
|
||||
Paragraph(SU(str(x)), CellStyle)
|
||||
if (not isinstance(x, Paragraph))
|
||||
else x
|
||||
for x in line
|
||||
]
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise ScoPDFFormatError(str(exc)) from exc
|
||||
pdf_style_list += self.pdf_table_style
|
||||
|
@ -1037,7 +1037,7 @@ def get_abs_count(etudid, sem):
|
||||
|
||||
def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso):
|
||||
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
||||
tuple (nb abs non justifiées, nb abs justifiées)
|
||||
tuple (nb abs, nb abs justifiées)
|
||||
Utilise un cache.
|
||||
"""
|
||||
key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso
|
||||
|
@ -35,6 +35,7 @@ import datetime
|
||||
|
||||
from flask import g, url_for
|
||||
from flask_mail import Message
|
||||
from app.models.formsemestre import FormSemestre
|
||||
|
||||
import app.scodoc.notesdb as ndb
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -55,27 +56,30 @@ def abs_notify(etudid, date):
|
||||
"""
|
||||
from app.scodoc import sco_abs
|
||||
|
||||
sem = retreive_current_formsemestre(etudid, date)
|
||||
if not sem:
|
||||
formsemestre = retreive_current_formsemestre(etudid, date)
|
||||
if not formsemestre:
|
||||
return # non inscrit a la date, pas de notification
|
||||
|
||||
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
|
||||
do_abs_notify(sem, etudid, date, nbabs, nbabsjust)
|
||||
nbabs, nbabsjust = sco_abs.get_abs_count_in_interval(
|
||||
etudid, formsemestre.date_debut.isoformat(), formsemestre.date_fin.isoformat()
|
||||
)
|
||||
do_abs_notify(formsemestre, etudid, date, nbabs, nbabsjust)
|
||||
|
||||
|
||||
def do_abs_notify(sem, etudid, date, nbabs, nbabsjust):
|
||||
def do_abs_notify(formsemestre: FormSemestre, etudid, date, nbabs, nbabsjust):
|
||||
"""Given new counts of absences, check if notifications are requested and send them."""
|
||||
# prefs fallback to global pref if sem is None:
|
||||
if sem:
|
||||
formsemestre_id = sem["formsemestre_id"]
|
||||
if formsemestre:
|
||||
formsemestre_id = formsemestre.id
|
||||
else:
|
||||
formsemestre_id = None
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id=sem["formsemestre_id"])
|
||||
prefs = sco_preferences.SemPreferences(formsemestre_id=formsemestre_id)
|
||||
|
||||
destinations = abs_notify_get_destinations(
|
||||
sem, prefs, etudid, date, nbabs, nbabsjust
|
||||
formsemestre, prefs, etudid, date, nbabs, nbabsjust
|
||||
)
|
||||
msg = abs_notification_message(sem, prefs, etudid, nbabs, nbabsjust)
|
||||
|
||||
msg = abs_notification_message(formsemestre, prefs, etudid, nbabs, nbabsjust)
|
||||
if not msg:
|
||||
return # abort
|
||||
|
||||
@ -131,19 +135,19 @@ def abs_notify_send(destinations, etudid, msg, nbabs, nbabsjust, formsemestre_id
|
||||
)
|
||||
|
||||
|
||||
def abs_notify_get_destinations(sem, prefs, etudid, date, nbabs, nbabsjust):
|
||||
def abs_notify_get_destinations(
|
||||
formsemestre: FormSemestre, prefs, etudid, date, nbabs, nbabsjust
|
||||
) -> set:
|
||||
"""Returns set of destination emails to be notified"""
|
||||
formsemestre_id = sem["formsemestre_id"]
|
||||
|
||||
destinations = [] # list of email address to notify
|
||||
|
||||
if abs_notify_is_above_threshold(etudid, nbabs, nbabsjust, formsemestre_id):
|
||||
if sem and prefs["abs_notify_respsem"]:
|
||||
if abs_notify_is_above_threshold(etudid, nbabs, nbabsjust, formsemestre.id):
|
||||
if prefs["abs_notify_respsem"]:
|
||||
# notifie chaque responsable du semestre
|
||||
for responsable_id in sem["responsables"]:
|
||||
u = sco_users.user_info(responsable_id)
|
||||
if u["email"]:
|
||||
destinations.append(u["email"])
|
||||
for responsable in formsemestre.responsables:
|
||||
if responsable.email:
|
||||
destinations.append(responsable.email)
|
||||
if prefs["abs_notify_chief"] and prefs["email_chefdpt"]:
|
||||
destinations.append(prefs["email_chefdpt"])
|
||||
if prefs["abs_notify_email"]:
|
||||
@ -156,7 +160,7 @@ def abs_notify_get_destinations(sem, prefs, etudid, date, nbabs, nbabsjust):
|
||||
# Notification (à chaque fois) des resp. de modules ayant des évaluations
|
||||
# à cette date
|
||||
# nb: on pourrait prevoir d'utiliser un autre format de message pour ce cas
|
||||
if sem and prefs["abs_notify_respeval"]:
|
||||
if prefs["abs_notify_respeval"]:
|
||||
mods = mod_with_evals_at_date(date, etudid)
|
||||
for mod in mods:
|
||||
u = sco_users.user_info(mod["responsable_id"])
|
||||
@ -232,7 +236,9 @@ def user_nbdays_since_last_notif(email_addr, etudid):
|
||||
return None
|
||||
|
||||
|
||||
def abs_notification_message(sem, prefs, etudid, nbabs, nbabsjust):
|
||||
def abs_notification_message(
|
||||
formsemestre: FormSemestre, prefs, etudid, nbabs, nbabsjust
|
||||
):
|
||||
"""Mime notification message based on template.
|
||||
returns a Message instance
|
||||
or None if sending should be canceled (empty template).
|
||||
@ -242,7 +248,7 @@ def abs_notification_message(sem, prefs, etudid, nbabs, nbabsjust):
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
|
||||
# Variables accessibles dans les balises du template: %(nom_variable)s :
|
||||
values = sco_bulletins.make_context_dict(sem, etud)
|
||||
values = sco_bulletins.make_context_dict(formsemestre, etud)
|
||||
|
||||
values["nbabs"] = nbabs
|
||||
values["nbabsjust"] = nbabsjust
|
||||
@ -264,9 +270,11 @@ def abs_notification_message(sem, prefs, etudid, nbabs, nbabsjust):
|
||||
return msg
|
||||
|
||||
|
||||
def retreive_current_formsemestre(etudid, cur_date):
|
||||
def retreive_current_formsemestre(etudid: int, cur_date) -> FormSemestre:
|
||||
"""Get formsemestre dans lequel etudid est (ou était) inscrit a la date indiquée
|
||||
date est une chaine au format ISO (yyyy-mm-dd)
|
||||
|
||||
Result: FormSemestre ou None si pas inscrit à la date indiquée
|
||||
"""
|
||||
req = """SELECT i.formsemestre_id
|
||||
FROM notes_formsemestre_inscription i, notes_formsemestre sem
|
||||
@ -278,8 +286,8 @@ def retreive_current_formsemestre(etudid, cur_date):
|
||||
if not r:
|
||||
return None
|
||||
# s'il y a plusieurs semestres, prend le premier (rarissime et non significatif):
|
||||
sem = sco_formsemestre.get_formsemestre(r[0]["formsemestre_id"])
|
||||
return sem
|
||||
formsemestre = FormSemestre.query.get(r[0]["formsemestre_id"])
|
||||
return formsemestre
|
||||
|
||||
|
||||
def mod_with_evals_at_date(date_abs, etudid):
|
||||
|
@ -28,30 +28,19 @@
|
||||
"""Génération des bulletins de notes
|
||||
|
||||
"""
|
||||
from app.models import formsemestre
|
||||
import time
|
||||
import pprint
|
||||
import email
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.base import MIMEBase
|
||||
from email.header import Header
|
||||
from reportlab.lib.colors import Color
|
||||
import urllib
|
||||
import time
|
||||
|
||||
from flask import g, request
|
||||
from flask import url_for
|
||||
from flask import render_template, url_for
|
||||
from flask_login import current_user
|
||||
from flask_mail import Message
|
||||
from app.models.moduleimpls import ModuleImplInscription
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import email
|
||||
from app import log
|
||||
from app.but import bulletin_but
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
from app.models import FormSemestre, Identite, ModuleImplInscription
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||
from app.scodoc import html_sco_header
|
||||
@ -60,9 +49,9 @@ from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_abs_views
|
||||
from app.scodoc import sco_bulletins_generator
|
||||
from app.scodoc import sco_bulletins_json
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_bulletins_xml
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc import sco_evaluation_db
|
||||
from app.scodoc import sco_formations
|
||||
@ -73,7 +62,9 @@ from app.scodoc import sco_photos
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_pvjury
|
||||
from app.scodoc import sco_users
|
||||
from app import email
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import ModuleType, fmt_note
|
||||
import app.scodoc.notesdb as ndb
|
||||
|
||||
# ----- CLASSES DE BULLETINS DE NOTES
|
||||
from app.scodoc import sco_bulletins_standard
|
||||
@ -85,33 +76,20 @@ from app.scodoc import sco_bulletins_legacy
|
||||
from app.scodoc import sco_bulletins_ucac # format expérimental UCAC Cameroun
|
||||
|
||||
|
||||
def make_context_dict(sem, etud):
|
||||
def make_context_dict(formsemestre: FormSemestre, etud: dict) -> dict:
|
||||
"""Construit dictionnaire avec valeurs pour substitution des textes
|
||||
(preferences bul_pdf_*)
|
||||
"""
|
||||
C = sem.copy()
|
||||
C["responsable"] = " ,".join(
|
||||
[
|
||||
sco_users.user_info(responsable_id)["prenomnom"]
|
||||
for responsable_id in sem["responsables"]
|
||||
]
|
||||
)
|
||||
|
||||
annee_debut = sem["date_debut"].split("/")[2]
|
||||
annee_fin = sem["date_fin"].split("/")[2]
|
||||
if annee_debut != annee_fin:
|
||||
annee = "%s - %s" % (annee_debut, annee_fin)
|
||||
else:
|
||||
annee = annee_debut
|
||||
C["anneesem"] = annee
|
||||
C = formsemestre.get_infos_dict()
|
||||
C["responsable"] = formsemestre.responsables_str()
|
||||
C["anneesem"] = C["annee"] # backward compat
|
||||
C.update(etud)
|
||||
# copie preferences
|
||||
# XXX devrait acceder directement à un dict de preferences, à revoir
|
||||
for name in sco_preferences.get_base_preferences().prefs_name:
|
||||
C[name] = sco_preferences.get_preference(name, sem["formsemestre_id"])
|
||||
C[name] = sco_preferences.get_preference(name, formsemestre.id)
|
||||
|
||||
# ajoute groupes et group_0, group_1, ...
|
||||
sco_groups.etud_add_group_infos(etud, sem)
|
||||
sco_groups.etud_add_group_infos(etud, formsemestre.id)
|
||||
C["groupes"] = etud["groupes"]
|
||||
n = 0
|
||||
for partition_id in etud["partitions"]:
|
||||
@ -132,7 +110,8 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
Le contenu du dictionnaire dépend des options (rangs, ...)
|
||||
et de la version choisie (short, long, selectedevals).
|
||||
|
||||
Cette fonction est utilisée pour les bulletins HTML et PDF, mais pas ceux en XML.
|
||||
Cette fonction est utilisée pour les bulletins CLASSIQUES (DUT, ...)
|
||||
en HTML et PDF, mais pas ceux en XML.
|
||||
"""
|
||||
from app.scodoc import sco_abs
|
||||
|
||||
@ -190,39 +169,23 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
show_mention=prefs["bul_show_mention"],
|
||||
)
|
||||
|
||||
if dpv:
|
||||
I["decision_sem"] = dpv["decisions"][0]["decision_sem"]
|
||||
else:
|
||||
I["decision_sem"] = ""
|
||||
I.update(infos)
|
||||
|
||||
I["etud_etat_html"] = _get_etud_etat_html(
|
||||
formsemestre.etuds_inscriptions[etudid].etat
|
||||
)
|
||||
I["etud_etat"] = nt.get_etud_etat(etudid)
|
||||
I["filigranne"] = ""
|
||||
I["filigranne"] = sco_bulletins_pdf.get_filigranne(
|
||||
I["etud_etat"], prefs, decision_sem=I["decision_sem"]
|
||||
)
|
||||
I["demission"] = ""
|
||||
if I["etud_etat"] == "D":
|
||||
if I["etud_etat"] == scu.DEMISSION:
|
||||
I["demission"] = "(Démission)"
|
||||
I["filigranne"] = "Démission"
|
||||
elif I["etud_etat"] == sco_codes_parcours.DEF:
|
||||
I["demission"] = "(Défaillant)"
|
||||
I["filigranne"] = "Défaillant"
|
||||
elif (prefs["bul_show_temporary"] and not I["decision_sem"]) or prefs[
|
||||
"bul_show_temporary_forced"
|
||||
]:
|
||||
I["filigranne"] = prefs["bul_temporary_txt"]
|
||||
|
||||
# --- Appreciations
|
||||
cnx = ndb.GetDBConnexion()
|
||||
apprecs = sco_etud.appreciations_list(
|
||||
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||
)
|
||||
I["appreciations_list"] = apprecs
|
||||
I["appreciations_txt"] = [x["date"] + ": " + x["comment"] for x in apprecs]
|
||||
I["appreciations"] = I[
|
||||
"appreciations_txt"
|
||||
] # deprecated / keep it for backward compat in templates
|
||||
I.update(get_appreciations_list(formsemestre_id, etudid))
|
||||
|
||||
# --- Notes
|
||||
ues = nt.get_ues_stat_dict()
|
||||
@ -316,7 +279,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
else:
|
||||
u["cur_moy_ue_txt"] = "bonus appliqué sur les UEs"
|
||||
else:
|
||||
u["cur_moy_ue_txt"] = "bonus de %.3g points" % x
|
||||
u["cur_moy_ue_txt"] = f"bonus de {fmt_note(x)} points"
|
||||
if nt.bonus_ues is not None:
|
||||
u["cur_moy_ue_txt"] += " (+ues)"
|
||||
u["moy_ue_txt"] = scu.fmt_note(ue_status["moy"])
|
||||
@ -407,13 +370,28 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
||||
I["matieres_modules"].update(_sort_mod_by_matiere(modules, nt, etudid))
|
||||
|
||||
#
|
||||
C = make_context_dict(I["sem"], I["etud"])
|
||||
C = make_context_dict(formsemestre, I["etud"])
|
||||
C.update(I)
|
||||
#
|
||||
# log( 'C = \n%s\n' % pprint.pformat(C) ) # tres pratique pour voir toutes les infos dispo
|
||||
return C
|
||||
|
||||
|
||||
def get_appreciations_list(formsemestre_id: int, etudid: int) -> dict:
|
||||
"""Appréciations pour cet étudiant dans ce semestre"""
|
||||
cnx = ndb.GetDBConnexion()
|
||||
apprecs = sco_etud.appreciations_list(
|
||||
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||
)
|
||||
d = {
|
||||
"appreciations_list": apprecs,
|
||||
"appreciations_txt": [x["date"] + ": " + x["comment"] for x in apprecs],
|
||||
}
|
||||
# deprecated / keep it for backward compat in templates:
|
||||
d["appreciations"] = d["appreciations_txt"]
|
||||
return d
|
||||
|
||||
|
||||
def _get_etud_etat_html(etat: str) -> str:
|
||||
"""chaine html représentant l'état (backward compat sco7)"""
|
||||
if etat == scu.INSCRIT: # "I"
|
||||
@ -691,6 +669,7 @@ def etud_descr_situation_semestre(
|
||||
descr_defaillance : "Défaillant" ou vide si non défaillant.
|
||||
decision_jury : "Validé", "Ajourné", ... (code semestre)
|
||||
descr_decision_jury : "Décision jury: Validé" (une phrase)
|
||||
decision_sem :
|
||||
decisions_ue : noms (acronymes) des UE validées, séparées par des virgules.
|
||||
descr_decisions_ue : ' UE acquises: UE1, UE2', ou vide si pas de dec. ou si pas show_uevalid
|
||||
descr_mention : 'Mention Bien', ou vide si pas de mention ou si pas show_mention
|
||||
@ -700,7 +679,7 @@ def etud_descr_situation_semestre(
|
||||
|
||||
# --- Situation et décisions jury
|
||||
|
||||
# demission/inscription ?
|
||||
# démission/inscription ?
|
||||
events = sco_etud.scolar_events_list(
|
||||
cnx, args={"etudid": etudid, "formsemestre_id": formsemestre_id}
|
||||
)
|
||||
@ -767,11 +746,15 @@ def etud_descr_situation_semestre(
|
||||
infos["situation"] += " " + infos["descr_defaillance"]
|
||||
|
||||
dpv = sco_pvjury.dict_pvjury(formsemestre_id, etudids=[etudid])
|
||||
if dpv:
|
||||
infos["decision_sem"] = dpv["decisions"][0]["decision_sem"]
|
||||
else:
|
||||
infos["decision_sem"] = ""
|
||||
|
||||
if not show_decisions:
|
||||
return infos, dpv
|
||||
|
||||
# Decisions de jury:
|
||||
# Décisions de jury:
|
||||
pv = dpv["decisions"][0]
|
||||
dec = ""
|
||||
if pv["decision_sem_descr"]:
|
||||
@ -810,24 +793,21 @@ def etud_descr_situation_semestre(
|
||||
def formsemestre_bulletinetud(
|
||||
etudid=None,
|
||||
formsemestre_id=None,
|
||||
format="html",
|
||||
format=None,
|
||||
version="long",
|
||||
xml_with_decisions=False,
|
||||
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
||||
prefer_mail_perso=False,
|
||||
):
|
||||
"page bulletin de notes"
|
||||
try:
|
||||
etud = sco_etud.get_etud_info(filled=True)[0]
|
||||
etudid = etud["etudid"]
|
||||
except:
|
||||
sco_etud.log_unknown_etud()
|
||||
raise ScoValueError("étudiant inconnu")
|
||||
# API, donc erreurs admises en ScoValueError
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||
format = format or "html"
|
||||
etud: Identite = Identite.query.get_or_404(etudid)
|
||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||
if not formsemestre:
|
||||
raise ScoValueError(f"semestre {formsemestre_id} inconnu !")
|
||||
|
||||
bulletin = do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre,
|
||||
etudid,
|
||||
format=format,
|
||||
version=version,
|
||||
@ -836,52 +816,22 @@ def formsemestre_bulletinetud(
|
||||
prefer_mail_perso=prefer_mail_perso,
|
||||
)[0]
|
||||
if format not in {"html", "pdfmail"}:
|
||||
filename = scu.bul_filename(sem, etud, format)
|
||||
filename = scu.bul_filename(formsemestre, etud, format)
|
||||
return scu.send_file(bulletin, filename, mime=scu.get_mime_suffix(format)[0])
|
||||
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||
H = [
|
||||
_formsemestre_bulletinetud_header_html(
|
||||
etud, etudid, sem, formsemestre_id, format, version
|
||||
),
|
||||
_formsemestre_bulletinetud_header_html(etud, formsemestre, format, version),
|
||||
bulletin,
|
||||
render_template(
|
||||
"bul_foot.html",
|
||||
etud=etud,
|
||||
formsemestre=formsemestre,
|
||||
inscription_courante=etud.inscription_courante(),
|
||||
inscription_str=etud.inscription_descr()["inscription_str"],
|
||||
),
|
||||
html_sco_header.sco_footer(),
|
||||
]
|
||||
|
||||
H.append("""<p>Situation actuelle: """)
|
||||
if etud["inscription_formsemestre_id"]:
|
||||
H.append(
|
||||
f"""<a class="stdlink" href="{url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=etud["inscription_formsemestre_id"])
|
||||
}">"""
|
||||
)
|
||||
H.append(etud["inscriptionstr"])
|
||||
if etud["inscription_formsemestre_id"]:
|
||||
H.append("""</a>""")
|
||||
H.append("""</p>""")
|
||||
if sem["modalite"] == "EXT":
|
||||
H.append(
|
||||
"""<p><a
|
||||
href="formsemestre_ext_edit_ue_validations?formsemestre_id=%s&etudid=%s"
|
||||
class="stdlink">
|
||||
Editer les validations d'UE dans ce semestre extérieur
|
||||
</a></p>"""
|
||||
% (formsemestre_id, etudid)
|
||||
)
|
||||
# Place du diagramme radar
|
||||
H.append(
|
||||
"""<form id="params">
|
||||
<input type="hidden" name="etudid" id="etudid" value="%s"/>
|
||||
<input type="hidden" name="formsemestre_id" id="formsemestre_id" value="%s"/>
|
||||
</form>"""
|
||||
% (etudid, formsemestre_id)
|
||||
)
|
||||
H.append('<div id="radar_bulletin"></div>')
|
||||
|
||||
# --- Pied de page
|
||||
H.append(html_sco_header.sco_footer())
|
||||
|
||||
return "".join(H)
|
||||
|
||||
|
||||
@ -896,23 +846,24 @@ def can_send_bulletin_by_mail(formsemestre_id):
|
||||
|
||||
|
||||
def do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
etudid,
|
||||
formsemestre: FormSemestre,
|
||||
etudid: int,
|
||||
version="long", # short, long, selectedevals
|
||||
format="html",
|
||||
format=None,
|
||||
nohtml=False,
|
||||
xml_with_decisions=False, # force decisions dans XML
|
||||
force_publishing=False, # force publication meme si semestre non publie sur "portail"
|
||||
prefer_mail_perso=False, # mails envoyes sur adresse perso si non vide
|
||||
xml_with_decisions=False, # force décisions dans XML
|
||||
force_publishing=False, # force publication meme si semestre non publié sur "portail"
|
||||
prefer_mail_perso=False, # mails envoyés sur adresse perso si non vide
|
||||
):
|
||||
"""Génère le bulletin au format demandé.
|
||||
Retourne: (bul, filigranne)
|
||||
où bul est str ou bytes au format demandé (html, pdf, pdfmail, pdfpart, xml, json)
|
||||
et filigranne est un message à placer en "filigranne" (eg "Provisoire").
|
||||
"""
|
||||
format = format or "html"
|
||||
if format == "xml":
|
||||
bul = sco_bulletins_xml.make_xml_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre.id,
|
||||
etudid,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
@ -923,7 +874,7 @@ def do_formsemestre_bulletinetud(
|
||||
|
||||
elif format == "json":
|
||||
bul = sco_bulletins_json.make_json_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre.id,
|
||||
etudid,
|
||||
xml_with_decisions=xml_with_decisions,
|
||||
force_publishing=force_publishing,
|
||||
@ -931,8 +882,13 @@ def do_formsemestre_bulletinetud(
|
||||
)
|
||||
return bul, ""
|
||||
|
||||
I = formsemestre_bulletinetud_dict(formsemestre_id, etudid)
|
||||
etud = I["etud"]
|
||||
if formsemestre.formation.is_apc():
|
||||
etud = Identite.query.get(etudid)
|
||||
r = bulletin_but.BulletinBUT(formsemestre)
|
||||
I = r.bulletin_etud_complet(etud)
|
||||
else:
|
||||
I = formsemestre_bulletinetud_dict(formsemestre.id, etudid)
|
||||
etud = I["etud"]
|
||||
|
||||
if format == "html":
|
||||
htm, _ = sco_bulletins_generator.make_formsemestre_bulletinetud(
|
||||
@ -958,7 +914,7 @@ def do_formsemestre_bulletinetud(
|
||||
elif format == "pdfmail":
|
||||
# format pdfmail: envoie le pdf par mail a l'etud, et affiche le html
|
||||
# check permission
|
||||
if not can_send_bulletin_by_mail(formsemestre_id):
|
||||
if not can_send_bulletin_by_mail(formsemestre.id):
|
||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||
|
||||
if nohtml:
|
||||
@ -987,7 +943,7 @@ def do_formsemestre_bulletinetud(
|
||||
) + htm
|
||||
return h, I["filigranne"]
|
||||
#
|
||||
mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr)
|
||||
mail_bulletin(formsemestre.id, I, pdfdata, filename, recipient_addr)
|
||||
emaillink = '<a class="stdlink" href="mailto:%s">%s</a>' % (
|
||||
recipient_addr,
|
||||
recipient_addr,
|
||||
@ -1055,17 +1011,15 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
|
||||
)
|
||||
|
||||
|
||||
def _formsemestre_bulletinetud_header_html(
|
||||
etud,
|
||||
etudid,
|
||||
sem,
|
||||
formsemestre_id=None,
|
||||
def _formsemestre_bulletinetud_header_html_old_XXX(
|
||||
etud: Identite,
|
||||
formsemestre: FormSemestre,
|
||||
format=None,
|
||||
version=None,
|
||||
):
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Bulletin de %(nomprenom)s" % etud,
|
||||
page_title=f"Bulletin de {etud.nomprenom}",
|
||||
javascripts=[
|
||||
"js/bulletin.js",
|
||||
"libjs/d3.v3.min.js",
|
||||
@ -1073,33 +1027,27 @@ def _formsemestre_bulletinetud_header_html(
|
||||
],
|
||||
cssstyles=["css/radar_bulletin.css"],
|
||||
),
|
||||
"""<table class="bull_head"><tr><td>
|
||||
<h2><a class="discretelink" href="%s">%s</a></h2>
|
||||
"""
|
||||
% (
|
||||
f"""<table class="bull_head"><tr><td>
|
||||
<h2><a class="discretelink" href="{
|
||||
url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"]
|
||||
),
|
||||
etud["nomprenom"],
|
||||
),
|
||||
"""
|
||||
<form name="f" method="GET" action="%s">"""
|
||||
% request.base_url,
|
||||
f"""Bulletin <span class="bull_liensemestre"><a href="{
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||
)}">{etud.nomprenom}</a></h2>
|
||||
|
||||
<form name="f" method="GET" action="{request.base_url}">
|
||||
Bulletin <span class="bull_liensemestre"><a href="{
|
||||
url_for("notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=sem["formsemestre_id"])}
|
||||
">{sem["titremois"]}</a></span>
|
||||
<br/>"""
|
||||
% sem,
|
||||
"""<table><tr>""",
|
||||
"""<td>établi le %s (notes sur 20)</td>""" % time.strftime("%d/%m/%Y à %Hh%M"),
|
||||
"""<td><span class="rightjust">
|
||||
<input type="hidden" name="formsemestre_id" value="%s"></input>"""
|
||||
% formsemestre_id,
|
||||
"""<input type="hidden" name="etudid" value="%s"></input>""" % etudid,
|
||||
"""<input type="hidden" name="format" value="%s"></input>""" % format,
|
||||
"""<select name="version" onchange="document.f.submit()" class="noprint">""",
|
||||
formsemestre_id=formsemestre.id)}
|
||||
">{formsemestre.titre_mois()}</a></span>
|
||||
<br/>
|
||||
<table><tr>
|
||||
<td>établi le {time.strftime("%d/%m/%Y à %Hh%M")} (notes sur 20)</td>
|
||||
<td><span class="rightjust">
|
||||
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}"></input>
|
||||
<input type="hidden" name="etudid" value="{etud.id}"></input>
|
||||
<input type="hidden" name="format" value="{format}"></input>
|
||||
<select name="version" onchange="document.f.submit()" class="noprint">
|
||||
""",
|
||||
]
|
||||
for (v, e) in (
|
||||
("short", "Version courte"),
|
||||
@ -1114,143 +1062,12 @@ def _formsemestre_bulletinetud_header_html(
|
||||
H.append("""</select></td>""")
|
||||
# Menu
|
||||
endpoint = "notes.formsemestre_bulletinetud"
|
||||
|
||||
menuBul = [
|
||||
{
|
||||
"title": "Réglages bulletins",
|
||||
"endpoint": "notes.formsemestre_edit_options",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
# "target_url": url_for(
|
||||
# "notes.formsemestre_bulletinetud",
|
||||
# scodoc_dept=g.scodoc_dept,
|
||||
# formsemestre_id=formsemestre_id,
|
||||
# etudid=etudid,
|
||||
# ),
|
||||
},
|
||||
"enabled": (current_user.id in sem["responsables"])
|
||||
or current_user.has_permission(Permission.ScoImplement),
|
||||
},
|
||||
{
|
||||
"title": 'Version papier (pdf, format "%s")'
|
||||
% sco_bulletins_generator.bulletin_get_class_name_displayed(
|
||||
formsemestre_id
|
||||
),
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"version": version,
|
||||
"format": "pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
"title": "Envoi par mail à %s" % etud["email"],
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"version": version,
|
||||
"format": "pdfmail",
|
||||
},
|
||||
# possible slt si on a un mail...
|
||||
"enabled": etud["email"] and can_send_bulletin_by_mail(formsemestre_id),
|
||||
},
|
||||
{
|
||||
"title": "Envoi par mail à %s (adr. personnelle)" % etud["emailperso"],
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"version": version,
|
||||
"format": "pdfmail",
|
||||
"prefer_mail_perso": 1,
|
||||
},
|
||||
# possible slt si on a un mail...
|
||||
"enabled": etud["emailperso"]
|
||||
and can_send_bulletin_by_mail(formsemestre_id),
|
||||
},
|
||||
{
|
||||
"title": "Version json",
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"version": version,
|
||||
"format": "json",
|
||||
},
|
||||
},
|
||||
{
|
||||
"title": "Version XML",
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
"version": version,
|
||||
"format": "xml",
|
||||
},
|
||||
},
|
||||
{
|
||||
"title": "Ajouter une appréciation",
|
||||
"endpoint": "notes.appreciation_add_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
},
|
||||
"enabled": (
|
||||
(current_user.id in sem["responsables"])
|
||||
or (current_user.has_permission(Permission.ScoEtudInscrit))
|
||||
),
|
||||
},
|
||||
{
|
||||
"title": "Enregistrer un semestre effectué ailleurs",
|
||||
"endpoint": "notes.formsemestre_ext_create_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
},
|
||||
"enabled": current_user.has_permission(Permission.ScoImplement),
|
||||
},
|
||||
{
|
||||
"title": "Enregistrer une validation d'UE antérieure",
|
||||
"endpoint": "notes.formsemestre_validate_previous_ue",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre_id),
|
||||
},
|
||||
{
|
||||
"title": "Enregistrer note d'une UE externe",
|
||||
"endpoint": "notes.external_ue_create_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre_id),
|
||||
},
|
||||
{
|
||||
"title": "Entrer décisions jury",
|
||||
"endpoint": "notes.formsemestre_validation_etud_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre_id),
|
||||
},
|
||||
{
|
||||
"title": "Editer PV jury",
|
||||
"endpoint": "notes.formsemestre_pvjury_pdf",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre_id,
|
||||
"etudid": etudid,
|
||||
},
|
||||
"enabled": True,
|
||||
},
|
||||
]
|
||||
menu_autres_operations = make_menu_autres_operations(
|
||||
formsemestre, etud, endpoint, version
|
||||
)
|
||||
|
||||
H.append("""<td class="bulletin_menubar"><div class="bulletin_menubar">""")
|
||||
H.append(htmlutils.make_menu("Autres opérations", menuBul, alone=True))
|
||||
H.append(menu_autres_operations)
|
||||
H.append("""</div></td>""")
|
||||
H.append(
|
||||
'<td> <a href="%s">%s</a></td>'
|
||||
@ -1258,8 +1075,8 @@ def _formsemestre_bulletinetud_header_html(
|
||||
url_for(
|
||||
"notes.formsemestre_bulletinetud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre_id,
|
||||
etudid=etudid,
|
||||
formsemestre_id=formsemestre.id,
|
||||
etudid=etud.id,
|
||||
format="pdf",
|
||||
version=version,
|
||||
),
|
||||
@ -1272,8 +1089,8 @@ def _formsemestre_bulletinetud_header_html(
|
||||
"""</form></span></td><td class="bull_photo"><a href="%s">%s</a>
|
||||
"""
|
||||
% (
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid),
|
||||
sco_photos.etud_photo_html(etud, title="fiche de " + etud["nom"]),
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id),
|
||||
sco_photos.etud_photo_html(etud, title="fiche de " + etud.nomprenom),
|
||||
)
|
||||
)
|
||||
H.append(
|
||||
@ -1283,3 +1100,177 @@ def _formsemestre_bulletinetud_header_html(
|
||||
)
|
||||
|
||||
return "".join(H)
|
||||
|
||||
|
||||
def make_menu_autres_operations(
|
||||
formsemestre: FormSemestre, etud: Identite, endpoint: str, version: str
|
||||
) -> str:
|
||||
etud_email = etud.get_first_email() or ""
|
||||
etud_perso = etud.get_first_email("emailperso") or ""
|
||||
menu_items = [
|
||||
{
|
||||
"title": "Réglages bulletins",
|
||||
"endpoint": "notes.formsemestre_edit_options",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
# "target_url": url_for(
|
||||
# "notes.formsemestre_bulletinetud",
|
||||
# scodoc_dept=g.scodoc_dept,
|
||||
# formsemestre_id=formsemestre_id,
|
||||
# etudid=etudid,
|
||||
# ),
|
||||
},
|
||||
"enabled": formsemestre.can_be_edited_by(current_user),
|
||||
},
|
||||
{
|
||||
"title": 'Version papier (pdf, format "%s")'
|
||||
% sco_bulletins_generator.bulletin_get_class_name_displayed(
|
||||
formsemestre.id
|
||||
),
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
"title": f"Envoi par mail à {etud_email}",
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "pdfmail",
|
||||
},
|
||||
# possible slt si on a un mail...
|
||||
"enabled": etud_email and can_send_bulletin_by_mail(formsemestre.id),
|
||||
},
|
||||
{
|
||||
"title": f"Envoi par mail à {etud_perso} (adr. personnelle)",
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "pdfmail",
|
||||
"prefer_mail_perso": 1,
|
||||
},
|
||||
# possible slt si on a un mail...
|
||||
"enabled": etud_perso and can_send_bulletin_by_mail(formsemestre.id),
|
||||
},
|
||||
{
|
||||
"title": "Version json",
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "json",
|
||||
},
|
||||
},
|
||||
{
|
||||
"title": "Version XML",
|
||||
"endpoint": endpoint,
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
"version": version,
|
||||
"format": "xml",
|
||||
},
|
||||
},
|
||||
{
|
||||
"title": "Ajouter une appréciation",
|
||||
"endpoint": "notes.appreciation_add_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": (
|
||||
formsemestre.can_be_edited_by(current_user)
|
||||
or current_user.has_permission(Permission.ScoEtudInscrit)
|
||||
),
|
||||
},
|
||||
{
|
||||
"title": "Enregistrer un semestre effectué ailleurs",
|
||||
"endpoint": "notes.formsemestre_ext_create_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": current_user.has_permission(Permission.ScoImplement),
|
||||
},
|
||||
{
|
||||
"title": "Enregistrer une validation d'UE antérieure",
|
||||
"endpoint": "notes.formsemestre_validate_previous_ue",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
|
||||
},
|
||||
{
|
||||
"title": "Enregistrer note d'une UE externe",
|
||||
"endpoint": "notes.external_ue_create_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
|
||||
},
|
||||
{
|
||||
"title": "Entrer décisions jury",
|
||||
"endpoint": "notes.formsemestre_validation_etud_form",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre.id),
|
||||
},
|
||||
{
|
||||
"title": "Éditer PV jury",
|
||||
"endpoint": "notes.formsemestre_pvjury_pdf",
|
||||
"args": {
|
||||
"formsemestre_id": formsemestre.id,
|
||||
"etudid": etud.id,
|
||||
},
|
||||
"enabled": True,
|
||||
},
|
||||
]
|
||||
return htmlutils.make_menu("Autres opérations", menu_items, alone=True)
|
||||
|
||||
|
||||
def _formsemestre_bulletinetud_header_html(
|
||||
etud,
|
||||
formsemestre: FormSemestre,
|
||||
format=None,
|
||||
version=None,
|
||||
):
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title=f"Bulletin de {etud.nomprenom}",
|
||||
javascripts=[
|
||||
"js/bulletin.js",
|
||||
"libjs/d3.v3.min.js",
|
||||
"js/radar_bulletin.js",
|
||||
],
|
||||
cssstyles=["css/radar_bulletin.css"],
|
||||
),
|
||||
render_template(
|
||||
"bul_head.html",
|
||||
etud=etud,
|
||||
format=format,
|
||||
formsemestre=formsemestre,
|
||||
menu_autres_operations=make_menu_autres_operations(
|
||||
etud=etud,
|
||||
formsemestre=formsemestre,
|
||||
endpoint="notes.formsemestre_bulletinetud",
|
||||
version=version,
|
||||
),
|
||||
scu=scu,
|
||||
time=time,
|
||||
version=version,
|
||||
),
|
||||
]
|
||||
return "\n".join(H)
|
||||
|
@ -63,48 +63,14 @@ from app.scodoc import sco_pdf
|
||||
from app.scodoc.sco_pdf import PDFLOCK
|
||||
import sco_version
|
||||
|
||||
# Liste des types des classes de générateurs de bulletins PDF:
|
||||
BULLETIN_CLASSES = collections.OrderedDict()
|
||||
|
||||
|
||||
def register_bulletin_class(klass):
|
||||
BULLETIN_CLASSES[klass.__name__] = klass
|
||||
|
||||
|
||||
def bulletin_class_descriptions():
|
||||
return [x.description for x in BULLETIN_CLASSES.values()]
|
||||
|
||||
|
||||
def bulletin_class_names():
|
||||
return list(BULLETIN_CLASSES.keys())
|
||||
|
||||
|
||||
def bulletin_default_class_name():
|
||||
return bulletin_class_names()[0]
|
||||
|
||||
|
||||
def bulletin_get_class(class_name):
|
||||
return BULLETIN_CLASSES[class_name]
|
||||
|
||||
|
||||
def bulletin_get_class_name_displayed(formsemestre_id):
|
||||
"""Le nom du générateur utilisé, en clair"""
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||
try:
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
return gen_class.description
|
||||
except:
|
||||
return "invalide ! (voir paramètres)"
|
||||
|
||||
|
||||
class BulletinGenerator(object):
|
||||
class BulletinGenerator:
|
||||
"Virtual superclass for PDF bulletin generators" ""
|
||||
# Here some helper methods
|
||||
# see sco_bulletins_standard.BulletinGeneratorStandard subclass for real methods
|
||||
supported_formats = [] # should list supported formats, eg [ 'html', 'pdf' ]
|
||||
description = "superclass for bulletins" # description for user interface
|
||||
list_in_menu = True # la classe doit-elle est montrée dans le menu de config ?
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -151,7 +117,7 @@ class BulletinGenerator(object):
|
||||
def get_filename(self):
|
||||
"""Build a filename to be proposed to the web client"""
|
||||
sem = sco_formsemestre.get_formsemestre(self.infos["formsemestre_id"])
|
||||
return scu.bul_filename(sem, self.infos["etud"], "pdf")
|
||||
return scu.bul_filename_old(sem, self.infos["etud"], "pdf")
|
||||
|
||||
def generate(self, format="", stand_alone=True):
|
||||
"""Return bulletin in specified format"""
|
||||
@ -270,9 +236,14 @@ def make_formsemestre_bulletinetud(
|
||||
|
||||
formsemestre_id = infos["formsemestre_id"]
|
||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||
try:
|
||||
|
||||
gen_class = None
|
||||
if infos.get("type") == "BUT" and format.startswith("pdf"):
|
||||
gen_class = bulletin_get_class(bul_class_name + "BUT")
|
||||
if gen_class is None:
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
except:
|
||||
|
||||
if gen_class is None:
|
||||
raise ValueError(
|
||||
"Type de bulletin PDF invalide (paramètre: %s)" % bul_class_name
|
||||
)
|
||||
@ -313,3 +284,52 @@ def make_formsemestre_bulletinetud(
|
||||
filename = bul_generator.get_filename()
|
||||
|
||||
return data, filename
|
||||
|
||||
|
||||
####
|
||||
|
||||
# Liste des types des classes de générateurs de bulletins PDF:
|
||||
BULLETIN_CLASSES = collections.OrderedDict()
|
||||
|
||||
|
||||
def register_bulletin_class(klass):
|
||||
BULLETIN_CLASSES[klass.__name__] = klass
|
||||
|
||||
|
||||
def bulletin_class_descriptions():
|
||||
return [
|
||||
BULLETIN_CLASSES[class_name].description
|
||||
for class_name in BULLETIN_CLASSES
|
||||
if BULLETIN_CLASSES[class_name].list_in_menu
|
||||
]
|
||||
|
||||
|
||||
def bulletin_class_names() -> list[str]:
|
||||
"Liste les noms des classes de bulletins à présenter à l'utilisateur"
|
||||
return [
|
||||
class_name
|
||||
for class_name in BULLETIN_CLASSES
|
||||
if BULLETIN_CLASSES[class_name].list_in_menu
|
||||
]
|
||||
|
||||
|
||||
def bulletin_default_class_name():
|
||||
return bulletin_class_names()[0]
|
||||
|
||||
|
||||
def bulletin_get_class(class_name: str) -> BulletinGenerator:
|
||||
"""La class de génération de bulletin de ce nom,
|
||||
ou None si pas trouvée
|
||||
"""
|
||||
return BULLETIN_CLASSES.get(class_name)
|
||||
|
||||
|
||||
def bulletin_get_class_name_displayed(formsemestre_id):
|
||||
"""Le nom du générateur utilisé, en clair"""
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
bul_class_name = sco_preferences.get_preference("bul_class_name", formsemestre_id)
|
||||
gen_class = bulletin_get_class(bul_class_name)
|
||||
if gen_class is None:
|
||||
return "invalide ! (voir paramètres)"
|
||||
return gen_class.description
|
||||
|
@ -138,7 +138,7 @@ def formsemestre_bulletinetud_published_dict(
|
||||
if not published:
|
||||
return d # stop !
|
||||
|
||||
etat_inscription = etud.etat_inscription(formsemestre.id)
|
||||
etat_inscription = etud.inscription_etat(formsemestre.id)
|
||||
if etat_inscription != scu.INSCRIT:
|
||||
d.update(dict_decision_jury(etudid, formsemestre_id, with_decisions=True))
|
||||
return d
|
||||
|
@ -61,12 +61,10 @@ from reportlab.platypus.doctemplate import BaseDocTemplate
|
||||
from flask import g, request
|
||||
|
||||
from app import log, ScoValueError
|
||||
from app.comp import res_sem
|
||||
from app.comp.res_common import NotesTableCompat
|
||||
from app.models import FormSemestre
|
||||
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_codes_parcours
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_etud
|
||||
@ -190,7 +188,7 @@ def get_formsemestre_bulletins_pdf(formsemestre_id, version="selectedevals"):
|
||||
i = 1
|
||||
for etud in formsemestre.get_inscrits(include_demdef=True, order=True):
|
||||
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre,
|
||||
etud.id,
|
||||
format="pdfpart",
|
||||
version=version,
|
||||
@ -239,8 +237,9 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
|
||||
filigrannes = {}
|
||||
i = 1
|
||||
for sem in etud["sems"]:
|
||||
formsemestre = FormSemestre.query.get(sem["formsemestre_id"])
|
||||
frag, filigranne = sco_bulletins.do_formsemestre_bulletinetud(
|
||||
sem["formsemestre_id"],
|
||||
formsemestre,
|
||||
etudid,
|
||||
format="pdfpart",
|
||||
version=version,
|
||||
@ -275,3 +274,16 @@ def get_etud_bulletins_pdf(etudid, version="selectedevals"):
|
||||
)
|
||||
|
||||
return pdfdoc, filename
|
||||
|
||||
|
||||
def get_filigranne(etud_etat: str, prefs, decision_sem=None) -> str:
|
||||
"""Texte à placer en "filigranne" sur le bulletin pdf"""
|
||||
if etud_etat == scu.DEMISSION:
|
||||
return "Démission"
|
||||
elif etud_etat == sco_codes_parcours.DEF:
|
||||
return "Défaillant"
|
||||
elif (prefs["bul_show_temporary"] and not decision_sem) or prefs[
|
||||
"bul_show_temporary_forced"
|
||||
]:
|
||||
return prefs["bul_temporary_txt"]
|
||||
return ""
|
||||
|
@ -66,7 +66,8 @@ from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_evaluations
|
||||
from app.scodoc import gen_tables
|
||||
|
||||
# Important: Le nom de la classe ne doit pas changer (bien le choisir), car il sera stocké en base de données (dans les préférences)
|
||||
# Important: Le nom de la classe ne doit pas changer (bien le choisir),
|
||||
# car il sera stocké en base de données (dans les préférences)
|
||||
class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||
description = "standard ScoDoc (version 2011)" # la description doit être courte: elle apparait dans le menu de paramètrage ScoDoc
|
||||
supported_formats = ["html", "pdf"]
|
||||
@ -264,11 +265,11 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||
|
||||
def build_bulletin_table(self):
|
||||
"""Génère la table centrale du bulletin de notes
|
||||
Renvoie: colkeys, P, pdf_style, colWidths
|
||||
- colkeys: nom des colonnes de la table (clés)
|
||||
- table (liste de dicts de chaines de caracteres)
|
||||
- style (commandes table Platypus)
|
||||
- largeurs de colonnes pour PDF
|
||||
Renvoie: col_keys, P, pdf_style, col_widths
|
||||
- col_keys: nom des colonnes de la table (clés)
|
||||
- table: liste de dicts de chaines de caractères
|
||||
- pdf_style: commandes table Platypus
|
||||
- col_widths: largeurs de colonnes pour PDF
|
||||
"""
|
||||
I = self.infos
|
||||
P = [] # elems pour générer table avec gen_table (liste de dicts)
|
||||
@ -287,25 +288,25 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||
with_col_coef = prefs["bul_show_coef"] or prefs["bul_show_ue_coef"]
|
||||
with_col_ects = prefs["bul_show_ects"]
|
||||
|
||||
colkeys = ["titre", "module"] # noms des colonnes à afficher
|
||||
col_keys = ["titre", "module"] # noms des colonnes à afficher
|
||||
if with_col_rang:
|
||||
colkeys += ["rang"]
|
||||
col_keys += ["rang"]
|
||||
if with_col_minmax:
|
||||
colkeys += ["min"]
|
||||
col_keys += ["min"]
|
||||
if with_col_moypromo:
|
||||
colkeys += ["moy"]
|
||||
col_keys += ["moy"]
|
||||
if with_col_minmax:
|
||||
colkeys += ["max"]
|
||||
colkeys += ["note"]
|
||||
col_keys += ["max"]
|
||||
col_keys += ["note"]
|
||||
if with_col_coef:
|
||||
colkeys += ["coef"]
|
||||
col_keys += ["coef"]
|
||||
if with_col_ects:
|
||||
colkeys += ["ects"]
|
||||
col_keys += ["ects"]
|
||||
if with_col_abs:
|
||||
colkeys += ["abs"]
|
||||
col_keys += ["abs"]
|
||||
colidx = {} # { nom_colonne : indice à partir de 0 } (pour styles platypus)
|
||||
i = 0
|
||||
for k in colkeys:
|
||||
for k in col_keys:
|
||||
colidx[k] = i
|
||||
i += 1
|
||||
|
||||
@ -313,7 +314,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||
bul_pdf_mod_colwidth = float(prefs["bul_pdf_mod_colwidth"]) * cm
|
||||
else:
|
||||
bul_pdf_mod_colwidth = None
|
||||
colWidths = {
|
||||
col_widths = {
|
||||
"titre": None,
|
||||
"module": bul_pdf_mod_colwidth,
|
||||
"min": 1.5 * cm,
|
||||
@ -541,7 +542,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
||||
("BOX", (0, 0), (-1, -1), 0.4, blue), # ajoute cadre extérieur bleu:
|
||||
]
|
||||
#
|
||||
return colkeys, P, pdf_style, colWidths
|
||||
return col_keys, P, pdf_style, col_widths
|
||||
|
||||
def _list_modules(
|
||||
self,
|
||||
|
@ -512,8 +512,8 @@ def module_edit(module_id=None):
|
||||
]
|
||||
else:
|
||||
mat_names = ["%s / %s" % (mat.ue.acronyme, mat.titre or "") for mat in matieres]
|
||||
ue_mat_ids = ["%s!%s" % (mat.ue.id, mat.id) for mat in matieres]
|
||||
|
||||
ue_mat_ids = ["%s!%s" % (mat.ue.id, mat.id) for mat in matieres]
|
||||
module["ue_matiere_id"] = "%s!%s" % (module["ue_id"], module["matiere_id"])
|
||||
|
||||
semestres_indices = list(range(1, parcours.NB_SEM + 1))
|
||||
@ -748,8 +748,11 @@ def module_edit(module_id=None):
|
||||
else:
|
||||
# l'UE de rattachement peut changer
|
||||
tf[2]["ue_id"], tf[2]["matiere_id"] = tf[2]["ue_matiere_id"].split("!")
|
||||
x, y = tf[2]["ue_matiere_id"].split("!")
|
||||
tf[2]["ue_id"] = int(x)
|
||||
tf[2]["matiere_id"] = int(y)
|
||||
old_ue_id = a_module.ue.id
|
||||
new_ue_id = int(tf[2]["ue_id"])
|
||||
new_ue_id = tf[2]["ue_id"]
|
||||
if (old_ue_id != new_ue_id) and in_use:
|
||||
new_ue = UniteEns.query.get_or_404(new_ue_id)
|
||||
if new_ue.semestre_idx != a_module.ue.semestre_idx:
|
||||
|
@ -954,13 +954,13 @@ def _ue_table_ues(
|
||||
|
||||
if cur_ue_semestre_id != ue["semestre_id"]:
|
||||
cur_ue_semestre_id = ue["semestre_id"]
|
||||
# if iue > 0:
|
||||
# H.append("</ul>")
|
||||
if ue["semestre_id"] == sco_codes_parcours.UE_SEM_DEFAULT:
|
||||
lab = "Pas d'indication de semestre:"
|
||||
else:
|
||||
lab = "Semestre %s:" % ue["semestre_id"]
|
||||
H.append('<div class="ue_list_tit_sem">%s</div>' % lab)
|
||||
H.append(
|
||||
'<div class="ue_list_div"><div class="ue_list_tit_sem">%s</div>' % lab
|
||||
)
|
||||
H.append('<ul class="notes_ue_list">')
|
||||
H.append('<li class="notes_ue_list">')
|
||||
if iue != 0 and editable:
|
||||
@ -1028,7 +1028,9 @@ def _ue_table_ues(
|
||||
H.append(
|
||||
f"""</ul><ul><li><a href="{url_for('notes.ue_create', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=ue['formation_id'], semestre_idx=ue['semestre_id'])
|
||||
}">Ajouter une UE dans le semestre {ue['semestre_id'] or ''}</a></li></ul>"""
|
||||
}">Ajouter une UE dans le semestre {ue['semestre_id'] or ''}</a></li></ul>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
iue += 1
|
||||
|
||||
|
@ -33,8 +33,7 @@ import os
|
||||
import time
|
||||
from operator import itemgetter
|
||||
|
||||
from flask import url_for, g, request
|
||||
from flask_mail import Message
|
||||
from flask import url_for, g
|
||||
|
||||
from app import email
|
||||
from app import log
|
||||
@ -46,7 +45,6 @@ from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||
from app.scodoc import safehtml
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc.scolog import logdb
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
|
||||
|
||||
def format_etud_ident(etud):
|
||||
@ -860,7 +858,7 @@ def list_scolog(etudid):
|
||||
return cursor.dictfetchall()
|
||||
|
||||
|
||||
def fill_etuds_info(etuds, add_admission=True):
|
||||
def fill_etuds_info(etuds: list[dict], add_admission=True):
|
||||
"""etuds est une liste d'etudiants (mappings)
|
||||
Pour chaque etudiant, ajoute ou formatte les champs
|
||||
-> informations pour fiche etudiant ou listes diverses
|
||||
@ -977,7 +975,10 @@ def etud_inscriptions_infos(etudid: int, ne="") -> dict:
|
||||
|
||||
|
||||
def descr_situation_etud(etudid: int, ne="") -> str:
|
||||
"""chaîne décrivant la situation actuelle de l'étudiant"""
|
||||
"""Chaîne décrivant la situation actuelle de l'étudiant
|
||||
XXX Obsolete, utiliser Identite.descr_situation_etud() dans
|
||||
les nouveaux codes
|
||||
"""
|
||||
from app.scodoc import sco_formsemestre
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
|
@ -180,7 +180,9 @@ def search_etud_in_dept(expnom=""):
|
||||
e["_nomprenom_target"] = target
|
||||
e["inscription_target"] = target
|
||||
e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"])
|
||||
sco_groups.etud_add_group_infos(e, e["cursem"])
|
||||
sco_groups.etud_add_group_infos(
|
||||
e, e["cursem"]["formsemestre_id"] if e["cursem"] else None
|
||||
)
|
||||
|
||||
tab = GenTable(
|
||||
columns_ids=("nomprenom", "code_nip", "inscription", "groupes"),
|
||||
|
@ -31,7 +31,7 @@
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask import url_for
|
||||
from flask import render_template, url_for
|
||||
from flask_login import current_user
|
||||
|
||||
from app import log
|
||||
@ -411,7 +411,7 @@ def formsemestre_status_menubar(sem):
|
||||
"enabled": sco_permissions_check.can_validate_sem(formsemestre_id),
|
||||
},
|
||||
{
|
||||
"title": "Editer les PV et archiver les résultats",
|
||||
"title": "Éditer les PV et archiver les résultats",
|
||||
"endpoint": "notes.formsemestre_archive",
|
||||
"args": {"formsemestre_id": formsemestre_id},
|
||||
"enabled": sco_permissions_check.can_edit_pv(formsemestre_id),
|
||||
@ -445,6 +445,7 @@ def retreive_formsemestre_from_request() -> int:
|
||||
"""Cherche si on a de quoi déduire le semestre affiché à partir des
|
||||
arguments de la requête:
|
||||
formsemestre_id ou moduleimpl ou evaluation ou group_id ou partition_id
|
||||
Returns None si pas défini.
|
||||
"""
|
||||
if request.method == "GET":
|
||||
args = request.args
|
||||
@ -505,34 +506,17 @@ def formsemestre_page_title():
|
||||
return ""
|
||||
try:
|
||||
formsemestre_id = int(formsemestre_id)
|
||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id).copy()
|
||||
formsemestre = FormSemestre.query.get(formsemestre_id)
|
||||
except:
|
||||
log("can't find formsemestre_id %s" % formsemestre_id)
|
||||
return ""
|
||||
|
||||
fill_formsemestre(sem)
|
||||
|
||||
h = f"""<div class="formsemestre_page_title">
|
||||
<div class="infos">
|
||||
<span class="semtitle"><a class="stdlink" title="{sem['session_id']}"
|
||||
href="{url_for('notes.formsemestre_status',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=sem['formsemestre_id'])}"
|
||||
>{sem['titre']}</a><a
|
||||
title="{sem['etape_apo_str']}">{sem['num_sem']}</a>{sem['modalitestr']}</span><span
|
||||
class="dates"><a
|
||||
title="du {sem['date_debut']} au {sem['date_fin']} "
|
||||
>{sem['mois_debut']} - {sem['mois_fin']}</a></span><span
|
||||
class="resp"><a title="{sem['nomcomplet']}">{sem['resp']}</a></span><span
|
||||
class="nbinscrits"><a class="discretelink"
|
||||
href="{url_for("scolar.groups_view",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=sem['formsemestre_id'])}"
|
||||
>{sem['nbinscrits']} inscrits</a></span><span
|
||||
class="lock">{sem['locklink']}</span><span
|
||||
class="eye">{sem['eyelink']}</span>
|
||||
</div>
|
||||
{formsemestre_status_menubar(sem)}
|
||||
</div>
|
||||
"""
|
||||
h = render_template(
|
||||
"formsemestre_page_title.html",
|
||||
formsemestre=formsemestre,
|
||||
scu=scu,
|
||||
sem_menu_bar=formsemestre_status_menubar(formsemestre.to_dict()),
|
||||
)
|
||||
|
||||
return h
|
||||
|
||||
|
@ -321,7 +321,7 @@ def get_group_infos(group_id, etat=None): # was _getlisteetud
|
||||
t["etath"] = t["etat"]
|
||||
# Add membership for all partitions, 'partition_id' : group
|
||||
for etud in members: # long: comment eviter ces boucles ?
|
||||
etud_add_group_infos(etud, sem)
|
||||
etud_add_group_infos(etud, sem["formsemestre_id"])
|
||||
|
||||
if group["group_name"] != None:
|
||||
group_tit = "%s %s" % (group["partition_name"], group["group_name"])
|
||||
@ -413,12 +413,12 @@ def formsemestre_get_etud_groupnames(formsemestre_id, attr="group_name"):
|
||||
return R
|
||||
|
||||
|
||||
def etud_add_group_infos(etud, sem, sep=" "):
|
||||
def etud_add_group_infos(etud, formsemestre_id, sep=" "):
|
||||
"""Add informations on partitions and group memberships to etud (a dict with an etudid)"""
|
||||
etud[
|
||||
"partitions"
|
||||
] = collections.OrderedDict() # partition_id : group + partition_name
|
||||
if not sem:
|
||||
if not formsemestre_id:
|
||||
etud["groupes"] = ""
|
||||
return etud
|
||||
|
||||
@ -430,7 +430,7 @@ def etud_add_group_infos(etud, sem, sep=" "):
|
||||
and p.formsemestre_id = %(formsemestre_id)s
|
||||
ORDER BY p.numero
|
||||
""",
|
||||
{"etudid": etud["etudid"], "formsemestre_id": sem["formsemestre_id"]},
|
||||
{"etudid": etud["etudid"], "formsemestre_id": formsemestre_id},
|
||||
)
|
||||
|
||||
for info in infos:
|
||||
@ -439,13 +439,13 @@ def etud_add_group_infos(etud, sem, sep=" "):
|
||||
|
||||
# resume textuel des groupes:
|
||||
etud["groupes"] = sep.join(
|
||||
[g["group_name"] for g in infos if g["group_name"] != None]
|
||||
[gr["group_name"] for gr in infos if gr["group_name"] is not None]
|
||||
)
|
||||
etud["partitionsgroupes"] = sep.join(
|
||||
[
|
||||
g["partition_name"] + ":" + g["group_name"]
|
||||
for g in infos
|
||||
if g["group_name"] != None
|
||||
gr["partition_name"] + ":" + gr["group_name"]
|
||||
for gr in infos
|
||||
if gr["group_name"] is not None
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -203,7 +203,7 @@ def sco_import_generate_excel_sample(
|
||||
for field in titles:
|
||||
if field == "groupes":
|
||||
sco_groups.etud_add_group_infos(
|
||||
etud, groups_infos.formsemestre, sep=";"
|
||||
etud, groups_infos.formsemestre_id, sep=";"
|
||||
)
|
||||
l.append(etud["partitionsgroupes"])
|
||||
else:
|
||||
|
@ -196,7 +196,10 @@ def do_inscrit(sem, etudids, inscrit_groupes=False):
|
||||
if len(etud["sems"]) < 2:
|
||||
continue
|
||||
prev_formsemestre = etud["sems"][1]
|
||||
sco_groups.etud_add_group_infos(etud, prev_formsemestre)
|
||||
sco_groups.etud_add_group_infos(
|
||||
etud,
|
||||
prev_formsemestre["formsemestre_id"] if prev_formsemestre else None,
|
||||
)
|
||||
|
||||
cursem_groups_by_name = dict(
|
||||
[
|
||||
|
@ -215,7 +215,9 @@ def ficheEtud(etudid=None):
|
||||
info["modifadresse"] = ""
|
||||
|
||||
# Groupes:
|
||||
sco_groups.etud_add_group_infos(info, info["cursem"])
|
||||
sco_groups.etud_add_group_infos(
|
||||
info, info["cursem"]["formsemestre_id"] if info["cursem"] else None
|
||||
)
|
||||
|
||||
# Parcours de l'étudiant
|
||||
if info["sems"]:
|
||||
|
@ -175,7 +175,7 @@ def etud_photo_is_local(etud: dict, size="small"):
|
||||
return photo_pathname(etud["photo_filename"], size=size)
|
||||
|
||||
|
||||
def etud_photo_html(etud=None, etudid=None, title=None, size="small"):
|
||||
def etud_photo_html(etud: dict = None, etudid=None, title=None, size="small"):
|
||||
"""HTML img tag for the photo, either in small size (h90)
|
||||
or original size (size=="orig")
|
||||
"""
|
||||
@ -351,7 +351,8 @@ def copy_portal_photo_to_fs(etud):
|
||||
"""Copy the photo from portal (distant website) to local fs.
|
||||
Returns rel. path or None if copy failed, with a diagnostic message
|
||||
"""
|
||||
sco_etud.format_etud_ident(etud)
|
||||
if "nomprenom" not in etud:
|
||||
sco_etud.format_etud_ident(etud)
|
||||
url = photo_portal_url(etud)
|
||||
if not url:
|
||||
return None, "%(nomprenom)s: pas de code NIP" % etud
|
||||
|
@ -2138,7 +2138,7 @@ class BasePreferences(object):
|
||||
return form
|
||||
|
||||
|
||||
class SemPreferences(object):
|
||||
class SemPreferences:
|
||||
"""Preferences for a formsemestre"""
|
||||
|
||||
def __init__(self, formsemestre_id=None):
|
||||
@ -2294,9 +2294,8 @@ def doc_preferences():
|
||||
return "\n".join([" | ".join(x) for x in L])
|
||||
|
||||
|
||||
def bulletin_option_affichage(formsemestre_id: int) -> dict:
|
||||
def bulletin_option_affichage(formsemestre_id: int, prefs: SemPreferences) -> dict:
|
||||
"dict avec les options d'affichages (préférences) pour ce semestre"
|
||||
prefs = SemPreferences(formsemestre_id)
|
||||
fields = (
|
||||
"bul_show_abs",
|
||||
"bul_show_abs_modules",
|
||||
|
@ -608,7 +608,7 @@ def is_valid_filename(filename):
|
||||
return VALID_EXP.match(filename)
|
||||
|
||||
|
||||
def bul_filename(sem, etud, format):
|
||||
def bul_filename_old(sem: dict, etud: dict, format):
|
||||
"""Build a filename for this bulletin"""
|
||||
dt = time.strftime("%Y-%m-%d")
|
||||
filename = f"bul-{sem['titre_num']}-{dt}-{etud['nom']}.{format}"
|
||||
@ -616,6 +616,14 @@ def bul_filename(sem, etud, format):
|
||||
return filename
|
||||
|
||||
|
||||
def bul_filename(formsemestre, etud, format):
|
||||
"""Build a filename for this bulletin"""
|
||||
dt = time.strftime("%Y-%m-%d")
|
||||
filename = f"bul-{formsemestre.titre_num()}-{dt}-{etud.nom}.{format}"
|
||||
filename = make_filename(filename)
|
||||
return filename
|
||||
|
||||
|
||||
def flash_errors(form):
|
||||
"""Flashes form errors (version sommaire)"""
|
||||
for field, errors in form.errors.items():
|
||||
|
@ -14,16 +14,25 @@
|
||||
}
|
||||
main{
|
||||
--couleurPrincipale: rgb(240,250,255);
|
||||
--couleurFondTitresUE: rgb(206,255,235);
|
||||
--couleurFondTitresRes: rgb(125, 170, 255);
|
||||
--couleurFondTitresSAE: rgb(211, 255, 255);
|
||||
--couleurFondTitresUE: #b6ebff;
|
||||
--couleurFondTitresRes: #f8c844;
|
||||
--couleurFondTitresSAE: #c6ffab;
|
||||
--couleurSecondaire: #fec;
|
||||
--couleurIntense: #c09;
|
||||
--couleurSurlignage: rgba(232, 255, 132, 0.47);
|
||||
--couleurIntense: rgb(4, 16, 159);;
|
||||
--couleurSurlignage: rgba(255, 253, 110, 0.49);
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
display: none;
|
||||
}
|
||||
.releve a, .releve a:visited {
|
||||
color: navy;
|
||||
text-decoration: none;
|
||||
}
|
||||
.releve a:hover {
|
||||
color: red;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.ready .wait{display: none;}
|
||||
.ready main{display: block;}
|
||||
h2{
|
||||
@ -97,7 +106,8 @@ section>div:nth-child(1){
|
||||
.hide_coef .synthese em,
|
||||
.hide_coef .eval>em,
|
||||
.hide_date_inscr .dateInscription,
|
||||
.hide_ects .ects{
|
||||
.hide_ects .ects,
|
||||
.hide_rangs .rang{
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -151,14 +161,19 @@ section>div:nth-child(1){
|
||||
column-gap: 4px;
|
||||
flex: none;
|
||||
}
|
||||
.infoSemestre>div:nth-child(1){
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.infoSemestre>div>div:nth-child(even){
|
||||
text-align: right;
|
||||
}
|
||||
.photo {
|
||||
border: none;
|
||||
margin-left: auto;
|
||||
}
|
||||
.rang{
|
||||
text-decoration: underline var(--couleurIntense);
|
||||
font-weight: bold;
|
||||
}
|
||||
.ue .rang{
|
||||
font-weight: 400;
|
||||
}
|
||||
.decision{
|
||||
margin: 5px 0;
|
||||
@ -186,6 +201,9 @@ section>div:nth-child(1){
|
||||
.synthese h3{
|
||||
background: var(--couleurFondTitresUE);
|
||||
}
|
||||
.synthese .ue>div{
|
||||
text-align: right;
|
||||
}
|
||||
.synthese em,
|
||||
.eval em{
|
||||
opacity: 0.6;
|
||||
@ -206,7 +224,6 @@ section>div:nth-child(1){
|
||||
scroll-margin-top: 60px;
|
||||
}
|
||||
.module, .ue {
|
||||
background: var(--couleurSecondaire);
|
||||
color: #000;
|
||||
padding: 4px 32px;
|
||||
border-radius: 4px;
|
||||
@ -218,6 +235,15 @@ section>div:nth-child(1){
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.ue {
|
||||
background: var(--couleurFondTitresRes);
|
||||
}
|
||||
.module {
|
||||
background: var(--couleurFondTitresRes);
|
||||
}
|
||||
.module h3 {
|
||||
background: var(--couleurFondTitresRes);
|
||||
}
|
||||
.module::before, .ue::before {
|
||||
content:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='26px' height='26px' fill='white'><path d='M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z' /></svg>");
|
||||
width: 26px;
|
||||
@ -308,6 +334,14 @@ h3{
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
section{
|
||||
padding: 16px;
|
||||
}
|
||||
.syntheseModule, .eval {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
/*.absences{
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
|
@ -1702,7 +1702,7 @@ ul.notes_ue_list {
|
||||
margin-top: 4px;
|
||||
margin-right: 1em;
|
||||
margin-left: 1em;
|
||||
padding-top: 1em;
|
||||
/* padding-top: 1em; */
|
||||
padding-bottom: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
@ -1767,9 +1767,25 @@ ul.notes_module_list {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
div.ue_list_div {
|
||||
border: 3px solid rgb(35, 0, 160);
|
||||
padding-left: 5px;
|
||||
padding-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
div.ue_list_tit_sem {
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
color: orangered;
|
||||
display: list-item; /* This has to be "list-item" */
|
||||
list-style-type: disc; /* See https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type */
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
input.sco_tag_checkbox {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.notes_ue_list a.stdlink {
|
||||
@ -1947,7 +1963,20 @@ table.notes_recapcomplet a:hover {
|
||||
div.notes_bulletin {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
div.bull_head {
|
||||
display: grid;
|
||||
justify-content: space-between;
|
||||
grid-template-columns: auto auto;
|
||||
}
|
||||
div.bull_photo {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
span.bulletin_menubar_but {
|
||||
display: inline-block;
|
||||
margin-left: 2em;
|
||||
margin-right: 2em;
|
||||
}
|
||||
table.notes_bulletin {
|
||||
border-collapse: collapse;
|
||||
border: 2px solid rgb(100,100,240);
|
||||
@ -2087,12 +2116,6 @@ a.bull_link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table.bull_head {
|
||||
width: 100%;
|
||||
}
|
||||
td.bull_photo {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.bulletin_menubar {
|
||||
padding-left: 25px;
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 34 KiB |
@ -41,7 +41,7 @@ class releveBUT extends HTMLElement {
|
||||
}
|
||||
|
||||
set showData(data) {
|
||||
this.showInformations(data);
|
||||
// this.showInformations(data);
|
||||
this.showSemestre(data);
|
||||
this.showSynthese(data);
|
||||
this.showEvaluations(data);
|
||||
@ -68,13 +68,7 @@ class releveBUT extends HTMLElement {
|
||||
<div>
|
||||
<div class="wait"></div>
|
||||
<main class="releve">
|
||||
<!--------------------------->
|
||||
<!-- Info. étudiant -->
|
||||
<!--------------------------->
|
||||
<section class=etudiant>
|
||||
<img class=studentPic src="" alt="Photo de l'étudiant" width=100 height=120>
|
||||
<div class=infoEtudiant></div>
|
||||
</section>
|
||||
|
||||
|
||||
<!--------------------------------------------------------------------------------------->
|
||||
<!-- Zone spéciale pour que les IUT puisse ajouter des infos locales sur la passerelle -->
|
||||
@ -85,8 +79,8 @@ class releveBUT extends HTMLElement {
|
||||
<!-- Semestre -->
|
||||
<!--------------------------->
|
||||
<section>
|
||||
<h2>Semestre </h2>
|
||||
<div class=flex>
|
||||
<h2 id="identite_etudiant"></h2>
|
||||
<div>
|
||||
<div class=infoSemestre></div>
|
||||
<div>
|
||||
<div class=decision></div>
|
||||
@ -103,7 +97,7 @@ class releveBUT extends HTMLElement {
|
||||
<section>
|
||||
<div>
|
||||
<div>
|
||||
<h2>Synthèse</h2>
|
||||
<h2>Unités d'enseignement</h2>
|
||||
<em>La moyenne des ressources dans une UE dépend des poids donnés aux évaluations.</em>
|
||||
</div>
|
||||
<div class=CTA_Liste>
|
||||
@ -132,7 +126,7 @@ class releveBUT extends HTMLElement {
|
||||
|
||||
<section>
|
||||
<div>
|
||||
<h2>SAÉ</h2>
|
||||
<h2>Situations d'apprentissage et d'évaluation (SAÉ)</h2>
|
||||
<div class=CTA_Liste>
|
||||
Liste <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 15l-6-6-6 6" />
|
||||
@ -198,7 +192,8 @@ class releveBUT extends HTMLElement {
|
||||
/* Information sur le semestre */
|
||||
/*******************************/
|
||||
showSemestre(data) {
|
||||
this.shadow.querySelector("h2").innerHTML += data.semestre.numero;
|
||||
|
||||
this.shadow.querySelector("#identite_etudiant").innerHTML = ` ${data.etudiant.nomprenom} `;
|
||||
this.shadow.querySelector(".dateInscription").innerHTML += this.ISOToDate(data.semestre.inscription);
|
||||
let output = `
|
||||
<div>
|
||||
@ -212,7 +207,9 @@ class releveBUT extends HTMLElement {
|
||||
<div class=enteteSemestre>Absences</div>
|
||||
<div class=enteteSemestre>N.J. ${data.semestre.absences?.injustifie ?? "-"}</div>
|
||||
<div style="grid-column: 2">Total ${data.semestre.absences?.total ?? "-"}</div>
|
||||
</div>`;
|
||||
</div>
|
||||
<a class=photo href="${data.etudiant.fiche_url}"><img src="${data.etudiant.photo_url || "default_Student.svg"}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0"></a>
|
||||
`;
|
||||
/*${data.semestre.groupes.map(groupe => {
|
||||
return `
|
||||
<div>
|
||||
@ -254,6 +251,7 @@ class releveBUT extends HTMLElement {
|
||||
</h3>
|
||||
<div>
|
||||
<div class=moyenne>Moyenne : ${dataUE.moyenne?.value || "-"}</div>
|
||||
<div class=rang>Rang : ${dataUE.moyenne?.rang} / ${dataUE.moyenne?.total}</div>
|
||||
<div class=info>
|
||||
Bonus : ${dataUE.bonus || 0} -
|
||||
Malus : ${dataUE.malus || 0}
|
||||
|
34
app/templates/bul_foot.html
Normal file
34
app/templates/bul_foot.html
Normal file
@ -0,0 +1,34 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
{# Pied des bulletins HTML #}
|
||||
|
||||
<p>Situation actuelle:
|
||||
{% if inscription_courante %}
|
||||
<a class="stdlink" href="{{url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=inscription_courante.formsemestre_id)
|
||||
}}">{{inscription_str}}</a>
|
||||
{% else %}
|
||||
{{inscription_str}}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% if formsemestre.modalite == "EXT" %}
|
||||
<p><a href="{{
|
||||
url_for('notes.formsemestre_ext_edit_ue_validations',
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
etudid=etud.id)}}"
|
||||
class="stdlink">
|
||||
Éditer les validations d'UE dans ce semestre extérieur
|
||||
</a></p>
|
||||
{% endif %}
|
||||
|
||||
{# Place du diagramme radar #}
|
||||
<form id="params">
|
||||
<input type="hidden" name="etudid" id="etudid" value="{{etud.id}}"/>
|
||||
<input type="hidden" name="formsemestre_id" id="formsemestre_id" value="{{formsemestre.id}}"/>
|
||||
</form>
|
||||
<div id="radar_bulletin"></div>
|
||||
|
||||
|
57
app/templates/bul_head.html
Normal file
57
app/templates/bul_head.html
Normal file
@ -0,0 +1,57 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
{# L'en-tête des bulletins HTML #}
|
||||
{# was _formsemestre_bulletinetud_header_html #}
|
||||
|
||||
<div class="bull_head">
|
||||
<div class="bull_head_text">
|
||||
{% if not is_apc %}
|
||||
<h2><a class="discretelink" href="{{
|
||||
url_for(
|
||||
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid,
|
||||
)}}">{{etud.nomprenom}}</a></h2>
|
||||
{% endif %}
|
||||
<form name="f" method="GET" action="{{request.base_url}}">
|
||||
<input type="hidden" name="formsemestre_id" value="{{formsemestre.id}}"></input>
|
||||
<input type="hidden" name="etudid" value="{{etud.id}}"></input>
|
||||
<input type="hidden" name="format" value="{{format}}"></input>
|
||||
Bulletin
|
||||
<span class="bull_liensemestre"><a href="{{
|
||||
url_for("notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id)}}">{{formsemestre.titre_mois()
|
||||
}}</a></span>
|
||||
|
||||
<div>
|
||||
<em>établi le {{time.strftime("%d/%m/%Y à %Hh%M")}} (notes sur 20)</em>
|
||||
<span class="rightjust">
|
||||
<select name="version" onchange="document.f.submit()" class="noprint">
|
||||
{% for (v, e) in (
|
||||
("short", "Version courte"),
|
||||
("selectedevals", "Version intermédiaire"),
|
||||
("long", "Version complète"),
|
||||
) %}
|
||||
<option value="{{v}}" {% if (v == version) %}selected{% endif %}>{{e}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
<span class="bulletin_menubar">
|
||||
<span class="bulletin_menubar_but">{{menu_autres_operations|safe}}</span>
|
||||
<a href="{{url_for(
|
||||
'notes.formsemestre_bulletinetud',
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
etudid=etud.id,
|
||||
format='pdf',
|
||||
version=version,
|
||||
)}}">{{scu.ICON_PDF|safe}}</a>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% if not is_apc %}
|
||||
<div class="bull_photo"><a href="{{
|
||||
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||
}}">{{etud.photo_html(title="fiche de " + etud["nom"])|safe}}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
@ -7,8 +7,13 @@
|
||||
|
||||
{% block app_content %}
|
||||
|
||||
{% include 'bul_head.html' %}
|
||||
|
||||
<releve-but></releve-but>
|
||||
<script src="/ScoDoc/static/js/releve-but.js"></script>
|
||||
|
||||
{% include 'bul_foot.html' %}
|
||||
|
||||
<script>
|
||||
let dataSrc = "{{bul_url|safe}}";
|
||||
fetch(dataSrc)
|
||||
|
50
app/templates/formsemestre_page_title.html
Normal file
50
app/templates/formsemestre_page_title.html
Normal file
@ -0,0 +1,50 @@
|
||||
{# -*- mode: jinja-html -*- #}
|
||||
{# Element HTML decrivant un semestre (barre de menu et infos) #}
|
||||
{# was formsemestre_page_title #}
|
||||
|
||||
<div class="formsemestre_page_title">
|
||||
<div class="infos">
|
||||
<span class="semtitle"><a class="stdlink"
|
||||
title="{{formsemestre.session_id}}"
|
||||
href="{{url_for('notes.formsemestre_status',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)}}"
|
||||
>TATO {{formsemestre.titre}}</a>
|
||||
{%- if formsemestre.semestre_id != -1 -%}
|
||||
<a
|
||||
title="{{formsemestre.etapes_apo_str()
|
||||
}}">, {{
|
||||
formsemestre.formation.get_parcours().SESSION_NAME}}
|
||||
{{formsemestre.semestre_id}}</a>
|
||||
{%- endif -%}
|
||||
{%- if formsemestre.modalite %} en {{formsemestre.modalite}}
|
||||
{%- endif %}</span><span
|
||||
class="dates"><a
|
||||
title="du {{formsemestre.date_debut.strftime('%d/%m/%Y')}}
|
||||
au {{formsemestre.date_fin.strftime('%d/%m/%Y')}} "
|
||||
>{{formsemestre.mois_debut()}} - {{formsemestre.mois_fin()}}</a></span><span
|
||||
class="resp"><a title="{{formsemestre.responsables_str(abbrev_prenom=False)}}">{{formsemestre.responsables_str()}}</a></span><span
|
||||
class="nbinscrits"><a class="discretelink"
|
||||
href="{{url_for('scolar.groups_view',
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
|
||||
}}"
|
||||
>{{formsemestre.etuds_inscriptions|length}} inscrits</a></span><span
|
||||
class="lock">
|
||||
{%-if formsemestre.etat -%}
|
||||
<a href="{{ url_for( 'notes.formsemestre_change_lock',
|
||||
scodoc_dept=scodoc_dept, formsemestre_id=formsemestre.id )}}">{{
|
||||
scu.icontag("lock_img", border="0", title="Semestre verrouillé")|safe
|
||||
}}</a>
|
||||
{%- endif -%}
|
||||
</span><span class="eye"><a href="{{
|
||||
url_for('notes.formsemestre_change_publication_bul',
|
||||
scodoc_dept=scodoc_dept, formsemestre_id=formsemestre.id )
|
||||
}}">{%-
|
||||
if formsemestre.bul_hide_xml -%}}
|
||||
{{scu.icontag("hide_img", border="0", title="Bulletins NON publiés")|safe}}
|
||||
{%- else -%}
|
||||
{{scu.icontag("eye_img", border="0", title="Bulletins publiés")|safe}}
|
||||
{%- endif -%}
|
||||
</a></span>
|
||||
</div>
|
||||
{{sem_menu_bar|safe}}
|
||||
</div>
|
@ -50,27 +50,29 @@ def close_dept_db_connection(arg):
|
||||
class ScoData:
|
||||
"""Classe utilisée pour passer des valeurs aux vues (templates)"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, etud=None, formsemestre=None):
|
||||
# Champs utilisés par toutes les pages ScoDoc (sidebar, en-tête)
|
||||
self.Permission = Permission
|
||||
self.scu = scu
|
||||
self.SCOVERSION = sco_version.SCOVERSION
|
||||
# -- Informations étudiant courant, si sélectionné:
|
||||
etudid = g.get("etudid", None)
|
||||
if not etudid:
|
||||
if request.method == "GET":
|
||||
etudid = request.args.get("etudid", None)
|
||||
elif request.method == "POST":
|
||||
etudid = request.form.get("etudid", None)
|
||||
|
||||
if etudid:
|
||||
if etud is None:
|
||||
etudid = g.get("etudid", None)
|
||||
if etudid is None:
|
||||
if request.method == "GET":
|
||||
etudid = request.args.get("etudid", None)
|
||||
elif request.method == "POST":
|
||||
etudid = request.form.get("etudid", None)
|
||||
if etudid is not None:
|
||||
etud = Identite.query.get_or_404(etudid)
|
||||
self.etud = etud
|
||||
if etud is not None:
|
||||
# Infos sur l'étudiant courant
|
||||
self.etud = Identite.query.get_or_404(etudid)
|
||||
ins = self.etud.inscription_courante()
|
||||
if ins:
|
||||
self.etud_cur_sem = ins.formsemestre
|
||||
self.nbabs, self.nbabsjust = sco_abs.get_abs_count_in_interval(
|
||||
etudid,
|
||||
etud.id,
|
||||
self.etud_cur_sem.date_debut.isoformat(),
|
||||
self.etud_cur_sem.date_fin.isoformat(),
|
||||
)
|
||||
@ -80,17 +82,22 @@ class ScoData:
|
||||
else:
|
||||
self.etud = None
|
||||
# --- Informations sur semestre courant, si sélectionné
|
||||
formsemestre_id = sco_formsemestre_status.retreive_formsemestre_from_request()
|
||||
if formsemestre_id is None:
|
||||
if formsemestre is None:
|
||||
formsemestre_id = (
|
||||
sco_formsemestre_status.retreive_formsemestre_from_request()
|
||||
)
|
||||
if formsemestre_id is not None:
|
||||
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
if formsemestre is None:
|
||||
self.sem = None
|
||||
self.sem_menu_bar = None
|
||||
else:
|
||||
self.sem = FormSemestre.query.get_or_404(formsemestre_id)
|
||||
self.sem = formsemestre
|
||||
self.sem_menu_bar = sco_formsemestre_status.formsemestre_status_menubar(
|
||||
self.sem.to_dict()
|
||||
)
|
||||
# --- Préférences
|
||||
self.prefs = sco_preferences.SemPreferences(formsemestre_id)
|
||||
self.prefs = sco_preferences.SemPreferences(formsemestre.id)
|
||||
|
||||
|
||||
from app.views import scodoc, notes, scolar, absences, users, pn_modules, refcomp
|
||||
|
@ -32,6 +32,7 @@ Emmanuel Viennet, 2021
|
||||
"""
|
||||
|
||||
from operator import itemgetter
|
||||
import time
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import flask
|
||||
@ -276,7 +277,7 @@ sco_publish(
|
||||
def formsemestre_bulletinetud(
|
||||
etudid=None,
|
||||
formsemestre_id=None,
|
||||
format="html",
|
||||
format=None,
|
||||
version="long",
|
||||
xml_with_decisions=False,
|
||||
force_publishing=False,
|
||||
@ -284,6 +285,7 @@ def formsemestre_bulletinetud(
|
||||
code_nip=None,
|
||||
code_ine=None,
|
||||
):
|
||||
format = format or "html"
|
||||
if not formsemestre_id:
|
||||
flask.abort(404, "argument manquant: formsemestre_id")
|
||||
if not isinstance(formsemestre_id, int):
|
||||
@ -311,12 +313,16 @@ def formsemestre_bulletinetud(
|
||||
if format == "json":
|
||||
r = bulletin_but.BulletinBUT(formsemestre)
|
||||
return jsonify(
|
||||
r.bulletin_etud(etud, formsemestre, force_publishing=force_publishing)
|
||||
r.bulletin_etud(
|
||||
etud,
|
||||
formsemestre,
|
||||
force_publishing=force_publishing,
|
||||
version=version,
|
||||
)
|
||||
)
|
||||
elif format == "html":
|
||||
return render_template(
|
||||
"but/bulletin.html",
|
||||
title=f"Bul. {etud.nom} - BUT",
|
||||
bul_url=url_for(
|
||||
"notes.formsemestre_bulletinetud",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
@ -324,8 +330,21 @@ def formsemestre_bulletinetud(
|
||||
etudid=etudid,
|
||||
format="json",
|
||||
force_publishing=1, # pour ScoDoc lui même
|
||||
version=version,
|
||||
),
|
||||
sco=ScoData(),
|
||||
etud=etud,
|
||||
formsemestre=formsemestre,
|
||||
inscription_courante=etud.inscription_courante(),
|
||||
inscription_str=etud.inscription_descr()["inscription_str"],
|
||||
is_apc=formsemestre.formation.is_apc(),
|
||||
menu_autres_operations=sco_bulletins.make_menu_autres_operations(
|
||||
formsemestre, etud, "notes.formsemestre_bulletinetud", version
|
||||
),
|
||||
sco=ScoData(etud=etud),
|
||||
scu=scu,
|
||||
time=time,
|
||||
title=f"Bul. {etud.nom} - BUT",
|
||||
version=version,
|
||||
)
|
||||
|
||||
if not (etudid or code_nip or code_ine):
|
||||
@ -1929,7 +1948,7 @@ def formsemestre_bulletins_mailetuds(
|
||||
nb_send = 0
|
||||
for etudid in etudids:
|
||||
h, _ = sco_bulletins.do_formsemestre_bulletinetud(
|
||||
formsemestre_id,
|
||||
formsemestre,
|
||||
etudid,
|
||||
version=version,
|
||||
prefer_mail_perso=prefer_mail_perso,
|
||||
|
@ -513,7 +513,7 @@ def etud_info(etudid=None, format="xml"):
|
||||
|
||||
sem = etud["cursem"]
|
||||
if sem:
|
||||
sco_groups.etud_add_group_infos(etud, sem)
|
||||
sco_groups.etud_add_group_infos(etud, sem["formsemestre_id"] if sem else None)
|
||||
d["insemestre"] = [
|
||||
{
|
||||
"current": "1",
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.1.72"
|
||||
SCOVERSION = "9.2a-72"
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
15
scodoc.py
15
scodoc.py
@ -33,6 +33,7 @@ from app.models.evaluations import Evaluation
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.views import notes, scolar
|
||||
import tools
|
||||
from tools.fakedatabase import create_test_api_database
|
||||
|
||||
from config import RunningConfig
|
||||
|
||||
@ -84,6 +85,7 @@ def make_shell_context():
|
||||
|
||||
|
||||
# ctx.push()
|
||||
# admin = User.query.filter_by(user_name="admin").first()
|
||||
# login_user(admin)
|
||||
|
||||
|
||||
@ -492,6 +494,19 @@ def clear_cache(sanitize): # clear-cache
|
||||
formation.sanitize_old_formation()
|
||||
|
||||
|
||||
@app.cli.command()
|
||||
def init_test_database():
|
||||
"""Initialise les objets en base pour les tests API
|
||||
(à appliquer sur SCODOC_TEST ou SCODOC_DEV)
|
||||
"""
|
||||
click.echo("Initialisation base de test API...")
|
||||
# import app as mapp # le package app
|
||||
|
||||
ctx = app.test_request_context()
|
||||
ctx.push()
|
||||
create_test_api_database.init_test_database()
|
||||
|
||||
|
||||
def recursive_help(cmd, parent=None):
|
||||
ctx = click.core.Context(cmd, info_name=cmd.name, parent=parent)
|
||||
print(cmd.get_help(ctx))
|
||||
|
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<formation id="220" titre="BUT R&amp;T" version="1" formation_code="V1RET" dept_id="5" acronyme="BUT R&amp;T" titre_officiel="Bachelor technologique réseaux et télécommunications" type_parcours="700" formation_id="220">
|
||||
<ue acronyme="RT1.1" numero="1" titre="Administrer les réseaux et l’Internet" type="0" ue_code="UCOD11" ects="12.0" is_external="0" code_apogee="" coefficient="0.0" semestre_idx="1" color="#B80004" reference="1896">
|
||||
<matiere titre="Administrer les réseaux et l’Internet" numero="1">
|
||||
<module titre="Initiation aux réseaux informatiques" abbrev="Init aux réseaux informatiques" code="R101" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="10" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1896" coef="12.0"/>
|
||||
<coefficients ue_reference="1897" coef="4.0"/>
|
||||
<coefficients ue_reference="1898" coef="4.0"/>
|
||||
</module>
|
||||
<module titre="Se sensibiliser à l&apos;hygiène informatique et à la cybersécurité" abbrev="Hygiène informatique" code="SAE11" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="10" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="1896" coef="16.0"/>
|
||||
</module>
|
||||
<module titre="Principe et architecture des réseaux" abbrev="" code="R102" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="20" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1896" coef="12.0"/>
|
||||
</module>
|
||||
<module titre="Réseaux locaux et équipements actifs" abbrev="Réseaux locaux" code="R103" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="30" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1896" coef="8.0"/>
|
||||
<coefficients ue_reference="1897" coef="4.0"/>
|
||||
</module>
|
||||
<module titre="Fondamentaux des systèmes électroniques" abbrev="" code="R104" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="40" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1896" coef="8.0"/>
|
||||
<coefficients ue_reference="1897" coef="5.0"/>
|
||||
</module>
|
||||
<module titre="Architecture des systèmes numériques et informatiques" abbrev="" code="R106" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="60" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1896" coef="10.0"/>
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue acronyme="RT2.1" numero="2" titre="Connecter les entreprises et les usagers" type="0" ue_code="UCOD12" ects="8.0" is_external="0" code_apogee="" coefficient="0.0" semestre_idx="1" color="#F97B3D" reference="1897">
|
||||
<matiere titre="Connecter les entreprises et les usagers" numero="1">
|
||||
<module titre="S&apos;initier aux réseaux informatiques" abbrev="" code="SAE12" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="20" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="1896" coef="33.0"/>
|
||||
</module>
|
||||
<module titre="Découvrir un dispositif de tranmission" abbrev="" code="SAE13" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="30" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="1897" coef="33.0"/>
|
||||
</module>
|
||||
<module titre="Support de transmission pour les réseaux locaux" abbrev="Support de transmission" code="R105" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="50" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1897" coef="5.0"/>
|
||||
</module>
|
||||
<module titre="Anglais général et init vocabulaire technique" abbrev="" code="R110" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="100" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1896" coef="3.0"/>
|
||||
<coefficients ue_reference="1897" coef="5.0"/>
|
||||
<coefficients ue_reference="1898" coef="5.0"/>
|
||||
</module>
|
||||
<module titre="Expression-culture-Communication Pro." abbrev="" code="R111" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="110" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1896" coef="3.0"/>
|
||||
<coefficients ue_reference="1897" coef="5.0"/>
|
||||
<coefficients ue_reference="1898" coef="4.0"/>
|
||||
</module>
|
||||
<module titre="Mathématiques du signal" abbrev="" code="R113" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="130" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1896" coef="5.0"/>
|
||||
<coefficients ue_reference="1897" coef="8.0"/>
|
||||
</module>
|
||||
<module titre="Mathématiques des transmissions" abbrev="" code="R114" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="140" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1896" coef="4.0"/>
|
||||
<coefficients ue_reference="1897" coef="8.0"/>
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
<ue acronyme="RT3.1" numero="3" titre="Créer des outils et applications informatiques pour les R&amp;T" type="0" ue_code="UCOD13" ects="10.0" is_external="0" code_apogee="" coefficient="0.0" semestre_idx="1" color="#FEB40B" reference="1898">
|
||||
<matiere titre="Créer des outils et applications informatiques pour les R&amp;T" numero="1">
|
||||
<module titre="Se présenter sur Internet" abbrev="" code="SAE14" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="40" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="1898" coef="16.0"/>
|
||||
</module>
|
||||
<module titre="Traiter des données" abbrev="" code="SAE15" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="50" code_apogee="" module_type="3">
|
||||
<coefficients ue_reference="1898" coef="26.0"/>
|
||||
</module>
|
||||
<module titre="Portofolio" abbrev="" code="SAE16" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="60" code_apogee="" module_type="3"/>
|
||||
<module titre="Fondamentaux de la programmation" abbrev="" code="R107" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="70" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1898" coef="22.0"/>
|
||||
</module>
|
||||
<module titre="Base des systèmes d&apos;exploitation" abbrev="" code="R108" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="80" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1896" coef="6.0"/>
|
||||
<coefficients ue_reference="1898" coef="7.0"/>
|
||||
</module>
|
||||
<module titre="Introduction aux technologies Web" abbrev="" code="R109" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="90" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1898" coef="4.0"/>
|
||||
</module>
|
||||
<module titre="PPP" abbrev="" code="R112" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="120" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1896" coef="2.0"/>
|
||||
<coefficients ue_reference="1897" coef="3.0"/>
|
||||
<coefficients ue_reference="1898" coef="4.0"/>
|
||||
</module>
|
||||
<module titre="Gestion de projets" abbrev="" code="R115" heures_cours="0.0" heures_td="0.0" heures_tp="0.0" coefficient="1.0" ects="" semestre_id="1" numero="150" code_apogee="" module_type="2">
|
||||
<coefficients ue_reference="1897" coef="2.0"/>
|
||||
<coefficients ue_reference="1898" coef="4.0"/>
|
||||
</module>
|
||||
</matiere>
|
||||
</ue>
|
||||
</formation>
|
@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
"""XXX OBSOLETE
|
||||
|
||||
Scenario: préparation base de données pour tests Selenium
|
||||
|
||||
S'utilise comme un test avec pytest, mais n'est pas un test !
|
||||
|
139
tools/fakedatabase/create_test_api_database.py
Normal file
139
tools/fakedatabase/create_test_api_database.py
Normal file
@ -0,0 +1,139 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Initialise une base pour les tests de l'API ScoDoc 9
|
||||
|
||||
Création des départements, formations, semestres, étudiants, groupes...
|
||||
|
||||
utilisation:
|
||||
1) modifier le .env pour indiquer
|
||||
SCODOC_DATABASE_URI="postgresql:///SCO_TEST_API"
|
||||
|
||||
2) En tant qu'utilisateur scodoc, lancer:
|
||||
tools/create_database.sh SCO_TEST_API
|
||||
flask db upgrade
|
||||
flask sco-db-init --erase
|
||||
flask init-test-database
|
||||
|
||||
3) relancer ScoDoc:
|
||||
flask run --host 0.0.0.0
|
||||
|
||||
4) lancer client de test (ou vérifier dans le navigateur)
|
||||
|
||||
"""
|
||||
import datetime
|
||||
import random
|
||||
|
||||
random.seed(12345678) # tests reproductibles
|
||||
|
||||
from flask_login import login_user
|
||||
|
||||
from app import auth
|
||||
from app import models
|
||||
from app import db
|
||||
from app.scodoc import sco_formations
|
||||
from tools.fakeportal.gen_nomprenoms import nomprenom
|
||||
|
||||
# La formation à utiliser:
|
||||
FORMATION_XML_FILENAME = "tests/ressources/formations/scodoc_formation_RT_BUT_RT_v1.xml"
|
||||
|
||||
|
||||
def init_departement(acronym):
|
||||
"Create dept, and switch context into it."
|
||||
import app as mapp
|
||||
|
||||
dept = models.Departement(acronym=acronym)
|
||||
db.session.add(dept)
|
||||
mapp.set_sco_dept(acronym)
|
||||
db.session.commit()
|
||||
return dept
|
||||
|
||||
|
||||
def import_formation() -> models.Formation:
|
||||
"""Import formation from XML.
|
||||
Returns formation_id
|
||||
"""
|
||||
with open(FORMATION_XML_FILENAME) as f:
|
||||
doc = f.read()
|
||||
# --- Création de la formation
|
||||
f = sco_formations.formation_import_xml(doc)
|
||||
return models.Formation.query.get(f[0])
|
||||
|
||||
|
||||
def create_user(dept):
|
||||
"""créé les utilisaterurs nécessaires aux tests"""
|
||||
user = auth.models.User(
|
||||
user_name="test", nom="Doe", prenom="John", dept=dept.acronym
|
||||
)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
|
||||
|
||||
def create_fake_etud():
|
||||
"""Créé un faux étudiant et l'insère dans la base"""
|
||||
civilite = random.choice(("M", "F", "X"))
|
||||
nom, prenom = nomprenom(civilite)
|
||||
etud = models.Identite(civilite=civilite, nom=nom, prenom=prenom)
|
||||
db.session.add(etud)
|
||||
db.session.commit()
|
||||
return etud
|
||||
|
||||
|
||||
def create_etuds(nb=16):
|
||||
"create nb etuds"
|
||||
return [create_fake_etud() for _ in range(nb)]
|
||||
|
||||
|
||||
def create_formsemestre(formation, user, semestre_idx=1):
|
||||
"""Create formsemestre and moduleimpls"""
|
||||
formsemestre = models.FormSemestre(
|
||||
dept_id=formation.dept_id,
|
||||
semestre_id=semestre_idx,
|
||||
titre="Semestre test",
|
||||
date_debut=datetime.datetime(2021, 9, 1),
|
||||
date_fin=datetime.datetime(2022, 1, 31),
|
||||
modalite="FI",
|
||||
formation=formation,
|
||||
)
|
||||
db.session.add(formsemestre)
|
||||
db.session.commit()
|
||||
# Crée un modulimpl par module de ce semestre:
|
||||
for module in formation.modules.filter_by(semestre_id=semestre_idx):
|
||||
modimpl = models.ModuleImpl(
|
||||
module_id=module.id, formsemestre_id=formsemestre.id, responsable_id=user.id
|
||||
)
|
||||
db.session.add(modimpl)
|
||||
db.session.commit()
|
||||
return formsemestre
|
||||
|
||||
|
||||
def inscrit_etudiants(etuds, formsemestre):
|
||||
"""Inscrit les etudiants aux semestres et à tous ses modules"""
|
||||
for etud in etuds:
|
||||
ins = models.FormSemestreInscription(
|
||||
etudid=etud.id, formsemestre_id=formsemestre.id, etat="I"
|
||||
)
|
||||
db.session.add(ins)
|
||||
for modimpl in formsemestre.modimpls:
|
||||
insmod = models.ModuleImplInscription(
|
||||
etudid=etud.id, moduleimpl_id=modimpl.id
|
||||
)
|
||||
db.session.add(insmod)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def init_test_database():
|
||||
dept = init_departement("TAPI")
|
||||
user = create_user(dept)
|
||||
login_user(user)
|
||||
|
||||
etuds = create_etuds()
|
||||
formation = import_formation()
|
||||
formsemestre = create_formsemestre(formation, user)
|
||||
inscrit_etudiants(etuds, formsemestre)
|
||||
# à compléter
|
||||
# - groupes
|
||||
# - absences
|
||||
# - notes
|
||||
# - décisions de jury
|
||||
# ...
|
Loading…
Reference in New Issue
Block a user