From 568c8681ba30e2706087d541c72b6c70541cc739 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 26 Nov 2023 18:28:56 +0100 Subject: [PATCH] =?UTF-8?q?Accepte=20codage=20boursiers=20O/N=20(USPN),=20?= =?UTF-8?q?fonction=20de=20resynchro=20globale,=20table=20de=20tous=20les?= =?UTF-8?q?=20=C3=A9tudiants=20courants=20avec=20=C3=A9tat=20boursier.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/departements.py | 22 +-- app/models/formsemestre.py | 23 ++- app/scodoc/sco_dept.py | 42 +++++- app/scodoc/sco_synchro_etuds.py | 18 ++- app/static/css/gt_table.css | 258 ++++++++++++++++++++++---------- app/static/css/scodoc.css | 4 +- app/tables/list_etuds.py | 75 +++++++++- app/views/scolar.py | 94 ++++++++---- 8 files changed, 397 insertions(+), 139 deletions(-) diff --git a/app/api/departements.py b/app/api/departements.py index 95a9c4e9a..7214032d1 100644 --- a/app/api/departements.py +++ b/app/api/departements.py @@ -7,7 +7,7 @@ """ ScoDoc 9 API : accès aux départements - Note: les routes /departement[s] sont publiées sur l'API (/ScoDoc/api/), + Note: les routes /departement[s] sont publiées sur l'API (/ScoDoc/api/), mais évidemment pas sur l'API web (/ScoDoc//api). """ from datetime import datetime @@ -271,23 +271,11 @@ def dept_formsemestres_courants(acronym: str): """ dept = Departement.query.filter_by(acronym=acronym).first_or_404() date_courante = request.args.get("date_courante") - if date_courante: - test_date = datetime.fromisoformat(date_courante) - else: - test_date = app.db.func.now() - # Les semestres en cours de ce département - formsemestres = FormSemestre.query.filter( - FormSemestre.dept_id == dept.id, - FormSemestre.date_debut <= test_date, - FormSemestre.date_fin >= test_date, - ) + date_courante = datetime.fromisoformat(date_courante) if date_courante else None return [ - d.to_dict_api() - for d in formsemestres.order_by( - FormSemestre.date_debut.desc(), - FormSemestre.modalite, - FormSemestre.semestre_id, - FormSemestre.titre, + formsemestre.to_dict_api() + for formsemestre in FormSemestre.get_dept_formsemestres_courants( + dept, date_courante ) ] diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 6bfbbdbb2..10875cbff 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -30,6 +30,7 @@ from app.models.but_refcomp import ( parcours_formsemestre, ) from app.models.config import ScoDocSiteConfig +from app.models.departements import Departement from app.models.etudiants import Identite from app.models.evaluations import Evaluation from app.models.formations import Formation @@ -521,7 +522,7 @@ class FormSemestre(db.Model): mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2, jour_pivot_annee=1, jour_pivot_periode=1, - ): + ) -> tuple[int, int]: """Calcule la session associée à un formsemestre commençant en date_debut sous la forme (année, période) année: première année de l'année scolaire @@ -571,6 +572,26 @@ class FormSemestre(db.Model): mois_pivot_periode=ScoDocSiteConfig.get_month_debut_periode2(), ) + @classmethod + def get_dept_formsemestres_courants( + cls, dept: Departement, date_courante: datetime.datetime | None = None + ) -> db.Query: + """Liste (query) ordonnée des formsemestres courants, c'est + à dire contenant la date courant (si None, la date actuelle)""" + date_courante = date_courante or db.func.now() + # Les semestres en cours de ce département + formsemestres = FormSemestre.query.filter( + FormSemestre.dept_id == dept.id, + FormSemestre.date_debut <= date_courante, + FormSemestre.date_fin >= date_courante, + ) + return formsemestres.order_by( + FormSemestre.date_debut.desc(), + FormSemestre.modalite, + FormSemestre.semestre_id, + FormSemestre.titre, + ) + def etapes_apo_vdi(self) -> list[ApoEtapeVDI]: "Liste des vdis" # was read_formsemestre_etapes diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py index eacfe0439..b81ea09f5 100644 --- a/app/scodoc/sco_dept.py +++ b/app/scodoc/sco_dept.py @@ -149,21 +149,49 @@ def index_html(showcodes=0, showsemtable=0):

""" ) # - if current_user.has_permission(Permission.EtudInscrit): - H.append( - """
+ H.append( + """

Gestion des étudiants

""" ) + H.append( + f""" +
  • exporter tableau des étudiants des semestres en cours +
  • + """ + ) + if current_user.has_permission( + Permission.EtudInscrit + ) and sco_preferences.get_preference("portal_url"): + H.append( + f""" +
  • resynchroniser les données étudiants des semestres en cours depuis le portail +
  • + """ + ) + H.append("") # if current_user.has_permission(Permission.EditApogee): H.append( diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py index e220b8431..da4813f3f 100644 --- a/app/scodoc/sco_synchro_etuds.py +++ b/app/scodoc/sco_synchro_etuds.py @@ -793,21 +793,25 @@ def update_etape_formsemestre_inscription(ins, etud): def formsemestre_import_etud_admission( - formsemestre_id, import_identite=True, import_email=False -): + formsemestre_id: int, import_identite=True, import_email=False +) -> tuple[list[Identite], list[Identite], list[tuple[Identite, str]]]: """Tente d'importer les données admission depuis le portail pour tous les étudiants du semestre. Si import_identite==True, recopie l'identité (nom/prenom/sexe/date_naissance) de chaque étudiant depuis le portail. N'affecte pas les etudiants inconnus sur le portail. + Renvoie: + - etuds_no_nip: liste d'étudiants sans code NIP + - etuds_unknown: etudiants avec NIP mais inconnus du portail + - changed_mails: (etudiant, old_mail) pour ceux dont le mail a changé """ sem = sco_formsemestre.get_formsemestre(formsemestre_id) ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( {"formsemestre_id": formsemestre_id} ) log(f"formsemestre_import_etud_admission: {formsemestre_id} ({len(ins)} etuds)") - no_nip = [] # liste d'etudids sans code NIP - unknowns = [] # etudiants avec NIP mais inconnus du portail + etuds_no_nip: list[Identite] = [] + etuds_unknown: list[Identite] = [] changed_mails: list[tuple[Identite, str]] = [] # modification d'adresse mails # Essaie de recuperer les etudiants des étapes, car @@ -828,7 +832,7 @@ def formsemestre_import_etud_admission( etud: Identite = Identite.query.get_or_404(etudid) code_nip = etud.code_nip if not code_nip: - no_nip.append(etudid) + etuds_no_nip.append(etud) else: data_apo = apo_etuds.get(code_nip) if not data_apo: @@ -865,7 +869,7 @@ def formsemestre_import_etud_admission( if adresse.email != data_apo["mail"]: changed_mails.append((etud, old_mail)) else: - unknowns.append(code_nip) + etuds_unknown.append(etud) db.session.commit() sco_cache.invalidate_formsemestre(formsemestre_id=sem["formsemestre_id"]) - return no_nip, unknowns, changed_mails + return etuds_no_nip, etuds_unknown, changed_mails diff --git a/app/static/css/gt_table.css b/app/static/css/gt_table.css index f4b644df4..e3d3246bd 100644 --- a/app/static/css/gt_table.css +++ b/app/static/css/gt_table.css @@ -1,4 +1,4 @@ -/* +/* * DataTables style for ScoDoc gen_tables * generated using https://datatables.net/manual/styling/theme-creator * and customized by hand @@ -138,111 +138,111 @@ table.dataTable.display tbody tr:hover.selected { background-color: #a9b7d1; } -table.dataTable.order-column tbody tr>.sorting_1, -table.dataTable.order-column tbody tr>.sorting_2, -table.dataTable.order-column tbody tr>.sorting_3, -table.dataTable.display tbody tr>.sorting_1, -table.dataTable.display tbody tr>.sorting_2, -table.dataTable.display tbody tr>.sorting_3 { +table.dataTable.order-column tbody tr > .sorting_1, +table.dataTable.order-column tbody tr > .sorting_2, +table.dataTable.order-column tbody tr > .sorting_3, +table.dataTable.display tbody tr > .sorting_1, +table.dataTable.display tbody tr > .sorting_2, +table.dataTable.display tbody tr > .sorting_3 { background-color: #f9f9f9; } -table.dataTable.order-column tbody tr.selected>.sorting_1, -table.dataTable.order-column tbody tr.selected>.sorting_2, -table.dataTable.order-column tbody tr.selected>.sorting_3, -table.dataTable.display tbody tr.selected>.sorting_1, -table.dataTable.display tbody tr.selected>.sorting_2, -table.dataTable.display tbody tr.selected>.sorting_3 { +table.dataTable.order-column tbody tr.selected > .sorting_1, +table.dataTable.order-column tbody tr.selected > .sorting_2, +table.dataTable.order-column tbody tr.selected > .sorting_3, +table.dataTable.display tbody tr.selected > .sorting_1, +table.dataTable.display tbody tr.selected > .sorting_2, +table.dataTable.display tbody tr.selected > .sorting_3 { background-color: #acbad4; } -table.dataTable.display tbody tr.odd>.sorting_1, -table.dataTable.order-column.stripe tbody tr.odd>.sorting_1 { +table.dataTable.display tbody tr.odd > .sorting_1, +table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 { background-color: #f1f1f1; } -table.dataTable.display tbody tr.odd>.sorting_2, -table.dataTable.order-column.stripe tbody tr.odd>.sorting_2 { +table.dataTable.display tbody tr.odd > .sorting_2, +table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 { background-color: #f3f3f3; } -table.dataTable.display tbody tr.odd>.sorting_3, -table.dataTable.order-column.stripe tbody tr.odd>.sorting_3 { +table.dataTable.display tbody tr.odd > .sorting_3, +table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 { background-color: whitesmoke; } -table.dataTable.display tbody tr.odd.selected>.sorting_1, -table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1 { +table.dataTable.display tbody tr.odd.selected > .sorting_1, +table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 { background-color: #a6b3cd; } -table.dataTable.display tbody tr.odd.selected>.sorting_2, -table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2 { +table.dataTable.display tbody tr.odd.selected > .sorting_2, +table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 { background-color: #a7b5ce; } -table.dataTable.display tbody tr.odd.selected>.sorting_3, -table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3 { +table.dataTable.display tbody tr.odd.selected > .sorting_3, +table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 { background-color: #a9b6d0; } -table.dataTable.display tbody tr.even>.sorting_1, -table.dataTable.order-column.stripe tbody tr.even>.sorting_1 { +table.dataTable.display tbody tr.even > .sorting_1, +table.dataTable.order-column.stripe tbody tr.even > .sorting_1 { background-color: #f9f9f9; } -table.dataTable.display tbody tr.even>.sorting_2, -table.dataTable.order-column.stripe tbody tr.even>.sorting_2 { +table.dataTable.display tbody tr.even > .sorting_2, +table.dataTable.order-column.stripe tbody tr.even > .sorting_2 { background-color: #fbfbfb; } -table.dataTable.display tbody tr.even>.sorting_3, -table.dataTable.order-column.stripe tbody tr.even>.sorting_3 { +table.dataTable.display tbody tr.even > .sorting_3, +table.dataTable.order-column.stripe tbody tr.even > .sorting_3 { background-color: #fdfdfd; } -table.dataTable.display tbody tr.even.selected>.sorting_1, -table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1 { +table.dataTable.display tbody tr.even.selected > .sorting_1, +table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 { background-color: #acbad4; } -table.dataTable.display tbody tr.even.selected>.sorting_2, -table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2 { +table.dataTable.display tbody tr.even.selected > .sorting_2, +table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 { background-color: #adbbd6; } -table.dataTable.display tbody tr.even.selected>.sorting_3, -table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3 { +table.dataTable.display tbody tr.even.selected > .sorting_3, +table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 { background-color: #afbdd8; } -table.dataTable.display tbody tr:hover>.sorting_1, -table.dataTable.order-column.hover tbody tr:hover>.sorting_1 { +table.dataTable.display tbody tr:hover > .sorting_1, +table.dataTable.order-column.hover tbody tr:hover > .sorting_1 { background-color: #eaeaea; } -table.dataTable.display tbody tr:hover>.sorting_2, -table.dataTable.order-column.hover tbody tr:hover>.sorting_2 { +table.dataTable.display tbody tr:hover > .sorting_2, +table.dataTable.order-column.hover tbody tr:hover > .sorting_2 { background-color: #ebebeb; } -table.dataTable.display tbody tr:hover>.sorting_3, -table.dataTable.order-column.hover tbody tr:hover>.sorting_3 { +table.dataTable.display tbody tr:hover > .sorting_3, +table.dataTable.order-column.hover tbody tr:hover > .sorting_3 { background-color: #eeeeee; } -table.dataTable.display tbody tr:hover.selected>.sorting_1, -table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1 { +table.dataTable.display tbody tr:hover.selected > .sorting_1, +table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 { background-color: #a1aec7; } -table.dataTable.display tbody tr:hover.selected>.sorting_2, -table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2 { +table.dataTable.display tbody tr:hover.selected > .sorting_2, +table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 { background-color: #a2afc8; } -table.dataTable.display tbody tr:hover.selected>.sorting_3, -table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3 { +table.dataTable.display tbody tr:hover.selected > .sorting_3, +table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 { background-color: #a4b2cb; } @@ -419,7 +419,13 @@ table.dataTable td { color: #333333 !important; border: 1px solid #979797; background-color: white; - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, gainsboro)); + background: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0%, white), + color-stop(100%, gainsboro) + ); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, white 0%, gainsboro 100%); /* Chrome10+,Safari5.1+ */ @@ -447,7 +453,13 @@ table.dataTable td { color: white !important; border: 1px solid #111111; background-color: #585858; - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111111)); + background: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0%, #585858), + color-stop(100%, #111111) + ); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, #585858 0%, #111111 100%); /* Chrome10+,Safari5.1+ */ @@ -464,7 +476,13 @@ table.dataTable td { .dataTables_wrapper .dataTables_paginate .paginate_button:active { outline: none; background-color: #2b2b2b; - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); + background: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0%, #2b2b2b), + color-stop(100%, #0c0c0c) + ); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Chrome10+,Safari5.1+ */ @@ -495,12 +513,50 @@ table.dataTable td { text-align: center; font-size: 1.2em; background-color: white; - background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); - background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); - background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -webkit-gradient( + linear, + left top, + right top, + color-stop(0%, rgba(255, 255, 255, 0)), + color-stop(25%, rgba(255, 255, 255, 0.9)), + color-stop(75%, rgba(255, 255, 255, 0.9)), + color-stop(100%, rgba(255, 255, 255, 0)) + ); + background: -webkit-linear-gradient( + left, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.9) 25%, + rgba(255, 255, 255, 0.9) 75%, + rgba(255, 255, 255, 0) 100% + ); + background: -moz-linear-gradient( + left, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.9) 25%, + rgba(255, 255, 255, 0.9) 75%, + rgba(255, 255, 255, 0) 100% + ); + background: -ms-linear-gradient( + left, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.9) 25%, + rgba(255, 255, 255, 0.9) 75%, + rgba(255, 255, 255, 0) 100% + ); + background: -o-linear-gradient( + left, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.9) 25%, + rgba(255, 255, 255, 0.9) 75%, + rgba(255, 255, 255, 0) 100% + ); + background: linear-gradient( + to right, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.9) 25%, + rgba(255, 255, 255, 0.9) 75%, + rgba(255, 255, 255, 0) 100% + ); } .dataTables_wrapper .dataTables_length, @@ -520,17 +576,69 @@ table.dataTable td { -webkit-overflow-scrolling: touch; } -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th, -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td, -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th, -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td { +.dataTables_wrapper + .dataTables_scroll + div.dataTables_scrollBody + > table + > thead + > tr + > th, +.dataTables_wrapper + .dataTables_scroll + div.dataTables_scrollBody + > table + > thead + > tr + > td, +.dataTables_wrapper + .dataTables_scroll + div.dataTables_scrollBody + > table + > tbody + > tr + > th, +.dataTables_wrapper + .dataTables_scroll + div.dataTables_scrollBody + > table + > tbody + > tr + > td { vertical-align: middle; } -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing, -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing, -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing, -.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing { +.dataTables_wrapper + .dataTables_scroll + div.dataTables_scrollBody + > table + > thead + > tr + > th + > div.dataTables_sizing, +.dataTables_wrapper + .dataTables_scroll + div.dataTables_scrollBody + > table + > thead + > tr + > td + > div.dataTables_sizing, +.dataTables_wrapper + .dataTables_scroll + div.dataTables_scrollBody + > table + > tbody + > tr + > th + > div.dataTables_sizing, +.dataTables_wrapper + .dataTables_scroll + div.dataTables_scrollBody + > table + > tbody + > tr + > td + > div.dataTables_sizing { height: 0; overflow: hidden; margin: 0 !important; @@ -541,8 +649,8 @@ table.dataTable td { border-bottom: 1px solid #111111; } -.dataTables_wrapper.no-footer div.dataTables_scrollHead>table, -.dataTables_wrapper.no-footer div.dataTables_scrollBody>table { +.dataTables_wrapper.no-footer div.dataTables_scrollHead > table, +.dataTables_wrapper.no-footer div.dataTables_scrollBody > table { border-bottom: none; } @@ -555,7 +663,6 @@ table.dataTable td { } @media screen and (max-width: 767px) { - .dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_paginate { float: none; @@ -568,7 +675,6 @@ table.dataTable td { } @media screen and (max-width: 640px) { - .dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_filter { float: none; @@ -580,8 +686,7 @@ table.dataTable td { } } - -/* ------ Ajouts spécifiques pour ScoDoc: +/* ------ Ajouts spécifiques pour ScoDoc: */ table.gt_table td { @@ -624,7 +729,6 @@ table.dataTable.stripe.hover tbody tr.odd:hover td, table.dataTable.stripe.hover tbody tr.odd:hover td.sorting_1, table.dataTable.order-column.stripe.hover tbody tr.odd:hover td.sorting_1 { background-color: rgb(80%, 85%, 80%); - ; } /* Lignes paires */ @@ -638,7 +742,6 @@ table.dataTable.stripe.hover tbody tr.even:hover td, table.dataTable.stripe.hover tbody tr.even:hover td.sorting_1, table.dataTable.order-column.stripe.hover tbody tr.even:hover td.sorting_1 { background-color: rgb(85%, 85%, 85%); - ; } /* Reglage largeur de la table */ @@ -652,4 +755,9 @@ table.dataTable.gt_table { /* Tables non centrées (inutile) */ table.dataTable.gt_table.gt_left { margin-left: 16px; -} \ No newline at end of file +} +table.dataTable.gt_table.gt_left td, +table.dataTable.gt_table.gt_left th { + text-align: left; +} +scodoc;css diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index f96efa8b4..97d195715 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -1133,7 +1133,8 @@ a.redlink:hover { text-decoration: underline; } -a.discretelink { +a.discretelink, +a:discretelink:visited { color: black; text-decoration: none; } @@ -1567,6 +1568,7 @@ h2.formsemestre, #gtrcontent h2 { margin-top: 2px; font-size: 130%; + font-weight: bold; } .formsemestre_page_title table.semtitle, diff --git a/app/tables/list_etuds.py b/app/tables/list_etuds.py index c559e9326..b520e50d2 100644 --- a/app/tables/list_etuds.py +++ b/app/tables/list_etuds.py @@ -7,8 +7,7 @@ """Liste simple d'étudiants """ -from flask import g, url_for -from app.models import Identite +from app.models import FormSemestre, FormSemestreInscription, Identite from app.tables import table_builder as tb @@ -26,6 +25,7 @@ class TableEtud(tb.Table): with_foot_titles=False, **kwargs, ): + etuds = etuds or [] self.rows: list["RowEtud"] = [] # juste pour que VSCode nous aide sur .rows classes = classes or ["gt_table", "gt_left"] super().__init__( @@ -46,10 +46,12 @@ class TableEtud(tb.Table): class RowEtud(tb.Row): "Ligne de la table d'étudiants" + # pour le moment très simple, extensible (codes, liens bulletins, ...) def __init__(self, table: TableEtud, etud: Identite, *args, **kwargs): super().__init__(table, etud.id, *args, **kwargs) self.etud = etud + self.target_url = etud.url_fiche() def add_etud_cols(self): """Ajoute colonnes étudiant: codes, noms""" @@ -85,7 +87,7 @@ class RowEtud(tb.Row): etud.nom_disp(), "identite_detail", data={"order": etud.sort_key}, - target=url_bulletin, + target=self.target_url, target_attrs={"class": "etudinfo discretelink", "id": str(etud.id)}, ) self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail") @@ -115,3 +117,70 @@ def html_table_etuds(etudids) -> str: etuds = etuds_sorted_from_ids(etudids) table = TableEtud(etuds) return table.html() + + +class RowEtudWithInfos(RowEtud): + """Ligne de la table d'étudiants avec plus d'informations: + département, formsemestre, codes, boursier + """ + + def __init__( + self, + table: TableEtud, + etud: Identite, + formsemestre: FormSemestre, + inscription: FormSemestreInscription, + *args, + **kwargs, + ): + super().__init__(table, etud, *args, **kwargs) + self.formsemestre = formsemestre + self.inscription = inscription + + def add_etud_cols(self): + """Ajoute colonnes étudiant: codes, noms""" + self.add_cell("dept", "Dépt.", self.etud.departement.acronym, "identite_detail") + self.add_cell( + "formsemestre", + "Semestre", + f"""{self.formsemestre.titre_formation()} { + ('S'+str(self.formsemestre.semestre_id)) + if self.formsemestre.semestre_id > 0 else ''} + """, + "identite_detail", + ) + super().add_etud_cols() + self.add_cell( + "etat", + "État", + self.inscription.etat, + "inscription", + ) + self.add_cell( + "boursier", + "Boursier", + "O" if self.etud.boursier else "N", + "identite_infos", + ) + + +class TableEtudWithInfos(TableEtud): + """Table d'étudiants avec formsemestre et inscription""" + + def add_formsemestre(self, formsemestre: FormSemestre): + "Ajoute les étudiants de ce semestre à la table" + etuds = formsemestre.get_inscrits(order=True, include_demdef=True) + for etud in etuds: + row = self.row_class( + self, etud, formsemestre, formsemestre.etuds_inscriptions.get(etud.id) + ) + row.add_etud_cols() + self.add_row(row) + + +def table_etudiants_courants(formsemestres: list[FormSemestre]) -> TableEtud: + """Table des étudiants des formsemestres indiqués""" + table = TableEtudWithInfos(row_class=RowEtudWithInfos) + for formsemestre in formsemestres: + table.add_formsemestre(formsemestre) + return table diff --git a/app/views/scolar.py b/app/views/scolar.py index ac908ce76..958d7b0a0 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -105,6 +105,7 @@ from app.scodoc import sco_synchro_etuds from app.scodoc import sco_trombino from app.scodoc import sco_trombino_tours from app.scodoc import sco_up_to_date +from app.tables import list_etuds def sco_publish(route, function, permission, methods=["GET"]): @@ -2071,6 +2072,21 @@ def check_group_apogee(group_id, etat=None, fix=False, fixmail=False): return "\n".join(H) + html_sco_header.sco_footer() +@bp.route("/export_etudiants_courants") +@scodoc +@permission_required(Permission.ScoView) +def export_etudiants_courants(): + """Table export de tous les étudiants des formsemestres en cours.""" + departement = Departement.query.get(g.scodoc_dept_id) + if not departement: + raise ScoValueError("département invalide") + formsemestres = FormSemestre.get_dept_formsemestres_courants(departement) + table = list_etuds.table_etudiants_courants(formsemestres) + return render_template( + "scolar/export_etudiants_courants.j2", sco=ScoData(), table=table + ) + + @bp.route("/form_students_import_excel", methods=["GET", "POST"]) @scodoc @permission_required(Permission.EtudInscrit) @@ -2361,35 +2377,57 @@ def form_students_import_infos_admissions(formsemestre_id=None): @scodoc @permission_required(Permission.EtudChangeAdr) @scodoc7func -def formsemestre_import_etud_admission(formsemestre_id, import_email=True): - """Ré-importe donnees admissions par synchro Portail Apogée""" - ( - no_nip, - unknowns, - changed_mails, - ) = sco_synchro_etuds.formsemestre_import_etud_admission( - formsemestre_id, import_identite=True, import_email=import_email - ) - H = [ - html_sco_header.html_sem_header("Ré-import données admission"), - "

    Opération effectuée

    ", - ] - if no_nip: - H.append("

    Attention: étudiants sans NIP: " + str(no_nip) + "

    ") - if unknowns: - H.append( - "

    Attention: étudiants inconnus du portail: codes NIP=" - + str(unknowns) - + "

    " +def formsemestre_import_etud_admission( + formsemestre_id=None, import_email=True, tous_courants=False +): + """Ré-importe donnees admissions par synchro Portail Apogée. + Si tous_courants, le fait pour tous les formsemestres courants du département + """ + if tous_courants: + departement = Departement.query.get(g.scodoc_dept_id) + formsemestres = FormSemestre.get_dept_formsemestres_courants(departement) + else: + formsemestres = [FormSemestre.get_formsemestre(formsemestre_id)] + + diag_by_sem = {} + for formsemestre in formsemestres: + ( + etuds_no_nip, + etuds_unknown, + changed_mails, + ) = sco_synchro_etuds.formsemestre_import_etud_admission( + formsemestre.id, import_identite=True, import_email=import_email ) - if changed_mails: - H.append("

    Adresses mails modifiées:

    ") - return "\n".join(H) + html_sco_header.sco_footer() + diag = "" + if etuds_no_nip: + diag += f"""

    Attention: étudiants sans NIP: + {', '.join([e.html_link_fiche() for e in etuds_no_nip])} +

    """ + + if etuds_unknown: + diag += f"""

    Attention: étudiants inconnus du portail: + {', '.join([(e.html_link_fiche() + ' (nip= ' + e.code_nip + ')') + for e in etuds_unknown])} +

    """ + + if changed_mails: + diag += """

    Adresses mails modifiées:

    " + diag_by_sem[formsemestre.id] = diag + + return f""" + { html_sco_header.html_sem_header("Ré-import données admission") } +

    Opération effectuée

    +

    Sur le(s) semestres(s):

    + + { html_sco_header.sco_footer() } + """ sco_publish(