diff --git a/app/but/jury_but.py b/app/but/jury_but.py index 3f8fa9a7a912f1353e1c8ef38d2381f4eb531026..fe670d70e252716b61c0bbf7d50154aad56e3922 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -77,7 +77,7 @@ from app.models.but_refcomp import ( ApcNiveau, ApcParcours, ) -from app.models import Scolog, ScolarAutorisationInscription +from app.models import Evaluation, Scolog, ScolarAutorisationInscription from app.models.but_validations import ( ApcValidationAnnee, ApcValidationRCUE, @@ -260,11 +260,11 @@ class DecisionsProposeesAnnee(DecisionsProposees): else [] ) # ---- Niveaux et RCUEs - niveaux_by_parcours = ( - formsemestre.formation.referentiel_competence.get_niveaux_by_parcours( - self.annee_but, [self.parcour] if self.parcour else None - )[1] - ) + niveaux_by_parcours = formsemestre.formation.referentiel_competence.get_niveaux_by_parcours( + self.annee_but, [self.parcour] if self.parcour else None + )[ + 1 + ] self.niveaux_competences = niveaux_by_parcours["TC"] + ( niveaux_by_parcours[self.parcour.id] if self.parcour else [] ) @@ -358,13 +358,17 @@ class DecisionsProposeesAnnee(DecisionsProposees): # self.codes = [] # pas de décision annuelle sur semestres impairs elif self.inscription_etat != scu.INSCRIT: self.codes = [ - sco_codes.DEM - if self.inscription_etat == scu.DEMISSION - else sco_codes.DEF, + ( + sco_codes.DEM + if self.inscription_etat == scu.DEMISSION + else sco_codes.DEF + ), # propose aussi d'autres codes, au cas où... - sco_codes.DEM - if self.inscription_etat != scu.DEMISSION - else sco_codes.DEF, + ( + sco_codes.DEM + if self.inscription_etat != scu.DEMISSION + else sco_codes.DEF + ), sco_codes.ABAN, sco_codes.ABL, sco_codes.EXCLU, @@ -595,11 +599,9 @@ class DecisionsProposeesAnnee(DecisionsProposees): # Ordonne par numéro d'UE niv_rcue = sorted( self.rcue_by_niveau.items(), - key=lambda x: x[1].ue_1.numero - if x[1].ue_1 - else x[1].ue_2.numero - if x[1].ue_2 - else 0, + key=lambda x: ( + x[1].ue_1.numero if x[1].ue_1 else x[1].ue_2.numero if x[1].ue_2 else 0 + ), ) return { niveau_id: DecisionsProposeesRCUE(self, rcue, self.inscription_etat) @@ -816,9 +818,15 @@ class DecisionsProposeesAnnee(DecisionsProposees): Return: True si au moins un code modifié et enregistré. """ modif = False - # Vérification notes en attente dans formsemestre origine - if only_validantes and self.has_notes_en_attente(): - return False + if only_validantes: + if self.has_notes_en_attente(): + # notes en attente dans formsemestre origine + return False + if Evaluation.get_evaluations_blocked_for_etud( + self.formsemestre, self.etud + ): + # évaluation(s) qui seront débloquées dans le futur + return False # Toujours valider dans l'ordre UE, RCUE, Année annee_scolaire = self.formsemestre.annee_scolaire() @@ -1488,9 +1496,11 @@ class DecisionsProposeesUE(DecisionsProposees): self.validation = None # cache toute validation self.explanation = "non inscrit (dem. ou déf.)" self.codes = [ - sco_codes.DEM - if res.get_etud_etat(etud.id) == scu.DEMISSION - else sco_codes.DEF + ( + sco_codes.DEM + if res.get_etud_etat(etud.id) == scu.DEMISSION + else sco_codes.DEF + ) ] return diff --git a/app/models/evaluations.py b/app/models/evaluations.py index cea6c62e0d4b743aa4fc5984af5f860c77e9116a..f58560ff917d86c86392531f6764a43b4c0168a0 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -488,6 +488,29 @@ class Evaluation(models.ScoDocModel): """ return NotesNotes.query.filter_by(etudid=etud.id, evaluation_id=self.id).first() + @classmethod + def get_evaluations_blocked_for_etud( + cls, formsemestre, etud: Identite + ) -> list["Evaluation"]: + """Liste des évaluations de ce semestre avec note pour cet étudiant et date blocage + et date blocage < FOREVER. + Si non vide, une note apparaitra dans le futur pour cet étudiant: il faut + donc interdire la saisie du jury. + """ + now = datetime.datetime.now(scu.TIME_ZONE) + return ( + Evaluation.query.filter( + Evaluation.blocked_until != None, # pylint: disable=C0121 + Evaluation.blocked_until >= now, + ) + .join(ModuleImpl) + .filter_by(formsemestre_id=formsemestre.id) + .join(ModuleImplInscription) + .filter_by(etudid=etud.id) + .join(NotesNotes) + .all() + ) + class EvaluationUEPoids(db.Model): """Poids des évaluations (BUT) @@ -657,3 +680,6 @@ def _moduleimpl_evaluation_insert_before( db.session.add(e) db.session.commit() return n + + +from app.models.moduleimpls import ModuleImpl, ModuleImplInscription diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index 7c68ddf9fce19af1a205c3d5e1b68440ff1d7ec2..df4770fa3c4a7d540aeecfd4c14150ef67886dd9 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -34,7 +34,7 @@ from flask import url_for, flash, g, request from flask_login import current_user import sqlalchemy as sa -from app.models.etudiants import Identite +from app.models import Identite, Evaluation import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu from app import db, log @@ -232,7 +232,9 @@ def formsemestre_validation_etud_form( H.append( tf_error_message( f"""Impossible de statuer sur cet étudiant: il a des notes en - attente dans des évaluations de ce semestre (voir <a href="{ + attente dans des évaluations de ce semestre (voir + <a class="stdlink" + href="{ url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id) }">tableau de bord</a>) @@ -241,6 +243,26 @@ def formsemestre_validation_etud_form( ) return "\n".join(H + footer) + evaluations_a_debloquer = Evaluation.get_evaluations_blocked_for_etud( + formsemestre, etud + ) + if evaluations_a_debloquer: + links_evals = [ + f"""<a class="stdlink" href="{url_for( + 'notes.evaluation_listenotes', scodoc_dept=g.scodoc_dept, evaluation_id=e.id + )}">{e.description} en {e.moduleimpl.module.code}</a>""" + for e in evaluations_a_debloquer + ] + H.append( + tf_error_message( + f"""Impossible de statuer sur cet étudiant: + il a des notes dans des évaluations qui seront débloquées plus tard: + voir {", ".join(links_evals)} + """ + ) + ) + return "\n".join(H + footer) + # Infos si pas de semestre précédent if not Se.prev: if Se.sem["semestre_id"] == 1: diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index 23e0b42e74941040e68cb6babc3efe529cc88501..4dbebb4feaad5b33b8ed58981af4dc54a84a9af5 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -3391,14 +3391,24 @@ li.tf-msg { padding-bottom: 5px; } -.warning { - font-weight: bold; +.warning, .warning-bloquant { color: red; + margin-left: 16px; + margin-bottom: 8px; + min-width: var(--sco-content-min-width); + max-width: var(--sco-content-max-width); } .warning::before { - content: url(/ScoDoc/static/icons/warning_img.png); - vertical-align: -80%; + content:""; + margin-right: 8px; + height:32px; + width: 32px; + background-size: 32px 32px; + background-image: url(/ScoDoc/static/icons/warning_std.svg); + background-repeat: no-repeat; + display: inline-block; + vertical-align: -40%; } .warning-light { @@ -3411,6 +3421,19 @@ li.tf-msg { /* EMO_WARNING, "⚠️" */ } +.warning-bloquant::before { + content:""; + margin-right: 8px; + height:32px; + width: 32px; + background-size: 32px 32px; + background-image: url(/ScoDoc/static/icons/warning_bloquant.svg); + background-repeat: no-repeat; + display: inline-block; + vertical-align: -40%; +} + + p.error { font-weight: bold; color: red; diff --git a/app/static/icons/warning_bloquant.svg b/app/static/icons/warning_bloquant.svg new file mode 100644 index 0000000000000000000000000000000000000000..81b4416e3da0da9636a97feebe13e4274404a089 --- /dev/null +++ b/app/static/icons/warning_bloquant.svg @@ -0,0 +1 @@ +<svg height="512" viewBox="0 0 60 60" width="512" xmlns="http://www.w3.org/2000/svg"><g id="Page-1" fill="none" fill-rule="evenodd"><g id="018---No-Termites" fill-rule="nonzero"><path id="Shape" d="m30 1c-16.0162577 0-29 12.9837423-29 29s12.9837423 29 29 29 29-12.9837423 29-29-12.9837423-29-29-29zm-25 29c.00097321-9.7485611 5.668525-18.6074081 14.5190052-22.69439279 8.8504803-4.08698469 19.2694168-2.65657431 26.6909948 3.66439279l-18.69 18.69-16.55 16.55c-3.85988084-4.5182251-5.97729805-10.2675271-5.97-16.21zm25 25c-5.9424729.007298-11.6917749-2.1101192-16.21-5.97l9.31-9.31 5.72-5.72 20.21-20.21c6.3209671 7.421578 7.7513775 17.8405145 3.6643928 26.6909948-4.0869847 8.8504802-12.9458317 14.518032-22.6943928 14.5190052z" fill="#f44335"/><path id="Shape" d="m30 1c-.5033333 0-1.0033333.01266667-1.5.038 15.4192225.79012792 27.5158989 13.5225465 27.5158989 28.962s-12.0966764 28.1718721-27.5158989 28.962c.4973333.0253333.9973333.038 1.5.038 16.0162576-.0000002 28.9999996-12.9837424 28.9999996-29s-12.983742-28.99999976-28.9999996-29z" fill="#c81e1e"/><path id="Shape" d="m36.89 20.29-5.65 5.65c-.4120737.0406713-.8259246.0606963-1.24.06-1.6837388.031295-3.352527-.3209288-4.88-1.03-1.32-.67-2.12-1.57-2.12-2.47-.0816968-1.8041136.2608048-3.6022468 1-5.25.4875684-1.4867671 1.3202892-2.83694 2.43-3.94.9756271-.8822162 2.255311-1.3517921 3.57-1.31 1.314689-.0417921 2.5943729.4277838 3.57 1.31 1.1097108 1.10306 1.9424316 2.4532329 2.43 3.94.4032643.979034.7015982 1.9980622.89 3.04z" fill="#a0522d"/><path id="Shape" d="m36 17.25c-.4875684-1.4867671-1.3202892-2.83694-2.43-3.94-.9756271-.8822162-2.255311-1.3517921-3.57-1.31-.5068631.002691-1.0111769.0719501-1.5.206.7690226.1933018 1.4794589.5708284 2.07 1.1 1.1102449 1.1042016 1.9430002 2.455801 2.43 3.944.4032643.979034.7015982 1.9980622.89 3.04l-5.59 5.589c.5634099.0774749 1.1312967.1178951 1.7.121.4140754.0006963.8279263-.0193287 1.24-.06l5.65-5.65c-.1884018-1.0419378-.4867357-2.060966-.89-3.04z" fill="#783e22"/><path id="Shape" d="m31.24 25.94-3.72 3.72c-2.59-.82-2.72-3.04-2.4-4.69 1.527473.7090712 3.1962612 1.061295 4.88 1.03.4140754.0006963.8279263-.0193287 1.24-.06z" fill="#91562d"/><path id="Shape" d="m30 26c-1.6837388.031295-3.352527-.3209288-4.88-1.03-.0551457.2847043-.0922198.5726129-.111.862.6582869.1076996 1.3239739.1638701 1.991.168.4140754.0006963.8279263-.0193287 1.24-.06l-2.54 2.536c.4706638.5722247 1.106128.9856256 1.82 1.184l3.72-3.72c-.4120737.0406713-.8259246.0606963-1.24.06z" fill="#6d4122"/><path id="Shape" d="m37 41.16c0 .28-.01.57-.03.84h-13.94c-.02-.27-.03-.56-.03-.84.0013406-.4816873.0347478-.962751.1-1.44l1.72-1.72h11.64c.3687814 1.0122318.5517174 2.0827462.54 3.16z" fill="#ff9801"/><path id="Shape" d="m36.46 38h-3c.3687814 1.0122318.5517174 2.0827462.54 3.16 0 .28-.01.57-.03.84h3c.02-.27.03-.56.03-.84.0117174-1.0772538-.1712186-2.1477682-.54-3.16z" fill="#f57c00"/><path id="Shape" d="m36.46 38h-11.64l4-4h4.18c1.6127662.8539838 2.8471855 2.281058 3.46 4z" fill="#a0522d"/><path id="Shape" d="m33 34h-3c1.6127662.8539838 2.8471855 2.281058 3.46 4h3c-.6128145-1.718942-1.8472338-3.1460162-3.46-4z" fill="#783e22"/><path id="Shape" d="m36.97 42c-.0671862 1.3722196-.3611635 2.7238393-.87 4h-12.2c-.5088365-1.2761607-.8028138-2.6277804-.87-4z" fill="#a0522d"/><path id="Shape" d="m33.97 42c-.0671862 1.3722196-.3611635 2.7238393-.87 4h3c.5088365-1.2761607.8028138-2.6277804.87-4z" fill="#783e22"/><path id="Shape" d="m36.1 46c-1.16 2.97-3.41 5-6.1 5s-4.94-2.03-6.1-5z" fill="#ff9801"/><path id="Shape" d="m33.1 46c-.72239 2.20782-2.4179924 3.9620313-4.6 4.759.4855851.1529973.9909277.234189 1.5.241 2.69 0 4.94-2.03 6.1-5z" fill="#f57c00"/><path id="Shape" d="m35 32c-.0081672 1.1011688-.8988312 1.9918328-2 2h-4.18l4-4h.18c1.1032019.0032948 1.9967052.8967981 2 2z" fill="#91562d"/><path id="Shape" d="m33 30h-.18l-1.064 1.064c.1574256.2870452.2412578.6086313.244.936-.0081672 1.1011688-.8988312 1.9918328-2 2h3c1.1011688-.0081672 1.9918328-.8988312 2-2-.0032948-1.1032019-.8967981-1.9967052-2-2z" fill="#6d4122"/><g fill="#000"><path id="Shape" d="m30 0c-16.5685425 0-30 13.4314575-30 30s13.4314575 30 30 30 30-13.4314575 30-30c-.018737-16.5607751-13.4392249-29.98126296-30-30zm0 58c-15.463973 0-28-12.536027-28-28s12.536027-28 28-28 28 12.536027 28 28c-.0176345 15.4566626-12.5433374 27.9823655-28 28z"/><path id="Shape" d="m47.209 10.93c-.0110433-.2786191-.1379147-.5399742-.35-.721-10.319918-8.77623759-25.6492103-8.15765771-35.228434 1.421566-9.57922371 9.5792237-10.19780359 24.908516-1.421566 35.228434.181259.2123356.4430282.339232.722.35h.039c.2651948-.0000566.5195073-.1054506.707-.293l35.24-35.24c.1970375-.1971108.3028764-.4675074.292-.746zm-20.109 3.122c.791-.7179055 1.8326894-1.0957873 2.9-1.052 1.0696105-.0431074 2.1131936.336606 2.905 1.057.9882341.9965544 1.7281655 2.2116175 2.16 3.547.3044352.7655123.5524311 1.5522809.742 2.354l-5 5c-.263.018-.525.039-.8.039-1.5261632.0299616-3.0394141-.2848575-4.427-.921-1.168-.587-1.58-1.216-1.58-1.576-.0791945-1.6818554.2408652-3.3585706.934-4.893.4324085-1.3388363 1.1744985-2.5568094 2.166-3.555zm-1.09 12.348c.9149874.2875845 1.8597342.4700924 2.816.544l-1.545 1.545c-.7984561-.3848655-1.2962223-1.2029879-1.271-2.089zm11.424-8.065c-.137-.434-.295-.9-.5-1.438-.4786135-1.3893709-1.2125321-2.6771199-2.164-3.797.813-2.276-.889-4.594-.971-4.7-.2143594-.28581247-.5649147-.43555473-.9196152-.39282032-.3547006.04273441-.6596589.27145311-.8.6-.1403412.32854688-.0947442.70700785.1196152.99282032.4436639.6266236.7166094 1.3579247.792 2.122-1.8792674-.963938-4.1077326-.963938-5.987 0 .0759883-.7645888.3503789-1.4960671.796-2.122.2143594-.28581247.2599564-.66427344.1196152-.99282032-.1403411-.32854689-.4452994-.55726559-.8-.6-.3547005-.04273441-.7052558.10700785-.9196152.39282032-.082.108-1.784 2.426-.971 4.7-.9514047 1.1210463-1.6852952 2.4097635-2.164 3.8-.782543 1.7594225-1.1470631 3.6761479-1.065 5.6.1183531 1.288151.8914076 2.4246846 2.046 3.008-.01.1-.034.212-.039.313l-4.249-.708-1.81-5.429c-.06346-.1932851-.1849817-.3622981-.348-.484l-4-3c-.4418278-.3313708-1.0686292-.2418278-1.4.2s-.2418278 1.0686292.2 1.4l3.75 2.813 1.9 5.7c.1171375.3512572.418779.6090376.784.67l5.445.908c.284308.8290817.8299654 1.5435244 1.555 2.036l-1.1 1.1-5.9.983c-.2056567.0341272-.3955061.1316741-.543.279l-3.786 3.786-4.7.941c-.5129335.0924078-.86888369.56328-.81790342 1.0819716.05098028.5186916.49179414.9112529 1.01290342.9020284.0671846.000387.1342224-.0063167.2-.02l5-1c.1937244-.0380671.3716632-.1331304.511-.273l3.773-3.773 2.822-.471-11.26 11.257c-7.41489066-9.5542352-6.56240906-23.1330174 1.9891893-31.6848698 8.5515984-8.55185229 22.1303553-9.40473714 31.6848107-1.9901302z"/><path id="Shape" d="m48.323 13.083-16.21 16.21-4 4-15.03 15.03c-.1972979.1972389-.3032331.467929-.2922194.7466912.0110138.2787622.1379751.5402507.3502194.7213088 10.319918 8.7762376 25.6492103 8.1576577 35.228434-1.421566s10.1978036-24.908516 1.421566-35.228434c-.181259-.2123356-.4430282-.339232-.722-.35-.2788475-.0130058-.5500742.093158-.746.292zm-23.089 25.917h10.5c.1685764.6535096.256894 1.3251256.263 2h-11.986c0-.269.029-.55.058-.835zm4-4h3.524c.8422091.4943116 1.5566891 1.1793355 2.086 2h-7.61zm6.166 10h-10.8c-.2172437-.6508995-.3742366-1.3203789-.469-2h11.735c-.0940841.6794702-.2500661 1.3489208-.466 2zm-.9 2c-.847567 1.7397962-2.568047 2.8867828-4.5 3-1.9298253-.1151196-3.6476359-1.2618556-4.494-3zm-.5-15c.0003954.2693445-.110308.526929-.306.712-.1823963.1865985-.4330776.2906276-.694.288h-1.766l1.966-1.961c.4612375.0893792.7957411.4912016.8.961zm20-2c-.0006796 9.1627579-5.2184286 17.5255903-13.4480746 21.554121s-18.0349577 3.0197039-25.2719254-2.600121l6.765-6.765c.0806656 1.4324131.3927024 2.8423195.924 4.175l.009.019.006.013c1.36 3.457 4.045 5.604 7.016 5.604s5.666-2.154 7.018-5.607v-.007-.008c.5498287-1.3713171.8681571-2.8244088.942-4.3 0-.024 0-.051 0-.075.018-.273.03-.559.03-.839.0041143-1.0311034-.1486326-2.0568342-.453-3.042l.623.5 1.9 4.756c.0911474.2275092.2626714.4136234.482.523l4 2c.3197527.1596977.7004141.1366785.9985928-.0603866.2981786-.1970651.4685741-.5382372.447-.895-.0215741-.3567629-.2318401-.6749157-.5515928-.8346134l-3.66-1.83-1.858-4.647c-.0635419-.1596545-.1668047-.3004361-.3-.409l-4.255-3.4c.1627127-.2051541.2972399-.4311598.4-.672l4.746.791 3.773 3.773c.1393368.1398696.3172756.2349329.511.273l5 1c.0684272.010888.1378419.0142414.207.01.5137531.0005163.9443106-.3883485.9959422-.8995008.0516315-.5111523-.2924636-.9782648-.7959422-1.0804992l-4.7-.941-3.786-3.786c-.1474939-.1473259-.3373433-.2448728-.543-.279l-5.308-.885c-.1942-.6480027-.6010914-1.2116619-1.155-1.6l14.246-14.249c3.2706748 4.2097894 5.0461 9.3889926 5.046 14.72z"/></g></g></g></svg> \ No newline at end of file diff --git a/app/static/icons/warning_std.svg b/app/static/icons/warning_std.svg new file mode 100644 index 0000000000000000000000000000000000000000..ae68b668ef4e49c445ba8aaf51edaac6861a3467 --- /dev/null +++ b/app/static/icons/warning_std.svg @@ -0,0 +1 @@ +<svg id="Capa_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m208.587 54.407-201.17 348.437c-21.073 36.499 5.268 82.122 47.413 82.122h402.34c42.145 0 68.486-45.624 47.413-82.122l-201.17-348.437c-21.072-36.498-73.754-36.498-94.826 0z" fill="#da4a54"/><path d="m54.83 443.76c-6.802 0-10.267-4.242-11.727-6.771s-3.401-7.65 0-13.542l201.17-348.436c3.401-5.891 8.807-6.771 11.727-6.771s8.326.88 11.727 6.771l201.17 348.436c3.401 5.891 1.46 11.013 0 13.541-1.46 2.529-4.925 6.771-11.727 6.771h-402.34z" fill="#f6e266"/><g fill="#544f57"><path d="m256 327.138c-14.379 0-26.036-11.657-26.036-26.036v-119.216c0-14.379 11.657-26.036 26.036-26.036 14.379 0 26.036 11.657 26.036 26.036v119.217c0 14.379-11.657 26.035-26.036 26.035z"/><circle cx="256" cy="381.152" r="26.036"/></g></svg> \ No newline at end of file diff --git a/app/views/notes.py b/app/views/notes.py index 1dfec6745e4ffe15984efb399df33bd77e426bda..96ae655564b39fbb776502bb76cec8ab32b0cdb0 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -2408,6 +2408,12 @@ def formsemestre_validation_but( ) deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre) + has_notes_en_attente = deca.has_notes_en_attente() + evaluations_a_debloquer = Evaluation.get_evaluations_blocked_for_etud( + formsemestre, etud + ) + if has_notes_en_attente or evaluations_a_debloquer: + read_only = True if request.method == "POST": if not read_only: deca.record_form(request.form) @@ -2452,9 +2458,21 @@ def formsemestre_validation_but( etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?") warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_pair.semestre_id}</div>""" - if deca.has_notes_en_attente(): - warning += f"""<div class="warning">{etud.nomprenom} a des notes en ATTente. - Vous devriez régler cela avant de statuer en jury !</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>""" + if evaluations_a_debloquer: + links_evals = [ + f"""<a class="stdlink" href="{url_for( + 'notes.evaluation_listenotes', scodoc_dept=g.scodoc_dept, evaluation_id=e.id + )}">{e.description} en {e.moduleimpl.module.code}</a>""" + for e in evaluations_a_debloquer + ] + warning += f"""<div class="warning-bloquant">Impossible de statuer sur cet étudiant: + il a des notes dans des évaluations qui seront débloquées plus tard: + voir {", ".join(links_evals)} + """ + H.append( f""" <div>