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 95a9c4e9a6..7214032d15 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 6bfbbdbb2b..10875cbff6 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 eacfe0439e..b81ea09f5d 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 e220b84311..da4813f3f5 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 f4b644df4a..e3d3246bd9 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 f96efa8b4b..97d195715f 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 c559e93266..b520e50d2c 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 ac908ce768..958d7b0a0a 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:
")
- for etud, old_mail in changed_mails:
- H.append(
- f"""- {etud.nom}: {old_mail} devient {etud.email}
"""
- )
- H.append("
")
- 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:
"""
+ for etud, old_mail in changed_mails:
+ diag += f"""- {etud.nom}: {old_mail} devient {etud.email}
"""
+ diag += "
"
+ 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):
+
+ -
+ { '
- '.join( [(s.html_link_status() + diag_by_sem[s.id]) for s in formsemestres ]) }
+
+
+ { html_sco_header.sco_footer() }
+ """
sco_publish(