Compare commits
30 Commits
46c86d2928
...
70e3006981
Author | SHA1 | Date | |
---|---|---|---|
70e3006981 | |||
bae46c2794 | |||
|
b1055a4ebe | ||
|
b2ef6a4c53 | ||
|
a7c7bd655d | ||
|
1309043a98 | ||
|
a75b41ca5f | ||
8df25ca02f | |||
61f9dddeb6 | |||
a1f5340935 | |||
68128c27d5 | |||
8ecaa2bed0 | |||
7c61dd8d63 | |||
f493ba344f | |||
f5079d9aef | |||
55add2ffb3 | |||
5865b67652 | |||
3c8b088d5e | |||
2da359ae41 | |||
09ec53f573 | |||
3787e0145a | |||
edf989ee04 | |||
203f3a5342 | |||
161f8476ca | |||
d419d75515 | |||
f23630d7fd | |||
fa0417f0b1 | |||
12256dc3d4 | |||
46529917ea | |||
2367984848 |
|
@ -38,7 +38,7 @@ from app.scodoc import sco_groups
|
|||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc.sco_utils import ModuleType
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.tables.recap import TableRecap
|
||||
from app.tables.recap import TableRecap, RowRecap
|
||||
|
||||
|
||||
@bp.route("/formsemestre/<int:formsemestre_id>")
|
||||
|
@ -543,16 +543,30 @@ def formsemestre_resultat(formsemestre_id: int):
|
|||
formsemestre: FormSemestre = query.first_or_404(formsemestre_id)
|
||||
app.set_sco_dept(formsemestre.departement.acronym)
|
||||
res: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||
table = TableRecap(
|
||||
res, convert_values=convert_values, include_evaluations=False, mode_jury=False
|
||||
)
|
||||
# Supprime les champs inutiles (mise en forme)
|
||||
rows = table.to_list()
|
||||
# Ajoute le groupe de chaque partition:
|
||||
# Ajoute le groupe de chaque partition,
|
||||
etud_groups = sco_groups.get_formsemestre_etuds_groups(formsemestre_id)
|
||||
for row in rows:
|
||||
row["partitions"] = etud_groups.get(row["etudid"], {})
|
||||
|
||||
class RowRecapAPI(RowRecap):
|
||||
"""Pour table avec partitions et sort_key"""
|
||||
|
||||
def add_etud_cols(self):
|
||||
"""Ajoute colonnes étudiant: codes, noms"""
|
||||
super().add_etud_cols()
|
||||
self.add_cell("partitions", "partitions", etud_groups.get(self.etud.id, {}))
|
||||
self.add_cell("sort_key", "sort_key", self.etud.sort_key)
|
||||
|
||||
table = TableRecap(
|
||||
res,
|
||||
convert_values=convert_values,
|
||||
include_evaluations=False,
|
||||
mode_jury=False,
|
||||
row_class=RowRecapAPI,
|
||||
)
|
||||
|
||||
rows = table.to_list()
|
||||
|
||||
# for row in rows:
|
||||
# row["partitions"] = etud_groups.get(row["etudid"], {})
|
||||
return rows
|
||||
|
||||
|
||||
|
|
|
@ -542,9 +542,9 @@ def formation_semestre_niveaux_warning(formation: Formation, semestre_idx: int)
|
|||
for parcour_code, niveaux in niveaux_sans_ue_by_parcour.items():
|
||||
H.append(
|
||||
f"""<li>Parcours {parcour_code} : {
|
||||
len(niveaux)} niveaux sans UEs
|
||||
<span>
|
||||
{ ', '.join( f'{niveau.competence.titre} {niveau.ordre}'
|
||||
len(niveaux)} niveaux sans UEs :
|
||||
<span class="niveau-nom"><span>
|
||||
{ '</span>, <span>'.join( f'{niveau.competence.titre} {niveau.ordre}'
|
||||
for niveau in niveaux
|
||||
)
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ from app.models.but_refcomp import (
|
|||
ApcNiveau,
|
||||
ApcParcours,
|
||||
)
|
||||
from app.models import Evaluation, Scolog, ScolarAutorisationInscription
|
||||
from app.models import Evaluation, ModuleImpl, Scolog, ScolarAutorisationInscription
|
||||
from app.models.but_validations import (
|
||||
ApcValidationAnnee,
|
||||
ApcValidationRCUE,
|
||||
|
@ -413,12 +413,12 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||
# Si validée par niveau supérieur:
|
||||
if self.code_valide == sco_codes.ADSUP:
|
||||
self.codes.insert(0, sco_codes.ADSUP)
|
||||
self.explanation = f"<div>{explanation}</div>"
|
||||
self.explanation = f'<div class="deca-expl">{explanation}</div>'
|
||||
messages = self.descr_pb_coherence()
|
||||
if messages:
|
||||
self.explanation += (
|
||||
'<div class="warning">'
|
||||
+ '</div><div class="warning">'.join(messages)
|
||||
'<div class="warning warning-info">'
|
||||
+ '</div><div class="warning warning-info">'.join(messages)
|
||||
+ "</div>"
|
||||
)
|
||||
self.codes = [self.codes[0]] + sorted((c or "") for c in self.codes[1:])
|
||||
|
@ -796,16 +796,33 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||
if self.formsemestre_pair is not None:
|
||||
sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre_pair.id)
|
||||
|
||||
def has_notes_en_attente(self) -> bool:
|
||||
"Vrai si l'étudiant a au moins une note en attente dans le semestre origine de ce deca"
|
||||
res = (
|
||||
def _get_current_res(self) -> ResultatsSemestreBUT:
|
||||
"Les res. du semestre d'origine du deca"
|
||||
return (
|
||||
self.res_pair
|
||||
if self.formsemestre_pair
|
||||
and (self.formsemestre.id == self.formsemestre_pair.id)
|
||||
else self.res_impair
|
||||
)
|
||||
|
||||
def has_notes_en_attente(self) -> bool:
|
||||
"Vrai si l'étudiant a au moins une note en attente dans le semestre origine de ce deca"
|
||||
res = self._get_current_res()
|
||||
return res and self.etud.id in res.get_etudids_attente()
|
||||
|
||||
def get_modimpls_attente(self) -> list[ModuleImpl]:
|
||||
"Liste des ModuleImpl dans lesquels l'étudiant à au moins une note en ATTente"
|
||||
res = self._get_current_res()
|
||||
modimpls_results = [
|
||||
modimpl_result
|
||||
for modimpl_result in res.modimpls_results.values()
|
||||
if self.etud.id in modimpl_result.etudids_attente
|
||||
]
|
||||
modimpls = [
|
||||
db.session.get(ModuleImpl, mr.moduleimpl_id) for mr in modimpls_results
|
||||
]
|
||||
return sorted(modimpls, key=lambda mi: (mi.module.numero, mi.module.code))
|
||||
|
||||
def record_all(self, only_validantes: bool = False) -> bool:
|
||||
"""Enregistre les codes qui n'ont pas été spécifiés par le formulaire,
|
||||
et sont donc en mode "automatique".
|
||||
|
@ -997,19 +1014,23 @@ class DecisionsProposeesAnnee(DecisionsProposees):
|
|||
if dec_ue.code_valide not in CODES_UE_VALIDES:
|
||||
if (
|
||||
dec_ue.ue_status
|
||||
and dec_ue.ue_status["was_capitalized"]
|
||||
and dec_ue.ue_status["is_capitalized"]
|
||||
):
|
||||
messages.append(
|
||||
f"Information: l'UE {ue.acronyme} capitalisée est utilisée pour un RCUE cette année"
|
||||
)
|
||||
else:
|
||||
messages.append(
|
||||
f"L'UE {ue.acronyme} n'est pas validée mais son RCUE l'est !"
|
||||
f"L'UE {ue.acronyme} n'est pas validée mais son RCUE l'est (probablement une validation antérieure)"
|
||||
)
|
||||
else:
|
||||
messages.append(
|
||||
f"L'UE {ue.acronyme} n'a pas décision (???)"
|
||||
)
|
||||
# Voyons si on est dispensé de cette ue ?
|
||||
res = self.res_impair if ue.semestre_idx % 2 else self.res_pair
|
||||
if res and (self.etud.id, ue.id) in res.dispense_ues:
|
||||
messages.append(f"Pas (ré)inscrit à l'UE {ue.acronyme}")
|
||||
return messages
|
||||
|
||||
def valide_diplome(self) -> bool:
|
||||
|
@ -1514,7 +1535,7 @@ class DecisionsProposeesUE(DecisionsProposees):
|
|||
|
||||
def __repr__(self) -> str:
|
||||
return f"""<{self.__class__.__name__} ue={self.ue.acronyme} valid={self.code_valide
|
||||
} codes={self.codes} explanation={self.explanation}>"""
|
||||
} codes={self.codes} explanation="{self.explanation}">"""
|
||||
|
||||
def compute_codes(self):
|
||||
"""Calcul des .codes attribuables et de l'explanation associée"""
|
||||
|
|
|
@ -16,8 +16,8 @@ from app.scodoc.sco_exceptions import ScoValueError
|
|||
|
||||
|
||||
def formsemestre_validation_auto_but(
|
||||
formsemestre: FormSemestre, only_adm: bool = True
|
||||
) -> int:
|
||||
formsemestre: FormSemestre, only_adm: bool = True, dry_run=False
|
||||
) -> tuple[int, list[jury_but.DecisionsProposeesAnnee]]:
|
||||
"""Calcul automatique des décisions de jury sur une "année" BUT.
|
||||
|
||||
- N'enregistre jamais de décisions de l'année scolaire précédente, même
|
||||
|
@ -27,16 +27,22 @@ def formsemestre_validation_auto_but(
|
|||
En revanche, si only_adm est faux, on enregistre la première décision proposée par ScoDoc
|
||||
(mode à n'utiliser que pour les tests unitaires vérifiant la saisie des jurys)
|
||||
|
||||
Returns: nombre d'étudiants pour lesquels on a enregistré au moins un code.
|
||||
Returns:
|
||||
- En mode normal, (nombre d'étudiants pour lesquels on a enregistré au moins un code, []])
|
||||
- En mode dry_run, (0, list[DecisionsProposeesAnnee])
|
||||
"""
|
||||
if not formsemestre.formation.is_apc():
|
||||
raise ScoValueError("fonction réservée aux formations BUT")
|
||||
nb_etud_modif = 0
|
||||
decas = []
|
||||
with sco_cache.DeferredSemCacheManager():
|
||||
for etudid in formsemestre.etuds_inscriptions:
|
||||
etud = Identite.get_etud(etudid)
|
||||
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
|
||||
nb_etud_modif += deca.record_all(only_validantes=only_adm)
|
||||
if not dry_run:
|
||||
nb_etud_modif += deca.record_all(only_validantes=only_adm)
|
||||
else:
|
||||
decas.append(deca)
|
||||
|
||||
db.session.commit()
|
||||
ScolarNews.add(
|
||||
|
@ -49,4 +55,4 @@ def formsemestre_validation_auto_but(
|
|||
formsemestre_id=formsemestre.id,
|
||||
),
|
||||
)
|
||||
return nb_etud_modif
|
||||
return nb_etud_modif, decas
|
||||
|
|
|
@ -109,23 +109,29 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
|||
</div>"""
|
||||
)
|
||||
ue_impair, ue_pair = rcue.ue_1, rcue.ue_2
|
||||
# Les UEs à afficher,
|
||||
# qui
|
||||
ues_ro = [
|
||||
# Les UEs à afficher : on regarde si read only et si dispense (non ré-inscription à l'UE)
|
||||
ues_ro_dispense = [
|
||||
(
|
||||
ue_impair,
|
||||
rcue.ue_cur_impair is None,
|
||||
deca.res_impair
|
||||
and (deca.etud.id, ue_impair.id) in deca.res_impair.dispense_ues,
|
||||
),
|
||||
(
|
||||
ue_pair,
|
||||
rcue.ue_cur_pair is None,
|
||||
deca.res_pair
|
||||
and (deca.etud.id, ue_pair.id) in deca.res_pair.dispense_ues,
|
||||
),
|
||||
]
|
||||
# Ordonne selon les dates des 2 semestres considérés:
|
||||
if reverse_semestre:
|
||||
ues_ro[0], ues_ro[1] = ues_ro[1], ues_ro[0]
|
||||
ues_ro_dispense[0], ues_ro_dispense[1] = (
|
||||
ues_ro_dispense[1],
|
||||
ues_ro_dispense[0],
|
||||
)
|
||||
# Colonnes d'UE:
|
||||
for ue, ue_read_only in ues_ro:
|
||||
for ue, ue_read_only, ue_dispense in ues_ro_dispense:
|
||||
if ue:
|
||||
H.append(
|
||||
_gen_but_niveau_ue(
|
||||
|
@ -134,6 +140,7 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
|||
disabled=read_only or ue_read_only,
|
||||
annee_prec=ue_read_only,
|
||||
niveau_id=ue.niveau_competence.id,
|
||||
ue_dispense=ue_dispense,
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
@ -188,21 +195,30 @@ def _gen_but_niveau_ue(
|
|||
disabled: bool = False,
|
||||
annee_prec: bool = False,
|
||||
niveau_id: int = None,
|
||||
ue_dispense: bool = False,
|
||||
) -> str:
|
||||
if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
|
||||
moy_ue_str = f"""<span class="ue_cap">{
|
||||
scu.fmt_note(dec_ue.moy_ue_with_cap)}</span>"""
|
||||
|
||||
if ue_dispense:
|
||||
etat_en_cours = """Non (ré)inscrit à cette UE"""
|
||||
else:
|
||||
etat_en_cours = f"""UE en cours
|
||||
{ "sans notes" if np.isnan(dec_ue.moy_ue)
|
||||
else
|
||||
("avec moyenne <b>" + scu.fmt_note(dec_ue.moy_ue) + "</b>")
|
||||
}
|
||||
"""
|
||||
|
||||
scoplement = f"""<div class="scoplement">
|
||||
<div>
|
||||
<b>UE {ue.acronyme} capitalisée </b>
|
||||
<span>le {dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")}
|
||||
</span>
|
||||
</div>
|
||||
<div>UE en cours
|
||||
{ "sans notes" if np.isnan(dec_ue.moy_ue)
|
||||
else
|
||||
("avec moyenne <b>" + scu.fmt_note(dec_ue.moy_ue) + "</b>")
|
||||
}
|
||||
<div>
|
||||
{ etat_en_cours }
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
@ -244,7 +260,13 @@ def _gen_but_niveau_ue(
|
|||
</div>
|
||||
"""
|
||||
else:
|
||||
scoplement = ""
|
||||
if dec_ue.ue_status and dec_ue.ue_status["was_capitalized"]:
|
||||
scoplement = """<div class="scoplement">
|
||||
UE déjà capitalisée avec résultat moins favorable.
|
||||
</div>
|
||||
"""
|
||||
else:
|
||||
scoplement = ""
|
||||
|
||||
ue_class = "" # 'recorded' if dec_ue.code_valide is not None else ''
|
||||
if dec_ue.code_valide is not None and dec_ue.codes:
|
||||
|
|
|
@ -75,7 +75,7 @@ class RegroupementCoherentUE:
|
|||
else None
|
||||
)
|
||||
|
||||
# Autres validations pour l'UE paire
|
||||
# Autres validations pour les UEs paire/impaire
|
||||
self.validation_ue_best_pair = best_autre_ue_validation(
|
||||
etud.id,
|
||||
niveau.id,
|
||||
|
@ -101,14 +101,24 @@ class RegroupementCoherentUE:
|
|||
"résultats formsemestre de l'UE si elle est courante, None sinon"
|
||||
self.ue_status_impair = None
|
||||
if self.ue_cur_impair:
|
||||
# UE courante
|
||||
ue_status = res_impair.get_etud_ue_status(etud.id, self.ue_cur_impair.id)
|
||||
self.moy_ue_1 = ue_status["moy"] if ue_status else None # avec capitalisée
|
||||
self.ue_1 = self.ue_cur_impair
|
||||
self.res_impair = res_impair
|
||||
self.ue_status_impair = ue_status
|
||||
elif self.validation_ue_best_impair:
|
||||
# UE capitalisée
|
||||
self.moy_ue_1 = self.validation_ue_best_impair.moy_ue
|
||||
self.ue_1 = self.validation_ue_best_impair.ue
|
||||
if (
|
||||
res_impair
|
||||
and self.validation_ue_best_impair
|
||||
and self.validation_ue_best_impair.ue
|
||||
):
|
||||
self.ue_status_impair = res_impair.get_etud_ue_status(
|
||||
etud.id, self.validation_ue_best_impair.ue.id
|
||||
)
|
||||
else:
|
||||
self.moy_ue_1, self.ue_1 = None, None
|
||||
self.moy_ue_1_val = self.moy_ue_1 if self.moy_ue_1 is not None else 0.0
|
||||
|
|
|
@ -438,7 +438,7 @@ class ResultatsSemestre(ResultatsCache):
|
|||
|
||||
def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict | None:
|
||||
"""L'état de l'UE pour cet étudiant.
|
||||
Result: dict, ou None si l'UE n'est pas dans ce semestre.
|
||||
Result: dict, ou None si l'UE n'existe pas ou n'est pas dans ce semestre.
|
||||
{
|
||||
"is_capitalized": # vrai si la version capitalisée est celle prise en compte (meilleure)
|
||||
"was_capitalized":# si elle a été capitalisée (meilleure ou pas)
|
||||
|
@ -456,6 +456,8 @@ class ResultatsSemestre(ResultatsCache):
|
|||
}
|
||||
"""
|
||||
ue: UniteEns = db.session.get(UniteEns, ue_id)
|
||||
if not ue:
|
||||
return None
|
||||
ue_dict = ue.to_dict()
|
||||
|
||||
if ue.type == UE_SPORT:
|
||||
|
|
|
@ -334,16 +334,14 @@ class Identite(models.ScoDocModel):
|
|||
return f"{(self.nom_usuel or self.nom or '?').upper()} {(self.prenom or '')[:2].capitalize()}."
|
||||
|
||||
@cached_property
|
||||
def sort_key(self) -> tuple:
|
||||
def sort_key(self) -> str:
|
||||
"clé pour tris par ordre alphabétique"
|
||||
# Note: scodoc7 utilisait sco_etud.etud_sort_key, à mettre à jour
|
||||
# si on modifie cette méthode.
|
||||
return (
|
||||
scu.sanitize_string(
|
||||
self.nom_usuel or self.nom or "", remove_spaces=False
|
||||
).lower(),
|
||||
scu.sanitize_string(self.prenom or "", remove_spaces=False).lower(),
|
||||
)
|
||||
return scu.sanitize_string(
|
||||
(self.nom_usuel or self.nom or "") + ";" + (self.prenom or ""),
|
||||
remove_spaces=False,
|
||||
).lower()
|
||||
|
||||
def get_first_email(self, field="email") -> str:
|
||||
"Le mail associé à la première adresse de l'étudiant, ou None"
|
||||
|
|
|
@ -208,7 +208,7 @@ class FormSemestre(models.ScoDocModel):
|
|||
return cls.query.filter_by(id=formsemestre_id).first_or_404()
|
||||
|
||||
def sort_key(self) -> tuple:
|
||||
"""clé pour tris par ordre alphabétique
|
||||
"""clé pour tris par ordre de date_debut, le plus ancien en tête
|
||||
(pour avoir le plus récent d'abord, sort avec reverse=True)"""
|
||||
return (self.date_debut, self.semestre_id)
|
||||
|
||||
|
|
|
@ -460,7 +460,8 @@ def dictfilter(d, fields, filter_nulls=True):
|
|||
# --- Misc Tools
|
||||
|
||||
|
||||
def DateDMYtoISO(dmy: str, null_is_empty=False) -> str | None: # XXX deprecated
|
||||
# XXX deprecated, voir convert_fr_date
|
||||
def DateDMYtoISO(dmy: str, null_is_empty=False) -> str | None:
|
||||
"""Convert date string from french format (or ISO) to ISO.
|
||||
If null_is_empty (default false), returns "" if no input.
|
||||
"""
|
||||
|
|
|
@ -89,7 +89,7 @@ def formsemestre_bulletinetud_published_dict(
|
|||
version="long",
|
||||
) -> dict:
|
||||
"""Dictionnaire representant les informations _publiees_ du bulletin de notes
|
||||
Utilisé pour JSON, devrait l'être aussi pour XML. (todo)
|
||||
Utilisé pour JSON des formations classiques (mais pas pour le XML, qui est deprecated).
|
||||
|
||||
version:
|
||||
short (sans les évaluations)
|
||||
|
@ -169,6 +169,21 @@ def formsemestre_bulletinetud_published_dict(
|
|||
pid = partition["partition_id"]
|
||||
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
||||
|
||||
# Il serait préférable de factoriser et d'avoir la même section
|
||||
# "semestre" que celle des bulletins BUT.
|
||||
etud_groups = sco_groups.get_etud_formsemestre_groups(
|
||||
etud, formsemestre, only_to_show=True
|
||||
)
|
||||
d["semestre"] = {
|
||||
"etapes": [str(x.etape_apo) for x in formsemestre.etapes if x.etape_apo],
|
||||
"date_debut": formsemestre.date_debut.isoformat(),
|
||||
"date_fin": formsemestre.date_fin.isoformat(),
|
||||
"annee_universitaire": formsemestre.annee_scolaire_str(),
|
||||
"numero": formsemestre.semestre_id,
|
||||
"inscription": "", # inutilisé mais nécessaire pour le js de Seb.
|
||||
"groupes": [group.to_dict() for group in etud_groups],
|
||||
}
|
||||
|
||||
ues_stat = nt.get_ues_stat_dict()
|
||||
modimpls = nt.get_modimpls_dict()
|
||||
nbetuds = len(nt.etud_moy_gen_ranks)
|
||||
|
|
|
@ -71,12 +71,10 @@ def report_debouche_date(start_year=None, fmt="html"):
|
|||
etudids = get_etudids_with_debouche(start_year)
|
||||
tab = table_debouche_etudids(etudids, keep_numeric=keep_numeric)
|
||||
|
||||
tab.filename = scu.make_filename("debouche_scodoc_%s" % start_year)
|
||||
tab.origin = (
|
||||
"Généré par %s le " % sco_version.SCONAME + scu.timedate_human_repr() + ""
|
||||
)
|
||||
tab.caption = "Récapitulatif débouchés à partir du 1/1/%s." % start_year
|
||||
tab.base_url = "%s?start_year=%s" % (request.base_url, start_year)
|
||||
tab.filename = scu.make_filename(f"debouche_scodoc_{start_year}")
|
||||
tab.origin = f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}"
|
||||
tab.caption = f"Récapitulatif débouchés à partir du 1/1/{start_year}."
|
||||
tab.base_url = f"{request.base_url}?start_year={start_year}"
|
||||
return tab.make_page(
|
||||
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
|
||||
init_qtip=True,
|
||||
|
@ -118,7 +116,16 @@ def get_etudids_with_debouche(start_year):
|
|||
|
||||
def table_debouche_etudids(etudids, keep_numeric=True):
|
||||
"""Rapport pour ces étudiants"""
|
||||
L = []
|
||||
rows = []
|
||||
# Recherche les débouchés:
|
||||
itemsuivi_etuds = {etudid: itemsuivi_list_etud(etudid) for etudid in etudids}
|
||||
all_tags = set()
|
||||
for debouche in itemsuivi_etuds.values():
|
||||
if debouche:
|
||||
for it in debouche:
|
||||
all_tags.update(tag.strip() for tag in it["tags"].split(","))
|
||||
all_tags = tuple(sorted(all_tags))
|
||||
|
||||
for etudid in etudids:
|
||||
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
|
||||
# retrouve le "dernier" semestre (au sens de la date de fin)
|
||||
|
@ -152,25 +159,33 @@ def table_debouche_etudids(etudids, keep_numeric=True):
|
|||
"sem_ident": "%s %s"
|
||||
% (last_sem["date_debut_iso"], last_sem["titre"]), # utile pour tris
|
||||
}
|
||||
|
||||
# recherche des débouchés
|
||||
debouche = itemsuivi_list_etud(etudid) # liste de plusieurs items
|
||||
debouche = itemsuivi_etuds[etudid] # liste de plusieurs items
|
||||
if debouche:
|
||||
row["debouche"] = "<br>".join(
|
||||
[
|
||||
str(it["item_date"])
|
||||
+ " : "
|
||||
+ it["situation"]
|
||||
+ " <i>"
|
||||
+ it["tags"]
|
||||
+ "</i>"
|
||||
for it in debouche
|
||||
]
|
||||
) #
|
||||
if keep_numeric: # pour excel:
|
||||
row["debouche"] = "\n".join(
|
||||
f"""{it["item_date"]}: {it["situation"]}""" for it in debouche
|
||||
)
|
||||
else:
|
||||
row["debouche"] = "<br>".join(
|
||||
[
|
||||
str(it["item_date"])
|
||||
+ " : "
|
||||
+ it["situation"]
|
||||
+ " <i>"
|
||||
+ it["tags"]
|
||||
+ "</i>"
|
||||
for it in debouche
|
||||
]
|
||||
)
|
||||
for it in debouche:
|
||||
for tag in it["tags"].split(","):
|
||||
tag = tag.strip()
|
||||
row[f"tag_{tag}"] = tag
|
||||
else:
|
||||
row["debouche"] = "non renseigné"
|
||||
L.append(row)
|
||||
L.sort(key=lambda x: x["sem_ident"])
|
||||
rows.append(row)
|
||||
rows.sort(key=lambda x: x["sem_ident"])
|
||||
|
||||
titles = {
|
||||
"civilite": "",
|
||||
|
@ -184,21 +199,25 @@ def table_debouche_etudids(etudids, keep_numeric=True):
|
|||
"effectif": "Eff.",
|
||||
"debouche": "Débouché",
|
||||
}
|
||||
columns_ids = [
|
||||
"semestre",
|
||||
"semestre_id",
|
||||
"periode",
|
||||
"civilite",
|
||||
"nom",
|
||||
"prenom",
|
||||
"moy",
|
||||
"rang",
|
||||
"effectif",
|
||||
"debouche",
|
||||
]
|
||||
for tag in all_tags:
|
||||
titles[f"tag_{tag}"] = tag
|
||||
columns_ids.append(f"tag_{tag}")
|
||||
tab = GenTable(
|
||||
columns_ids=(
|
||||
"semestre",
|
||||
"semestre_id",
|
||||
"periode",
|
||||
"civilite",
|
||||
"nom",
|
||||
"prenom",
|
||||
"moy",
|
||||
"rang",
|
||||
"effectif",
|
||||
"debouche",
|
||||
),
|
||||
columns_ids=columns_ids,
|
||||
titles=titles,
|
||||
rows=L,
|
||||
rows=rows,
|
||||
# html_col_width='4em',
|
||||
html_sortable=True,
|
||||
html_class="table_leftalign table_listegroupe",
|
||||
|
|
|
@ -79,21 +79,30 @@ def index_html(showcodes=0, showsemtable=0, export_table_formsemestres=False):
|
|||
else:
|
||||
html_table_formsemestres = None
|
||||
|
||||
current_formsemestres_by_modalite, modalites = (
|
||||
sco_modalites.group_formsemestres_by_modalite(current_formsemestres)
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"scolar/index.j2",
|
||||
current_user=current_user,
|
||||
current_formsemestres=current_formsemestres,
|
||||
current_formsemestres_by_modalite=current_formsemestres_by_modalite,
|
||||
dept_name=sco_preferences.get_preference("DeptName"),
|
||||
formsemestres=formsemestres,
|
||||
html_current_formsemestres=_show_current_formsemestres(
|
||||
current_formsemestres, showcodes
|
||||
emptygroupicon=scu.icontag(
|
||||
"emptygroupicon_img", title="Pas d'inscrits", border="0"
|
||||
),
|
||||
formsemestres=formsemestres,
|
||||
groupicon=scu.icontag("groupicon_img", title="Inscrits", border="0"),
|
||||
html_table_formsemestres=html_table_formsemestres,
|
||||
locked_formsemestres=locked_formsemestres,
|
||||
modalites=modalites,
|
||||
nb_locked=locked_formsemestres.count(),
|
||||
nb_user_accounts=sco_users.get_users_count(dept=g.scodoc_dept),
|
||||
page_title=f"ScoDoc {g.scodoc_dept}",
|
||||
Permission=Permission,
|
||||
scolar_news_summary=ScolarNews.scolar_news_summary_html(),
|
||||
showcodes=showcodes,
|
||||
showsemtable=showsemtable,
|
||||
sco=ScoData(),
|
||||
)
|
||||
|
@ -116,6 +125,7 @@ def _convert_formsemestres_to_dicts(
|
|||
lockicon = "X"
|
||||
# génère liste de dict
|
||||
sems = []
|
||||
formsemestre: FormSemestre
|
||||
for formsemestre in formsemestres:
|
||||
nb_inscrits = len(formsemestre.inscriptions)
|
||||
formation = formsemestre.formation
|
||||
|
@ -151,61 +161,6 @@ def _convert_formsemestres_to_dicts(
|
|||
return sems
|
||||
|
||||
|
||||
def _show_current_formsemestres(formsemestres: Query, showcodes: bool) -> str:
|
||||
"""html div avec les formsemestres courants de la page d'accueil"""
|
||||
|
||||
H = []
|
||||
if formsemestres.count():
|
||||
H.append("""<div class="scobox-title">Sessions en cours</div>""")
|
||||
H.append(_sem_table(_convert_formsemestres_to_dicts(formsemestres, showcodes)))
|
||||
else:
|
||||
# aucun semestre courant: affiche aide
|
||||
H.append(
|
||||
"""
|
||||
<div class="scobox-title">Aucune session en cours !</div>
|
||||
<p>Pour ajouter une session, aller dans <a href="Notes" id="link-programmes">Formations</a>,
|
||||
choisissez une formation, puis suivez le lien "<em>UE, modules, semestres</em>".
|
||||
</p>
|
||||
<p>Là, en bas de page, suivez le lien
|
||||
"<em>Mettre en place un nouveau semestre de formation...</em>"
|
||||
</p>"""
|
||||
)
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def _sem_table(sems: list[dict]) -> str:
|
||||
"""Affiche liste des semestres, utilisée pour semestres en cours"""
|
||||
tmpl = f"""<tr class="%(trclass)s">%(tmpcode)s
|
||||
<td class="semicon">%(lockimg)s <a href="{
|
||||
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept)}?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
|
||||
<td class="datesem">%(mois_debut)s <a title="%(session_id)s">-</a> %(mois_fin)s</td>
|
||||
<td class="titresem"><a class="stdlink" href="{url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept)}?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
<span class="respsem">(%(responsable_name)s)</span>
|
||||
</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
# Liste des semestres, groupés par modalités
|
||||
sems_by_mod, modalites = sco_modalites.group_sems_by_modalite(sems)
|
||||
|
||||
H = ['<table class="listesems">']
|
||||
for modalite in modalites:
|
||||
if len(modalites) > 1:
|
||||
H.append('<tr><th colspan="3">%s</th></tr>' % modalite["titre"])
|
||||
|
||||
if sems_by_mod[modalite["modalite"]]:
|
||||
cur_idx = sems_by_mod[modalite["modalite"]][0]["semestre_id"]
|
||||
for sem in sems_by_mod[modalite["modalite"]]:
|
||||
if cur_idx != sem["semestre_id"]:
|
||||
sem["trclass"] = "firstsem" # separe les groupes de semestres
|
||||
cur_idx = sem["semestre_id"]
|
||||
else:
|
||||
sem["trclass"] = ""
|
||||
H.append(tmpl % sem)
|
||||
H.append("</table>")
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def _sem_table_gt(formsemestres: Query, showcodes=False, fmt="html") -> GenTable:
|
||||
"""Table des semestres
|
||||
Utilise une datatables.
|
||||
|
@ -223,9 +178,9 @@ def _sem_table_gt(formsemestres: Query, showcodes=False, fmt="html") -> GenTable
|
|||
columns_ids = (
|
||||
"lockimg",
|
||||
"published",
|
||||
"dash_mois_fin",
|
||||
"semestre_id_n",
|
||||
"modalite",
|
||||
"dash_mois_fin",
|
||||
"titre_resp",
|
||||
"nb_inscrits",
|
||||
"formation",
|
||||
|
@ -275,16 +230,8 @@ def _style_sems(sems: list[dict], fmt="html") -> list[dict]:
|
|||
"""ajoute quelques attributs de présentation pour la table"""
|
||||
is_h = fmt == "html"
|
||||
if is_h:
|
||||
icon_published = scu.icontag(
|
||||
"eye_img",
|
||||
border="0",
|
||||
title="Bulletins publiés sur la passerelle étudiants",
|
||||
)
|
||||
icon_hidden = scu.icontag(
|
||||
"hide_img",
|
||||
border="0",
|
||||
title="Bulletins NON publiés sur la passerelle étudiants",
|
||||
)
|
||||
icon_published = scu.ICON_PUBLISHED
|
||||
icon_hidden = scu.ICON_HIDDEN
|
||||
else:
|
||||
icon_published = "publié"
|
||||
icon_hidden = "non publié"
|
||||
|
|
|
@ -756,7 +756,7 @@ def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list
|
|||
H = [
|
||||
html_sco_header.sco_header(
|
||||
cssstyles=html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
||||
+ ["libjs/jQuery-tagEditor/jquery.tag-editor.css"],
|
||||
+ ["libjs/jQuery-tagEditor/jquery.tag-editor.css", "css/ue_table.css"],
|
||||
javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS
|
||||
+ [
|
||||
"libjs/jinplace-1.2.1.min.js",
|
||||
|
|
|
@ -122,16 +122,14 @@ def format_pays(s):
|
|||
return ""
|
||||
|
||||
|
||||
def etud_sort_key(etud: dict) -> tuple:
|
||||
def etud_sort_key(etud: dict) -> str:
|
||||
"""Clé de tri pour les étudiants représentés par des dict (anciens codes).
|
||||
Equivalent moderne: identite.sort_key
|
||||
"""
|
||||
return (
|
||||
scu.sanitize_string(
|
||||
etud.get("nom_usuel") or etud["nom"] or "", remove_spaces=False
|
||||
).lower(),
|
||||
scu.sanitize_string(etud["prenom"] or "", remove_spaces=False).lower(),
|
||||
)
|
||||
return scu.sanitize_string(
|
||||
(etud.get("nom_usuel") or etud["nom"] or "") + ";" + (etud["prenom"] or ""),
|
||||
remove_spaces=False,
|
||||
).lower()
|
||||
|
||||
|
||||
_identiteEditor = ndb.EditableTable(
|
||||
|
|
|
@ -374,13 +374,7 @@ def evaluation_create_form(
|
|||
args = tf[2]
|
||||
# modifie le codage des dates
|
||||
# (nb: ce formulaire ne permet de créer que des évaluation sur la même journée)
|
||||
if args.get("jour"):
|
||||
try:
|
||||
date_debut = datetime.datetime.strptime(args["jour"], "%d/%m/%Y")
|
||||
except ValueError as exc:
|
||||
raise ScoValueError("Date (j/m/a) invalide") from exc
|
||||
else:
|
||||
date_debut = None
|
||||
date_debut = scu.convert_fr_date(args["jour"]) if args.get("jour") else None
|
||||
args["date_debut"] = date_debut
|
||||
args["date_fin"] = date_debut # même jour
|
||||
args.pop("jour", None)
|
||||
|
|
|
@ -812,14 +812,18 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||
)
|
||||
msg = ""
|
||||
if tf[0] == 1:
|
||||
# check dates
|
||||
if ndb.DateDMYtoISO(tf[2]["date_debut"]) > ndb.DateDMYtoISO(tf[2]["date_fin"]):
|
||||
msg = '<ul class="tf-msg"><li class="tf-msg">Dates de début et fin incompatibles !</li></ul>'
|
||||
# convert and check dates
|
||||
tf[2]["date_debut"] = scu.convert_fr_date(tf[2]["date_debut"])
|
||||
tf[2]["date_fin"] = scu.convert_fr_date(tf[2]["date_fin"])
|
||||
if tf[2]["date_debut"] > tf[2]["date_fin"]:
|
||||
msg = """<ul class="tf-msg">
|
||||
<li class="tf-msg">Dates de début et fin incompatibles !</li>
|
||||
</ul>"""
|
||||
|
||||
if (
|
||||
sco_preferences.get_preference("always_require_apo_sem_codes")
|
||||
and not any(
|
||||
[tf[2]["etape_apo" + str(n)] for n in range(0, scu.EDIT_NB_ETAPES + 1)]
|
||||
tf[2]["etape_apo" + str(n)] for n in range(0, scu.EDIT_NB_ETAPES + 1)
|
||||
)
|
||||
# n'impose pas d'Apo pour les sem. extérieurs
|
||||
and ((formsemestre is None) or formsemestre.modalite != "EXT")
|
||||
|
|
|
@ -1495,7 +1495,12 @@ def formsemestre_note_etuds_sans_notes(
|
|||
</div>
|
||||
{message}
|
||||
|
||||
<form method="post">
|
||||
<style>
|
||||
.sco-std-form select, .sco-std-form input[type="submit"] {{
|
||||
height: 24px;
|
||||
}}
|
||||
</style>
|
||||
<form class="sco-std-form" method="post">
|
||||
<input type="hidden" name="formsemestre_id" value="{formsemestre.id}">
|
||||
<input type="hidden" name="etudid" value="{etudid or ""}">
|
||||
|
||||
|
@ -1506,7 +1511,7 @@ def formsemestre_note_etuds_sans_notes(
|
|||
<option value="ATT" selected>ATT (en attente)</option>
|
||||
<option value="EXC">EXC (neutralisée)</option>
|
||||
</select>
|
||||
<input type="submit" name="enregistrer">
|
||||
<input type="submit" value="Enregistrer">
|
||||
</form>
|
||||
{html_sco_header.sco_footer()}
|
||||
"""
|
||||
|
|
|
@ -36,37 +36,45 @@ Elle n'est pas utilisée pour les parcours, ni pour rien d'autre
|
|||
import collections
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app import log
|
||||
from app.models import FormSemestre
|
||||
|
||||
|
||||
def list_formsemestres_modalites(sems):
|
||||
def list_formsemestres_modalites(formsemestres: list[FormSemestre]) -> list[dict]:
|
||||
"""Liste ordonnée des modalités présentes dans ces formsemestres"""
|
||||
modalites = {}
|
||||
for sem in sems:
|
||||
if sem["modalite"] not in modalites:
|
||||
m = do_modalite_list(args={"modalite": sem["modalite"]})[0]
|
||||
for formsemestre in formsemestres:
|
||||
if formsemestre.modalite not in modalites:
|
||||
m = do_modalite_list(args={"modalite": formsemestre.modalite})[0]
|
||||
modalites[m["modalite"]] = m
|
||||
modalites = list(modalites.values())
|
||||
modalites.sort(key=lambda x: x["numero"])
|
||||
return modalites
|
||||
|
||||
|
||||
def group_sems_by_modalite(sems: list[dict]):
|
||||
def group_formsemestres_by_modalite(
|
||||
formsemestres: list[FormSemestre],
|
||||
) -> dict[str, list[FormSemestre]]:
|
||||
"""Given the list of formsemestre, group them by modalite,
|
||||
sorted in each one by semestre id and date
|
||||
"""
|
||||
sems_by_mod = collections.defaultdict(list)
|
||||
modalites = list_formsemestres_modalites(sems)
|
||||
for modalite in modalites:
|
||||
for sem in sems:
|
||||
if sem["semestre_id"] < 0: # formations en un semestre
|
||||
sem["sortkey"] = (-100 * sem["semestre_id"], sem["dateord"])
|
||||
else:
|
||||
sem["sortkey"] = (sem["semestre_id"], sem["dateord"])
|
||||
if sem["modalite"] == modalite["modalite"]:
|
||||
sems_by_mod[modalite["modalite"]].append(sem)
|
||||
modalites = list_formsemestres_modalites(formsemestres)
|
||||
sems_by_mod = {
|
||||
modalite["modalite"]: [
|
||||
formsemestre
|
||||
for formsemestre in formsemestres
|
||||
if formsemestre.modalite == modalite["modalite"]
|
||||
]
|
||||
for modalite in modalites
|
||||
}
|
||||
# tri dans chaque modalité par indice de semestre et date debut
|
||||
for modalite in modalites:
|
||||
sems_by_mod[modalite["modalite"]].sort(key=lambda x: x["sortkey"])
|
||||
sems_by_mod[modalite["modalite"]].sort(
|
||||
key=lambda x: (
|
||||
x.semestre_id if x.semestre_id > 0 else -1000 * x.semestre_id,
|
||||
x.date_debut,
|
||||
)
|
||||
)
|
||||
|
||||
return sems_by_mod, modalites
|
||||
|
||||
|
|
|
@ -109,6 +109,38 @@ ETATS_INSCRIPTION = {
|
|||
}
|
||||
|
||||
|
||||
def convert_fr_date(date_str: str, allow_iso=True) -> datetime.datetime:
|
||||
"""Converti une date saisie par un humain français avant 2070
|
||||
en un objet datetime.
|
||||
12/2/1972 => 1972-02-12, 12/2/72 => 1972-02-12, mais 12/2/24 => 2024-02-12
|
||||
Le pivot est 70.
|
||||
ScoValueError si date invalide.
|
||||
"""
|
||||
try:
|
||||
return datetime.datetime.strptime(date_str, "%d/%m/%Y")
|
||||
except ValueError:
|
||||
# Try to add century ?
|
||||
m = re.match(r"^(\d{1,2})/(\d{1,2})/(\d\d)$", date_str)
|
||||
if m:
|
||||
year = int(m.group(3))
|
||||
if year < 70:
|
||||
year += 2000
|
||||
else:
|
||||
year += 1900
|
||||
try:
|
||||
return datetime.datetime.strptime(
|
||||
f"{m.group(1)}/{m.group(2)}/{year}", "%d/%m/%Y"
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
if allow_iso:
|
||||
try:
|
||||
return datetime.datetime.fromisoformat(date_str)
|
||||
except ValueError as exc:
|
||||
raise ScoValueError("Date (j/m/a or ISO) invalide") from exc
|
||||
raise ScoValueError("Date (j/m/a) invalide")
|
||||
|
||||
|
||||
def print_progress_bar(
|
||||
iteration,
|
||||
total,
|
||||
|
@ -1444,6 +1476,15 @@ def icontag(name, file_format="png", no_size=False, **attrs):
|
|||
ICON_PDF = icontag("pdficon16x20_img", title="Version PDF")
|
||||
ICON_XLS = icontag("xlsicon_img", title="Export tableur (xlsx)")
|
||||
|
||||
ICON_PUBLISHED = """<img src="/ScoDoc/static/icons/eye_visible_green.svg"
|
||||
width="24" height="19" border="0"
|
||||
title="Bulletins publiés sur la passerelle étudiants"
|
||||
alt="Bulletins publiés sur la passerelle étudiants" />"""
|
||||
ICON_HIDDEN = """<img src="/ScoDoc/static/icons/eye_hidden.svg"
|
||||
width="24" height="19" border="0"
|
||||
title="Bulletins NON publiés sur la passerelle étudiants"
|
||||
alt="Bulletins NON publiés sur la passerelle étudiants" />"""
|
||||
|
||||
# HTML emojis
|
||||
EMO_WARNING = "⚠️" # warning /!\
|
||||
EMO_RED_TRIANGLE_DOWN = "🔻" # red triangle pointed down
|
||||
|
|
|
@ -449,7 +449,7 @@
|
|||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.assiduite-infos {
|
||||
.assiduite-actions {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin: 5px;
|
||||
|
|
|
@ -19,6 +19,13 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.modimpls_att {
|
||||
margin-top: 8px;
|
||||
margin-left: 32px;
|
||||
padding-top: 0;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.jury_but h3 {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
@ -273,3 +280,9 @@ div.but_doc table tr td.amue {
|
|||
font-weight: normal;
|
||||
color: var(--color-explanation);
|
||||
}
|
||||
|
||||
.deca-expl {
|
||||
font-size: 110%;
|
||||
margin-bottom: 8px;
|
||||
margin-left: 16px;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
217
app/static/css/ue_table.css
Normal file
217
app/static/css/ue_table.css
Normal file
|
@ -0,0 +1,217 @@
|
|||
div.formation_descr {
|
||||
background-color: rgb(250, 250, 240);
|
||||
border: 1px solid rgb(128, 128, 128);
|
||||
padding-left: 5px;
|
||||
padding-bottom: 5px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
div.formation_descr span.fd_t {
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
div.formation_descr span.fd_n {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
color: green;
|
||||
margin-left: 6em;
|
||||
}
|
||||
|
||||
|
||||
div.formation_ue_list {
|
||||
border: 1px solid black;
|
||||
background-color: rgb(232, 249, 255);
|
||||
margin-top: 5px;
|
||||
margin-right: 12px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
div.formation_list_ues_titre {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
border-top-right-radius: 18px;
|
||||
border-top-left-radius: 18px;
|
||||
background-color: #0051a9;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
div.formation_list_modules,
|
||||
div.formation_list_ues {
|
||||
border-radius: 18px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
div.formation_list_ues {
|
||||
background-color: #b7d2fa;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
div.formation_list_ues_content {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
div.formation_list_modules {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
div.formation_list_modules_RESSOURCE {
|
||||
background-color: var(--sco-color-ressources);
|
||||
}
|
||||
|
||||
div.formation_list_modules_SAE {
|
||||
background-color: var(--sco-color-saes);
|
||||
}
|
||||
|
||||
div.formation_list_modules_STANDARD {
|
||||
background-color: var(--sco-color-mod-std);
|
||||
}
|
||||
|
||||
div.formation_list_modules_titre {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
font-weight: bold;
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
div.formation_list_ues ul.notes_module_list {
|
||||
margin-top: 0px;
|
||||
margin-bottom: -1px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
div.formation_list_modules ul.notes_module_list {
|
||||
margin-top: 0px;
|
||||
margin-bottom: -1px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
span.missing_ue_ects {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
span.niveau-nom {
|
||||
color: black;
|
||||
}
|
||||
|
||||
span.niveau-nom>span {
|
||||
text-decoration: dashed underline;
|
||||
}
|
||||
|
||||
.formation_apc_infos ul li:not(:last-child) {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
div.formation_parcs {
|
||||
display: inline-flex;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
div.formation_parcs>div {
|
||||
font-size: 100%;
|
||||
color: white;
|
||||
background-color: #09c;
|
||||
opacity: 0.7;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
padding: 2px 6px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
div.formation_parcs>div.ue_tc {
|
||||
color: black;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.formation_parcs>div.focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
div.formation_parcs>div>a:hover {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
div.formation_parcs>div>a,
|
||||
div.formation_parcs>div>a:visited {
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.ue_choix_niveau>div.formation_parcs>div {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
|
||||
div.ue_list_tit {
|
||||
font-weight: bold;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
div.ue_list_tit form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
div.ue_list_tit span.lock_info {
|
||||
color: red;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
ul.apc_ue_list {
|
||||
background-color: rgba(180, 189, 191, 0.14);
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
ul.notes_ue_list {
|
||||
margin-top: 4px;
|
||||
margin-right: 1em;
|
||||
margin-left: 1em;
|
||||
/* padding-top: 1em; */
|
||||
padding-bottom: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.formation_classic_infos ul.notes_ue_list {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.formation_classic_infos li.notes_ue_list {
|
||||
margin-top: 9px;
|
||||
list-style-type: none;
|
||||
border: 1px solid maroon;
|
||||
border-radius: 10px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
li.module_malus span.formation_module_tit {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
span.invalid-module-type {
|
||||
color: red;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
span.formation_module_ue {
|
||||
color: #6e7d92;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
span.notes_module_list_buts {
|
||||
margin-right: 5px;
|
||||
}
|
40
app/static/icons/eye_hidden.svg
Normal file
40
app/static/icons/eye_hidden.svg
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="Capa_1"
|
||||
enable-background="new 0 0 511.985 511.985"
|
||||
height="349.46161"
|
||||
viewBox="0 0 511.985 349.45137"
|
||||
width="512"
|
||||
version="1.1"
|
||||
sodipodi:docname="eye_hidden.svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<g
|
||||
id="g4"
|
||||
transform="translate(5e-4,-81.266625)">
|
||||
<g
|
||||
id="g3">
|
||||
<g
|
||||
id="Layer_2_00000011005494452173574160000003459929325544675728_">
|
||||
<g
|
||||
id="hide_00000049195420225258176850000009593604245528296082_">
|
||||
<path
|
||||
d="m 255.992,406.518 c -110.53,0 -208.51,-87.52 -245.5,-125.15 -13.99,-14.024 -13.99,-36.726 0,-50.75 37,-37.63 135,-125.16 245.5,-125.16 110.5,0 208.51,87.53 245.5,125.16 13.99,14.024 13.99,36.726 0,50.75 -37,37.63 -134.98,125.15 -245.5,125.15 z m 0,-271.06 c -99.18,0 -189.76,81.26 -224.11,116.19 -2.397,2.352 -2.434,6.201 -0.082,8.598 0.027,0.028 0.054,0.055 0.082,0.082 34.34,34.94 124.92,116.19 224.11,116.19 99.19,0 189.75,-81.25 224.1,-116.19 2.397,-2.352 2.434,-6.201 0.082,-8.598 -0.027,-0.028 -0.054,-0.055 -0.082,-0.082 -34.35,-34.93 -124.93,-116.19 -224.1,-116.19 z"
|
||||
style="fill:red;"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 255.992,346.268 c -49.871,0.006 -90.304,-40.419 -90.31,-90.29 -0.006,-49.871 40.419,-90.304 90.29,-90.31 49.871,-0.006 90.304,40.419 90.31,90.29 v 0.01 c -0.055,49.845 -40.445,90.239 -90.29,90.3 z m 0,-150.59 c -33.303,-0.006 -60.304,26.987 -60.31,60.29 -0.006,33.303 26.987,60.304 60.29,60.31 33.303,0.006 60.304,-26.987 60.31,-60.29 0,-0.007 0,-0.013 0,-0.02 -0.05,-33.273 -27.017,-60.231 -60.29,-60.27 z"
|
||||
style="fill:red;"
|
||||
id="path2" />
|
||||
<path
|
||||
d="m 96.262,430.718 c -8.284,0.004 -15.003,-6.709 -15.007,-14.993 -0.002,-3.982 1.58,-7.802 4.397,-10.617 l 319.45,-319.45 c 5.86,-5.857 15.358,-5.855 21.215,0.005 5.857,5.86 5.855,15.358 -0.005,21.215 l -319.45,319.44 c -2.806,2.819 -6.621,4.403 -10.6,4.4 z"
|
||||
style="fill:red;"
|
||||
id="path3" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 673 B |
19
app/static/icons/eye_visible.svg
Normal file
19
app/static/icons/eye_visible.svg
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="Layer_1"
|
||||
enable-background="new 0 0 512 512"
|
||||
height="318.62299"
|
||||
viewBox="0 0 474.22501 318.62299"
|
||||
width="474.22501"
|
||||
version="1.1"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m 441.578,168.115 c -41.315,74.193 -119.667,120.282 -204.438,120.282 -84.826,0 -163.177,-46.089 -204.493,-120.282 -3.182,-5.761 -3.182,-11.851 0,-17.607 C 73.963,76.316 152.314,30.232 237.14,30.232 c 84.771,0 163.122,46.084 204.438,120.276 3.238,5.756 3.238,11.846 0,17.607 z M 467.97,135.798 C 421.332,52.031 332.885,0 237.14,0 141.34,0 52.893,52.031 6.255,135.798 c -8.34,14.946 -8.34,32.081 0,47.016 46.638,83.767 135.085,135.809 230.885,135.809 95.745,0 184.192,-52.042 230.83,-135.809 8.34,-14.934 8.34,-32.07 0,-47.016 z m -230.83,85.528 c 34.183,0 62.001,-27.818 62.001,-62.017 0,-34.199 -27.818,-62.017 -62.001,-62.017 -34.238,0 -62.056,27.818 -62.056,62.017 0,34.199 27.819,62.017 62.056,62.017 z m 0,-154.266 c -50.918,0 -92.288,41.387 -92.288,92.25 0,50.874 41.371,92.244 92.288,92.244 50.863,0 92.233,-41.371 92.233,-92.244 0,-50.863 -41.37,-92.25 -92.233,-92.25 z"
|
||||
fill-rule="evenodd"
|
||||
id="path1" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
20
app/static/icons/eye_visible_green.svg
Normal file
20
app/static/icons/eye_visible_green.svg
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="Layer_1"
|
||||
enable-background="new 0 0 512 512"
|
||||
height="318.62299"
|
||||
viewBox="0 0 474.22501 318.62299"
|
||||
width="474.22501"
|
||||
version="1.1"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m 441.578,168.115 c -41.315,74.193 -119.667,120.282 -204.438,120.282 -84.826,0 -163.177,-46.089 -204.493,-120.282 -3.182,-5.761 -3.182,-11.851 0,-17.607 C 73.963,76.316 152.314,30.232 237.14,30.232 c 84.771,0 163.122,46.084 204.438,120.276 3.238,5.756 3.238,11.846 0,17.607 z M 467.97,135.798 C 421.332,52.031 332.885,0 237.14,0 141.34,0 52.893,52.031 6.255,135.798 c -8.34,14.946 -8.34,32.081 0,47.016 46.638,83.767 135.085,135.809 230.885,135.809 95.745,0 184.192,-52.042 230.83,-135.809 8.34,-14.934 8.34,-32.07 0,-47.016 z m -230.83,85.528 c 34.183,0 62.001,-27.818 62.001,-62.017 0,-34.199 -27.818,-62.017 -62.001,-62.017 -34.238,0 -62.056,27.818 -62.056,62.017 0,34.199 27.819,62.017 62.056,62.017 z m 0,-154.266 c -50.918,0 -92.288,41.387 -92.288,92.25 0,50.874 41.371,92.244 92.288,92.244 50.863,0 92.233,-41.371 92.233,-92.244 0,-50.863 -41.37,-92.25 -92.233,-92.25 z"
|
||||
fill-rule="evenodd"
|
||||
style="fill: rgb(29, 124, 39);"
|
||||
id="path1" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
44
app/static/icons/warning-info.svg
Normal file
44
app/static/icons/warning-info.svg
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-34.1074" y1="645.6909" x2="-34.1074" y2="622.0029" gradientTransform="matrix(20.48 0 0 -20.48 954.52 13234.4395)">
|
||||
<stop offset="0" style="stop-color:#FF9400"/>
|
||||
<stop offset="1" style="stop-color:#FF5F39"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_1_);" d="M296.182,382.792c-1.577,3.83-5.325,6.328-9.462,6.328h-20.48h-20.48h-20.48
|
||||
c-4.137,0-7.885-2.499-9.462-6.328c-1.577-3.83-0.696-8.233,2.232-11.162l17.469-17.469V239.759l-17.469-17.49
|
||||
c-2.929-2.929-3.809-7.332-2.232-11.162c1.577-3.83,5.325-6.308,9.462-6.308h20.48H256h10.24c5.652,0,10.24,4.567,10.24,10.24
|
||||
v139.121l17.49,17.469C296.878,374.559,297.759,378.962,296.182,382.792 M256,122.88c16.937,0,30.72,13.763,30.72,30.72
|
||||
c0,16.937-13.783,30.72-30.72,30.72s-30.72-13.783-30.72-30.72C225.28,136.643,239.063,122.88,256,122.88 M256,0
|
||||
C114.852,0,0,114.831,0,256c0,141.148,114.852,256,256,256c141.169,0,256-114.852,256-256C512,114.831,397.169,0,256,0"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -217,13 +217,11 @@ function creerLigneEtudiant(etud, index) {
|
|||
const nameField = document.createElement("div");
|
||||
nameField.classList.add("name_field");
|
||||
|
||||
if ($("#pdp").is(":checked")) {
|
||||
const pdp = document.createElement("img");
|
||||
pdp.src = `../../api/etudiant/etudid/${etud.id}/photo?size=small`;
|
||||
pdp.alt = `${etud.nom} ${etud.prenom}`;
|
||||
pdp.classList.add("pdp");
|
||||
nameField.appendChild(pdp);
|
||||
}
|
||||
const pdp = document.createElement("img");
|
||||
pdp.src = `../../api/etudiant/etudid/${etud.id}/photo?size=small`;
|
||||
pdp.alt = `${etud.nom} ${etud.prenom}`;
|
||||
pdp.classList.add("pdp");
|
||||
nameField.appendChild(pdp);
|
||||
|
||||
const nameSet = document.createElement("a");
|
||||
nameSet.classList.add("name_set");
|
||||
|
@ -857,13 +855,25 @@ function setupAssiduiteBubble(el, assiduite) {
|
|||
|
||||
// Ajout d'un lien pour plus d'informations
|
||||
const infos = document.createElement("a");
|
||||
infos.className = "assiduite-infos";
|
||||
infos.className = "";
|
||||
infos.textContent = `ℹ️`;
|
||||
infos.title = "Cliquez pour plus d'informations";
|
||||
infos.target = "_blank";
|
||||
infos.href = `tableau_assiduite_actions?type=assiduite&action=details&obj_id=${assiduite.assiduite_id}`;
|
||||
|
||||
bubble.appendChild(infos);
|
||||
// Ajout d'un lien pour modifier l'assiduité
|
||||
const modifs = document.createElement("a");
|
||||
modifs.className = "";
|
||||
modifs.textContent = `📝`;
|
||||
modifs.title = "Cliquez pour modifier l'assiduité";
|
||||
modifs.target = "_blank";
|
||||
modifs.href = `tableau_assiduite_actions?type=assiduite&action=modifier&obj_id=${assiduite.assiduite_id}`;
|
||||
|
||||
const actionsDiv = document.createElement("div");
|
||||
actionsDiv.className = "assiduite-actions";
|
||||
actionsDiv.appendChild(modifs);
|
||||
actionsDiv.appendChild(infos);
|
||||
bubble.appendChild(actionsDiv);
|
||||
|
||||
const idDiv = document.createElement("div");
|
||||
idDiv.className = "assiduite-id";
|
||||
|
|
|
@ -391,11 +391,11 @@ class RowAssiJusti(tb.Row):
|
|||
multi_days = self.ligne["date_debut"].date() != self.ligne["date_fin"].date()
|
||||
|
||||
date_affichees: list[str] = [
|
||||
self.ligne["date_debut"].strftime("%d/%m/%y de %H:%M"), # date début
|
||||
self.ligne["date_fin"].strftime("%d/%m/%y de %H:%M"), # date fin
|
||||
self.ligne["date_debut"].strftime("%d/%m/%y %H:%M"), # date début
|
||||
self.ligne["date_fin"].strftime("%d/%m/%y %H:%M"), # date fin
|
||||
]
|
||||
|
||||
if multi_days:
|
||||
if multi_days and self.ligne["type"] != "justificatif":
|
||||
date_affichees[0] = self.ligne["date_debut"].strftime("%d/%m/%y")
|
||||
date_affichees[1] = self.ligne["date_fin"].strftime("%d/%m/%y")
|
||||
|
||||
|
|
|
@ -74,8 +74,6 @@ div.submit > input {
|
|||
|
||||
<div>
|
||||
{{ form.date_fin.label }} : {{ form.date_fin }}
|
||||
<span class="help">si le jour de fin est différent,
|
||||
les heures seront ignorées (journées complètes)</span>
|
||||
{{ render_field_errors(form, 'date_fin') }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#actions {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
#actions label{
|
||||
margin: 0;
|
||||
|
@ -174,6 +174,48 @@ async function nouvellePeriode(period = null) {
|
|||
}
|
||||
}
|
||||
|
||||
// Vérification de la plage horaire
|
||||
// On génère une date de début et de fin de la période
|
||||
const date_debut = new Date(
|
||||
$("#date").datepicker("getDate").format("YYYY-MM-DD") + "T" + debut
|
||||
);
|
||||
const date_fin = new Date(
|
||||
$("#date").datepicker("getDate").format("YYYY-MM-DD") + "T" + fin
|
||||
);
|
||||
date_debut.add(1, "seconds");
|
||||
|
||||
// On vérifie que les dates sont valides
|
||||
if (!date_debut.isValid()){
|
||||
const p = document.createElement("p");
|
||||
p.textContent = "La date de début n'est pas valide.";
|
||||
openAlertModal(
|
||||
"Erreur",
|
||||
p,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!date_fin.isValid()){
|
||||
const p = document.createElement("p");
|
||||
p.textContent = "La date de fin n'est pas valide.";
|
||||
openAlertModal(
|
||||
"Erreur",
|
||||
p,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// On vérifie que l'heure de fin est supérieure à l'heure de début
|
||||
if (date_debut >= date_fin) {
|
||||
const p = document.createElement("p");
|
||||
p.textContent = "La plage horaire n'est pas valide. L'heure de fin doit être "+
|
||||
"supérieure à l'heure de début.";
|
||||
openAlertModal(
|
||||
"Erreur",
|
||||
p,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// On ajoute la nouvelle période au tableau
|
||||
let periodeDiv = document.createElement("div");
|
||||
periodeDiv.classList.add("cell", "header");
|
||||
|
@ -211,15 +253,6 @@ async function nouvellePeriode(period = null) {
|
|||
...document.querySelectorAll(".ligne[data-etudid]"),
|
||||
].map((e) => e.getAttribute("data-etudid"));
|
||||
|
||||
// On génère une date de début et de fin de la période
|
||||
const date_debut = new Date(
|
||||
$("#date").datepicker("getDate").format("YYYY-MM-DD") + "T" + debut
|
||||
);
|
||||
const date_fin = new Date(
|
||||
$("#date").datepicker("getDate").format("YYYY-MM-DD") + "T" + fin
|
||||
);
|
||||
date_debut.add(1, "seconds");
|
||||
|
||||
// Préparation de la requête
|
||||
const url =
|
||||
`../../api/assiduites/group/query?date_debut=${date_debut.toFakeIso()}` +
|
||||
|
|
|
@ -54,7 +54,8 @@
|
|||
}
|
||||
|
||||
document.getElementById("pdp").addEventListener("change", (e) => {
|
||||
creerTousLesEtudiants(etuds);
|
||||
afficherPDP(e.target.checked);
|
||||
//creerTousLesEtudiants(etuds);
|
||||
});
|
||||
|
||||
$('#date').on('change', async function(d) {
|
||||
|
@ -87,6 +88,8 @@
|
|||
}
|
||||
creerTousLesEtudiants(etuds);
|
||||
|
||||
// affichage ou non des PDP
|
||||
afficherPDP(localStorage.getItem("scodoc-etud-pdp") == "true" )
|
||||
}
|
||||
|
||||
setTimeout(main, 0);
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<h2>Calcul automatique des décisions de jury du BUT</h2>
|
||||
<ul>
|
||||
<li>N'enregistre jamais de décisions de l'année scolaire précédente, même
|
||||
si on a des RCUE "à cheval" sur deux années.
|
||||
si on a des RCUEs "à cheval" sur deux années.
|
||||
</li>
|
||||
|
||||
<li><b>Attention: peut modifier des décisions déjà enregistrées</b>, si la
|
||||
|
@ -47,6 +47,20 @@
|
|||
|
||||
</div>
|
||||
|
||||
{% if formsemestres_suspects %}
|
||||
<div class="scobox">
|
||||
<div class="scobox-title">Attention</div>
|
||||
<div class="warning">Les semestres pairs suivants vont être pris en compte, mais ils sont postérieurs et n'ont pas leurs moyennes bloquées
|
||||
(voir la documentation sur les jurys BUT).
|
||||
</div>
|
||||
<ul>
|
||||
{% for formsemestre in formsemestres_suspects.values() %}
|
||||
<li>{{ formsemestre.html_link_status() | safe }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
{{ wtf.quick_form(form) }}
|
||||
|
|
|
@ -28,9 +28,9 @@
|
|||
<a href="{{url_for('notes.formsemestre_change_publication_bul', scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=sco.sem.id)}}">
|
||||
{% if sco.sem.bul_hide_xml %}
|
||||
{{ scu.icontag("hide_img", border="0", title="Bulletins NON publiés sur la passerelle étudiants")|safe}}
|
||||
{{ scu.ICON_HIDDEN|safe}}
|
||||
{% else %}
|
||||
{{ scu.icontag("eye_img", border="0", title="Bulletins publiés sur la passerelle étudiants")|safe }}
|
||||
{{ scu.ICON_PUBLISHED|safe }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</span>
|
||||
|
|
|
@ -31,9 +31,9 @@
|
|||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id )
|
||||
}}">{%-
|
||||
if formsemestre.bul_hide_xml -%}
|
||||
{{scu.icontag("hide_img", border="0", title="Bulletins NON publiés sur la passerelle étudiants")|safe}}
|
||||
{{scu.ICON_HIDDEN|safe}}
|
||||
{%- else -%}
|
||||
{{scu.icontag("eye_img", border="0", title="Bulletins publiés sur la passerelle étudiants")|safe}}
|
||||
{{scu.ICON_PUBLISHED|safe}}
|
||||
{%- endif -%}
|
||||
</a></span>
|
||||
</div>
|
||||
|
|
|
@ -16,9 +16,13 @@ table.listesems tr td.titresem {
|
|||
font-weight: bold;
|
||||
font-size: 110%;
|
||||
}
|
||||
div.semlist {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
table.semlist tr td.datesem {
|
||||
font-size: 80%;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.semlist tr td.semestre_id_n {
|
||||
|
@ -28,6 +32,124 @@ table.semlist tr td.nb_inscrits {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S-1 td {
|
||||
background-color:rgb(176, 214, 226);
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S1 td {
|
||||
background-color:#e9efef;
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S2 td {
|
||||
background-color: #d4ebd7;
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S3 td {
|
||||
background-color: #bedebe;
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_S4 td {
|
||||
background-color: #afd7ad;
|
||||
}
|
||||
div#gtrcontent table.semlist tbody tr.css_S5 td {
|
||||
background-color: #a0cd9a;
|
||||
}
|
||||
div#gtrcontent table.semlist tbody tr.css_S6 td {
|
||||
background-color: #7dcf78;
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tbody tr.css_MEXT td {
|
||||
color: #fefcdf;
|
||||
}
|
||||
|
||||
table.semlist tr td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
table.semlist tbody tr a.stdlink,
|
||||
table.semlist tbody tr a.stdlink:visited {
|
||||
color: navy;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div#gtrcontent table.semlist tr a.stdlink:hover {
|
||||
color: red;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table.semlist tbody tr td.modalite {
|
||||
text-align: left;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
div.modalite {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
span.effectif {
|
||||
display: inline-block;
|
||||
min-width: 24px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cur-formsemestres {
|
||||
width: max-content; /* Fits content, but respects max-width */
|
||||
max-width: 1024px; /* Maximum width */
|
||||
margin: 0 auto 0 0; /* Centers divs if they are narrower than 1024px */
|
||||
}
|
||||
.cur-formsemestre {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-left: 1px solid #ddd;
|
||||
border-right: 1px solid #ddd;;
|
||||
background-color: rgb(246, 255, 254);
|
||||
padding: 0px 8px 0px 8px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.cur-formsemestre.new-sem {
|
||||
margin-top: 8px;
|
||||
border-top: 1px solid #ddd;;
|
||||
}
|
||||
|
||||
.left-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cur-formsemestre .title {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin: 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.right-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
}
|
||||
.responsable {
|
||||
font-weight: bold;
|
||||
color: navy;
|
||||
}
|
||||
|
||||
|
||||
=======
|
||||
>>>>>>> b1055a4ebe841f17860d2556cc4b03aa12ec3ab1
|
||||
</style>
|
||||
|
||||
{# News #}
|
||||
|
@ -48,7 +170,43 @@ table.semlist tr td.nb_inscrits {
|
|||
|
||||
{# Les semestres courants (cad non verrouillés) #}
|
||||
<div class="scobox">
|
||||
{{html_current_formsemestres|safe}}
|
||||
{% if current_formsemestres.count() == 0 %}
|
||||
<div class="scobox-title">Aucune session en cours !</div>
|
||||
<p>Pour ajouter une session, aller dans <a href="Notes" id="link-programmes">Formations</a>,
|
||||
choisissez une formation, puis suivez le lien "<em>UE, modules, semestres</em>".
|
||||
</p>
|
||||
<p>Là, en bas de page, suivez le lien
|
||||
"<em>Mettre en place un nouveau semestre de formation...</em>"
|
||||
</p>
|
||||
{% else %}
|
||||
<div class="scobox-title">Sessions en cours</div>
|
||||
<div class="cur-formsemestres">
|
||||
{% for modalite in modalites %}
|
||||
{% if modalites|length > 1 %}
|
||||
<div class="modalite">{{modalite.titre}}</div>
|
||||
{% endif %}
|
||||
{% for formsemestre in current_formsemestres_by_modalite[modalite.modalite] %}
|
||||
<div class="cur-formsemestre {{'new-sem' if loop.first or formsemestre.semestre_id != loop.previtem.semestre_id}}">
|
||||
<div class="left-section">
|
||||
{{groupicon|safe if formsemestre.inscriptions|length else emptygroupicon|safe}}
|
||||
<div class="date">
|
||||
<div class="date-begin"><a title="{{formsemestre.session_id()}}">{{formsemestre.mois_debut()}}</a></div>
|
||||
<div class="date-end">{{formsemestre.mois_fin()}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title">
|
||||
<a class="stdlink" href="{{ url_for("notes.formsemestre_status",
|
||||
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)}}">{{formsemestre.titre_num()}}</a>
|
||||
</div>
|
||||
<div class="right-section">
|
||||
<div class="responsable">{{formsemestre.responsables_str()}}</div>
|
||||
<div class="effectif">{{formsemestre.inscriptions|length}} étuds</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Table de tous les semestres #}
|
||||
|
@ -62,7 +220,9 @@ table.semlist tr td.nb_inscrits {
|
|||
url_for('scolar.export_table_dept_formsemestres', scodoc_dept=g.scodoc_dept)
|
||||
}}">{{scu.ICON_XLS|safe}}</a>
|
||||
</summary>
|
||||
{{ html_table_formsemestres|safe }}
|
||||
<div class="semlist">
|
||||
{{ html_table_formsemestres|safe }}
|
||||
</div>
|
||||
</details>
|
||||
{% else %}
|
||||
<p><a class="stdlink" href="{{
|
||||
|
|
|
@ -86,7 +86,7 @@ span.calendarEdit {
|
|||
let etudiants = await fetchData("/ScoDoc/{{formsemestre.departement.acronym}}/api/formsemestre/" + formsemestre_id + "/resultats");
|
||||
|
||||
etudiants.sort((a, b) => {
|
||||
return a.nom_short.localeCompare(b.nom_short)
|
||||
return a.sort_key.localeCompare(b.sort_key)
|
||||
})
|
||||
|
||||
processDatas(partitions, etudiants);
|
||||
|
|
|
@ -299,7 +299,7 @@ def ajout_assiduite_etud() -> str | Response:
|
|||
|
||||
def _get_dates_from_assi_form(
|
||||
form: AjoutAssiOrJustForm,
|
||||
all_day: bool = False,
|
||||
from_justif: bool = False,
|
||||
) -> tuple[
|
||||
bool, datetime.datetime | None, datetime.datetime | None, datetime.datetime | None
|
||||
]:
|
||||
|
@ -327,32 +327,15 @@ def _get_dates_from_assi_form(
|
|||
date_fin = None
|
||||
form.set_error("date fin invalide", form.date_fin)
|
||||
|
||||
if date_fin:
|
||||
# ignore les heures si plusieurs jours
|
||||
|
||||
# Assiduité : garde les heures inscritent dans le formulaire
|
||||
# Justificatif : ignore les heures inscrites dans le formulaire (0h -> 23h59)
|
||||
|
||||
heure_debut = (
|
||||
datetime.time.fromisoformat(debut_jour)
|
||||
if not all_day
|
||||
else datetime.time(0, 0, 0)
|
||||
) # 0h ou ConfigAssiduite.MorningTime
|
||||
heure_fin = (
|
||||
datetime.time.fromisoformat(fin_jour)
|
||||
if not all_day
|
||||
else datetime.time(23, 59, 59)
|
||||
) # 23h59 ou ConfigAssiduite.AfternoonTime
|
||||
if not from_justif and date_fin:
|
||||
# Ne prends pas en compte les heures pour les assiduités sur plusieurs jours
|
||||
heure_debut = datetime.time.fromisoformat(debut_jour)
|
||||
heure_fin = datetime.time.fromisoformat(fin_jour)
|
||||
else:
|
||||
try:
|
||||
if all_day:
|
||||
heure_debut = datetime.time.fromisoformat(
|
||||
form.heure_debut.data or "00:00"
|
||||
)
|
||||
else:
|
||||
heure_debut = datetime.time.fromisoformat(
|
||||
form.heure_debut.data or debut_jour
|
||||
)
|
||||
heure_debut = datetime.time.fromisoformat(
|
||||
form.heure_debut.data or debut_jour
|
||||
)
|
||||
except ValueError:
|
||||
form.set_error("heure début invalide", form.heure_debut)
|
||||
if bool(form.heure_debut.data) != bool(form.heure_fin.data):
|
||||
|
@ -360,10 +343,7 @@ def _get_dates_from_assi_form(
|
|||
"Les deux heures début et fin doivent être spécifiées, ou aucune"
|
||||
)
|
||||
try:
|
||||
if all_day:
|
||||
heure_fin = datetime.time.fromisoformat(form.heure_fin.data or "23:59")
|
||||
else:
|
||||
heure_fin = datetime.time.fromisoformat(form.heure_fin.data or fin_jour)
|
||||
heure_fin = datetime.time.fromisoformat(form.heure_fin.data or fin_jour)
|
||||
except ValueError:
|
||||
form.set_error("heure fin invalide", form.heure_fin)
|
||||
|
||||
|
@ -398,6 +378,19 @@ def _get_dates_from_assi_form(
|
|||
# Ajoute time zone serveur
|
||||
dt_debut_tz_server = scu.TIME_ZONE.localize(dt_debut)
|
||||
dt_fin_tz_server = scu.TIME_ZONE.localize(dt_fin)
|
||||
|
||||
if from_justif:
|
||||
cas: list[bool] = [
|
||||
# cas 1 (date de fin vide et pas d'heure de début)
|
||||
not form.date_fin.data and not form.heure_debut.data,
|
||||
# cas 2 (date de fin et pas d'heures)
|
||||
form.date_fin.data != "" and not form.heure_debut.data,
|
||||
]
|
||||
|
||||
if any(cas):
|
||||
dt_debut_tz_server = dt_debut_tz_server.replace(hour=0, minute=0)
|
||||
dt_fin_tz_server = dt_fin_tz_server.replace(hour=23, minute=59)
|
||||
|
||||
dt_entry_date_tz_server = (
|
||||
scu.TIME_ZONE.localize(dt_entry_date) if dt_entry_date else None
|
||||
)
|
||||
|
@ -753,6 +746,34 @@ def ajout_justificatif_etud():
|
|||
)
|
||||
|
||||
|
||||
def _verif_date_form_justif(
|
||||
form: AjoutJustificatifEtudForm, deb: datetime.datetime, fin: datetime.datetime
|
||||
) -> tuple[datetime.datetime, datetime.datetime]:
|
||||
"""Gère les cas suivants :
|
||||
- si on indique seulement une date de debut : journée 0h-23h59
|
||||
- si on indique date de debut et heures : journée +heure deb/fin
|
||||
(déjà géré par _get_dates_from_assi_form)
|
||||
- Si on indique une date de début et de fin sans heures : Journées 0h-23h59
|
||||
- Si on indique une date de début et de fin avec heures : On fait un objet avec
|
||||
datedeb/heuredeb + datefin/heurefin (déjà géré par _get_dates_from_assi_form)
|
||||
"""
|
||||
|
||||
cas: list[bool] = [
|
||||
# cas 1
|
||||
not form.date_fin.data and not form.heure_debut.data,
|
||||
# cas 3
|
||||
form.date_fin.data != "" and not form.heure_debut.data,
|
||||
]
|
||||
|
||||
if any(cas):
|
||||
deb = deb.replace(hour=0, minute=0)
|
||||
fin = fin.replace(hour=23, minute=59)
|
||||
|
||||
print(f"DEBUG {cas=}")
|
||||
|
||||
return deb, fin
|
||||
|
||||
|
||||
def _record_justificatif_etud(
|
||||
etud: Identite, form: AjoutJustificatifEtudForm, justif: Justificatif | None = None
|
||||
) -> bool:
|
||||
|
@ -769,7 +790,7 @@ def _record_justificatif_etud(
|
|||
dt_debut_tz_server,
|
||||
dt_fin_tz_server,
|
||||
dt_entry_date_tz_server,
|
||||
) = _get_dates_from_assi_form(form, all_day=True)
|
||||
) = _get_dates_from_assi_form(form, from_justif=True)
|
||||
if not ok:
|
||||
log("_record_justificatif_etud: dates invalides")
|
||||
form.set_error("Erreur: dates invalides")
|
||||
|
@ -831,11 +852,6 @@ def _record_justificatif_etud(
|
|||
db.session.rollback()
|
||||
return False
|
||||
db.session.commit()
|
||||
# FIX TEMPORAIRE:
|
||||
# on reprend toutes les assiduités et tous les justificatifs
|
||||
# pour utiliser le "reset" (remise en "non_just") des assiduités
|
||||
# (à terme, il faudrait ne recalculer que les assiduités impactées)
|
||||
# VOIR TODO dans compute_assiduites_justified
|
||||
justif.justifier_assiduites()
|
||||
scass.simple_invalidate_cache(justif.to_dict(), etud.id)
|
||||
flash(message)
|
||||
|
|
|
@ -30,6 +30,7 @@ Module notes: issu de ScoDoc7 / ZNotes.py
|
|||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
import datetime
|
||||
import html
|
||||
from operator import itemgetter
|
||||
import time
|
||||
|
@ -2392,17 +2393,16 @@ def formsemestre_validation_but(
|
|||
<div class="bull_head">
|
||||
<div>
|
||||
<div class="titre_parcours">Jury BUT</div>
|
||||
<div class="nom_etud">{etud.nomprenom}</div>
|
||||
<div class="nom_etud">{etud.html_link_fiche()}</div>
|
||||
</div>
|
||||
<div class="bull_photo"><a href="{
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||
etud.url_fiche()
|
||||
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="warning">Impossible de statuer sur cet étudiant:
|
||||
il est démissionnaire ou défaillant (voir <a class="stdlink" href="{
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||
}">sa fiche</a>)
|
||||
etud.url_fiche()}">sa fiche</a>)
|
||||
</div>
|
||||
</div>
|
||||
{navigation_div}
|
||||
|
@ -2449,22 +2449,38 @@ def formsemestre_validation_but(
|
|||
warning += (
|
||||
"""<div class="warning">L'étudiant n'est pas inscrit à un parcours.</div>"""
|
||||
)
|
||||
if formsemestre.date_fin - datetime.date.today() > datetime.timedelta(days=12):
|
||||
# encore loin de la fin du semestre de départ de ce jury ?
|
||||
warning += f"""<div class="warning">Le semestre S{formsemestre.semestre_id}
|
||||
terminera le {formsemestre.date_fin.strftime("%d/%m/%Y")} :
|
||||
êtes-vous certain de vouloir enregistrer une décision de jury ?
|
||||
</div>"""
|
||||
|
||||
if deca.formsemestre_impair:
|
||||
inscription = deca.formsemestre_impair.etuds_inscriptions.get(etud.id)
|
||||
if (not inscription) or inscription.etat != scu.INSCRIT:
|
||||
etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
|
||||
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_impair.semestre_id}</div>"""
|
||||
warning += f"""<div class="warning">{etat_ins}
|
||||
en S{deca.formsemestre_impair.semestre_id}</div>"""
|
||||
|
||||
if deca.formsemestre_pair:
|
||||
inscription = deca.formsemestre_pair.etuds_inscriptions.get(etud.id)
|
||||
if (not inscription) or inscription.etat != scu.INSCRIT:
|
||||
etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
|
||||
warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_pair.semestre_id}</div>"""
|
||||
warning += f"""<div class="warning">{etat_ins}
|
||||
en S{deca.formsemestre_pair.semestre_id}</div>"""
|
||||
|
||||
if has_notes_en_attente:
|
||||
warning += f"""<div class="warning-bloquant">{etud.nomprenom} a des notes en ATTente.
|
||||
Vous devez régler cela avant de statuer en jury !</div>"""
|
||||
warning += f"""<div class="warning-bloquant">{etud.html_link_fiche()
|
||||
} a des notes en ATTente dans les modules suivants.
|
||||
Vous devez régler cela avant de statuer en jury !
|
||||
<ul class="modimpls_att">
|
||||
"""
|
||||
for modimpl in deca.get_modimpls_attente():
|
||||
warning += f"""<li><a href="{
|
||||
url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
|
||||
}" class="stdlink">{modimpl.module.code} {modimpl.module.titre_str()}</a></li>"""
|
||||
warning += "</ul></div>"
|
||||
if evaluations_a_debloquer:
|
||||
links_evals = [
|
||||
f"""<a class="stdlink" href="{url_for(
|
||||
|
@ -2477,6 +2493,8 @@ def formsemestre_validation_but(
|
|||
voir {", ".join(links_evals)}
|
||||
"""
|
||||
|
||||
if warning:
|
||||
warning = f"""<div class="jury_but_warning jury_but_box">{warning}</div>"""
|
||||
H.append(
|
||||
f"""
|
||||
<div>
|
||||
|
@ -2485,16 +2503,13 @@ def formsemestre_validation_but(
|
|||
<div class="titre_parcours">Jury BUT{deca.annee_but}
|
||||
- Parcours {(deca.parcour.libelle if deca.parcour else False) or "non spécifié"}
|
||||
- {deca.annee_scolaire_str()}</div>
|
||||
<div class="nom_etud">{etud.nomprenom}</div>
|
||||
<div class="nom_etud">{etud.html_link_fiche()}</div>
|
||||
</div>
|
||||
<div class="bull_photo"><a href="{
|
||||
url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||
}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
||||
etud.url_fiche()}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="jury_but_warning jury_but_box">
|
||||
{warning}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" class="jury_but_box" id="jury_but">
|
||||
|
@ -2614,13 +2629,17 @@ def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
|||
formsemestre_id=formsemestre_id,
|
||||
)
|
||||
)
|
||||
if not formsemestre.formation.is_apc():
|
||||
raise ScoValueError(
|
||||
"formsemestre_validation_auto_but est réservé aux formations APC"
|
||||
)
|
||||
|
||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
form = jury_but_forms.FormSemestreValidationAutoBUTForm()
|
||||
if request.method == "POST":
|
||||
if not form.cancel.data:
|
||||
nb_etud_modif = jury_but_validation_auto.formsemestre_validation_auto_but(
|
||||
formsemestre
|
||||
nb_etud_modif, _ = (
|
||||
jury_but_validation_auto.formsemestre_validation_auto_but(formsemestre)
|
||||
)
|
||||
flash(f"Décisions enregistrées ({nb_etud_modif} étudiants modifiés)")
|
||||
return redirect(
|
||||
|
@ -2631,9 +2650,25 @@ def formsemestre_validation_auto_but(formsemestre_id: int = None):
|
|||
mode_jury=1,
|
||||
)
|
||||
)
|
||||
# Avertissement si formsemestre impair
|
||||
formsemestres_suspects = {}
|
||||
if formsemestre.semestre_id % 2:
|
||||
_, decas = jury_but_validation_auto.formsemestre_validation_auto_but(
|
||||
formsemestre, dry_run=True
|
||||
)
|
||||
# regarde si il y a des semestres pairs postérieurs qui ne soient pas bloqués
|
||||
formsemestres_suspects = {
|
||||
deca.formsemestre_pair.id: deca.formsemestre_pair
|
||||
for deca in decas
|
||||
if deca.formsemestre_pair
|
||||
and deca.formsemestre_pair.date_debut > formsemestre.date_debut
|
||||
and not deca.formsemestre_pair.block_moyennes
|
||||
}
|
||||
|
||||
return render_template(
|
||||
"but/formsemestre_validation_auto_but.j2",
|
||||
form=form,
|
||||
formsemestres_suspects=formsemestres_suspects,
|
||||
sco=ScoData(formsemestre=formsemestre),
|
||||
title="Calcul automatique jury BUT",
|
||||
)
|
||||
|
|
|
@ -1365,6 +1365,15 @@ def etudident_edit_form():
|
|||
return _etudident_create_or_edit_form(edit=True)
|
||||
|
||||
|
||||
def _validate_date_naissance(val: str, field) -> bool:
|
||||
"vrai si date saisie valide"
|
||||
try:
|
||||
date_naissance = scu.convert_fr_date(val)
|
||||
except ScoValueError:
|
||||
return False
|
||||
return date_naissance < datetime.datetime.now()
|
||||
|
||||
|
||||
def _etudident_create_or_edit_form(edit):
|
||||
"Le formulaire HTML"
|
||||
H = [html_sco_header.sco_header()]
|
||||
|
@ -1506,8 +1515,7 @@ def _etudident_create_or_edit_form(edit):
|
|||
"title": "Date de naissance",
|
||||
"input_type": "date",
|
||||
"explanation": "j/m/a",
|
||||
"validator": lambda val, _: DMY_REGEXP.match(val)
|
||||
and (ndb.DateDMYtoISO(val) < datetime.date.today().isoformat()),
|
||||
"validator": _validate_date_naissance,
|
||||
},
|
||||
),
|
||||
("lieu_naissance", {"title": "Lieu de naissance", "size": 32}),
|
||||
|
@ -1779,7 +1787,7 @@ def _etudident_create_or_edit_form(edit):
|
|||
+ homonyms_html
|
||||
+ F
|
||||
)
|
||||
|
||||
tf[2]["date_naissance"] = scu.convert_fr_date(tf[2]["date_naissance"])
|
||||
if not edit:
|
||||
etud = sco_etud.create_etud(cnx, args=tf[2])
|
||||
etudid = etud["etudid"]
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
SCOVERSION = "9.6.953"
|
||||
<<<<<<< HEAD
|
||||
SCOVERSION = "9.6.955"
|
||||
=======
|
||||
SCOVERSION = "9.6.954"
|
||||
>>>>>>> b1055a4ebe841f17860d2556cc4b03aa12ec3ab1
|
||||
|
||||
SCONAME = "ScoDoc"
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
"etudid": 11,
|
||||
"code_nip": "11",
|
||||
"rang": "1",
|
||||
"nom_short": "FLEURY Ma.",
|
||||
"civilite_str": "Mme",
|
||||
"nom_disp": "FLEURY",
|
||||
"prenom": "MADELEINE",
|
||||
|
@ -51,6 +50,7 @@
|
|||
"moy_sae_15_3": "~",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
"sort_key": "fleury;madeleine",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
"partitions": {
|
||||
|
@ -61,7 +61,6 @@
|
|||
"etudid": 8,
|
||||
"code_nip": "NIP8",
|
||||
"rang": "2",
|
||||
"nom_short": "SAUNIER Ja.",
|
||||
"civilite_str": "M.",
|
||||
"nom_disp": "SAUNIER",
|
||||
"prenom": "JACQUES",
|
||||
|
@ -109,6 +108,7 @@
|
|||
"moy_sae_15_3": "~",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
"sort_key": "saunier;jacques",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
"partitions": {
|
||||
|
@ -119,7 +119,6 @@
|
|||
"etudid": 6,
|
||||
"code_nip": "NIP6",
|
||||
"rang": "3",
|
||||
"nom_short": "LENFANT Ma.",
|
||||
"civilite_str": "",
|
||||
"nom_disp": "LENFANT",
|
||||
"prenom": "MAXIME",
|
||||
|
@ -166,6 +165,7 @@
|
|||
"moy_sae_14_3": "05.70",
|
||||
"moy_sae_15_3": "~",
|
||||
"bac": "",
|
||||
"sort_key": "lenfant;maxime",
|
||||
"specialite": "",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
|
@ -177,7 +177,6 @@
|
|||
"etudid": 7,
|
||||
"code_nip": "7",
|
||||
"rang": "4",
|
||||
"nom_short": "CUNY Ca.",
|
||||
"civilite_str": "",
|
||||
"nom_disp": "CUNY",
|
||||
"prenom": "CAMILLE",
|
||||
|
@ -225,6 +224,7 @@
|
|||
"moy_sae_15_3": "~",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
"sort_key": "cuny;camille",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
"partitions": {
|
||||
|
@ -235,7 +235,6 @@
|
|||
"etudid": 12,
|
||||
"code_nip": "NIP12",
|
||||
"rang": "5",
|
||||
"nom_short": "MOUTON Cl.",
|
||||
"civilite_str": "M.",
|
||||
"nom_disp": "MOUTON",
|
||||
"prenom": "CLAUDE",
|
||||
|
@ -282,6 +281,7 @@
|
|||
"moy_sae_14_3": "11.09",
|
||||
"moy_sae_15_3": "~",
|
||||
"bac": "",
|
||||
"sort_key": "mouton;claude",
|
||||
"specialite": "",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
|
@ -293,7 +293,6 @@
|
|||
"etudid": 3,
|
||||
"code_nip": "3",
|
||||
"rang": "6",
|
||||
"nom_short": "R\u00c9GNIER Pa.",
|
||||
"civilite_str": "M.",
|
||||
"nom_disp": "R\u00c9GNIER",
|
||||
"prenom": "PATRICK",
|
||||
|
@ -341,6 +340,7 @@
|
|||
"moy_sae_15_3": "~",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
"sort_key": "regnier;patrick",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
"partitions": {
|
||||
|
@ -351,7 +351,6 @@
|
|||
"etudid": 13,
|
||||
"code_nip": "13",
|
||||
"rang": "7",
|
||||
"nom_short": "ESTEVE Al.",
|
||||
"civilite_str": "",
|
||||
"nom_disp": "ESTEVE",
|
||||
"prenom": "ALIX",
|
||||
|
@ -398,6 +397,7 @@
|
|||
"moy_sae_14_3": "05.17",
|
||||
"moy_sae_15_3": "~",
|
||||
"bac": "",
|
||||
"sort_key": "esteve;alix",
|
||||
"specialite": "",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
|
@ -409,7 +409,6 @@
|
|||
"etudid": 16,
|
||||
"code_nip": "NIP16",
|
||||
"rang": "8",
|
||||
"nom_short": "GILLES Ma.",
|
||||
"civilite_str": "",
|
||||
"nom_disp": "GILLES",
|
||||
"prenom": "MAXIME",
|
||||
|
@ -456,6 +455,7 @@
|
|||
"moy_sae_14_3": "03.32",
|
||||
"moy_sae_15_3": "~",
|
||||
"bac": "",
|
||||
"sort_key": "gilles;maxime",
|
||||
"specialite": "",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
|
@ -467,7 +467,6 @@
|
|||
"etudid": 2,
|
||||
"code_nip": "NIP2",
|
||||
"rang": "9",
|
||||
"nom_short": "NAUDIN Si.",
|
||||
"civilite_str": "Mme",
|
||||
"nom_disp": "NAUDIN",
|
||||
"prenom": "SIMONE",
|
||||
|
@ -514,6 +513,7 @@
|
|||
"moy_sae_14_3": "02.10",
|
||||
"moy_sae_15_3": "~",
|
||||
"bac": "",
|
||||
"sort_key": "naudin;simone",
|
||||
"specialite": "",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
|
@ -525,7 +525,6 @@
|
|||
"etudid": 1,
|
||||
"code_nip": "1",
|
||||
"rang": "10",
|
||||
"nom_short": "COSTA Sa.",
|
||||
"civilite_str": "",
|
||||
"nom_disp": "COSTA",
|
||||
"prenom": "SACHA",
|
||||
|
@ -572,6 +571,7 @@
|
|||
"moy_sae_14_3": "07.17",
|
||||
"moy_sae_15_3": "~",
|
||||
"bac": "",
|
||||
"sort_key": "costa;sacha",
|
||||
"specialite": "",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
|
@ -583,7 +583,6 @@
|
|||
"etudid": 4,
|
||||
"code_nip": "NIP4",
|
||||
"rang": "11 ex",
|
||||
"nom_short": "GAUTIER G\u00e9.",
|
||||
"civilite_str": "M.",
|
||||
"nom_disp": "GAUTIER",
|
||||
"prenom": "G\u00c9RARD",
|
||||
|
@ -630,6 +629,7 @@
|
|||
"moy_sae_14_3": "",
|
||||
"moy_sae_15_3": "",
|
||||
"bac": "",
|
||||
"sort_key": "gautier;gerard",
|
||||
"specialite": "",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
|
@ -641,7 +641,6 @@
|
|||
"etudid": 5,
|
||||
"code_nip": "5",
|
||||
"rang": "11 ex",
|
||||
"nom_short": "VILLENEUVE Fr.",
|
||||
"civilite_str": "Mme",
|
||||
"nom_disp": "VILLENEUVE",
|
||||
"prenom": "FRAN\u00c7OISE",
|
||||
|
@ -688,6 +687,7 @@
|
|||
"moy_sae_14_3": "",
|
||||
"moy_sae_15_3": "",
|
||||
"bac": "",
|
||||
"sort_key": "villeneuve;francoise",
|
||||
"specialite": "",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
|
@ -699,7 +699,6 @@
|
|||
"etudid": 9,
|
||||
"code_nip": "9",
|
||||
"rang": "11 ex",
|
||||
"nom_short": "SCHMITT Em.",
|
||||
"civilite_str": "M.",
|
||||
"nom_disp": "SCHMITT",
|
||||
"prenom": "EMMANUEL",
|
||||
|
@ -746,6 +745,7 @@
|
|||
"moy_sae_14_3": "",
|
||||
"moy_sae_15_3": "",
|
||||
"bac": "",
|
||||
"sort_key": "schmitt;emmanuel",
|
||||
"specialite": "",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
|
@ -757,7 +757,6 @@
|
|||
"etudid": 10,
|
||||
"code_nip": "NIP10",
|
||||
"rang": "11 ex",
|
||||
"nom_short": "BOUTET Ma.",
|
||||
"civilite_str": "Mme",
|
||||
"nom_disp": "BOUTET",
|
||||
"prenom": "MARGUERITE",
|
||||
|
@ -804,6 +803,7 @@
|
|||
"moy_sae_14_3": "",
|
||||
"moy_sae_15_3": "",
|
||||
"bac": "",
|
||||
"sort_key": "boutet;marguerite",
|
||||
"specialite": "",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
|
@ -815,7 +815,6 @@
|
|||
"etudid": 14,
|
||||
"code_nip": "NIP14",
|
||||
"rang": "11 ex",
|
||||
"nom_short": "ROLLIN De.",
|
||||
"civilite_str": "M.",
|
||||
"nom_disp": "ROLLIN",
|
||||
"prenom": "DERC'HEN",
|
||||
|
@ -863,6 +862,7 @@
|
|||
"moy_sae_15_3": "",
|
||||
"bac": "",
|
||||
"specialite": "",
|
||||
"sort_key": "rollin;derchen",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
"partitions": {
|
||||
|
@ -873,7 +873,6 @@
|
|||
"etudid": 15,
|
||||
"code_nip": "15",
|
||||
"rang": "11 ex",
|
||||
"nom_short": "DIOT Ca.",
|
||||
"civilite_str": "",
|
||||
"nom_disp": "DIOT",
|
||||
"prenom": "CAMILLE",
|
||||
|
@ -920,6 +919,7 @@
|
|||
"moy_sae_14_3": "",
|
||||
"moy_sae_15_3": "",
|
||||
"bac": "",
|
||||
"sort_key": "diot;camille",
|
||||
"specialite": "",
|
||||
"type_admission": "",
|
||||
"classement": "",
|
||||
|
|
Loading…
Reference in New Issue
Block a user