1
0
forked from ScoDoc/ScoDoc

Accepte codage boursiers O/N (USPN), fonction de resynchro globale, table de tous les étudiants courants avec état boursier.

This commit is contained in:
Emmanuel Viennet 2023-11-26 18:28:56 +01:00
parent 60109bb513
commit 568c8681ba
8 changed files with 397 additions and 139 deletions

View File

@ -7,7 +7,7 @@
""" """
ScoDoc 9 API : accès aux départements 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/<dept>/api). mais évidemment pas sur l'API web (/ScoDoc/<dept>/api).
""" """
from datetime import datetime from datetime import datetime
@ -271,23 +271,11 @@ def dept_formsemestres_courants(acronym: str):
""" """
dept = Departement.query.filter_by(acronym=acronym).first_or_404() dept = Departement.query.filter_by(acronym=acronym).first_or_404()
date_courante = request.args.get("date_courante") date_courante = request.args.get("date_courante")
if date_courante: date_courante = datetime.fromisoformat(date_courante) if date_courante else None
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,
)
return [ return [
d.to_dict_api() formsemestre.to_dict_api()
for d in formsemestres.order_by( for formsemestre in FormSemestre.get_dept_formsemestres_courants(
FormSemestre.date_debut.desc(), dept, date_courante
FormSemestre.modalite,
FormSemestre.semestre_id,
FormSemestre.titre,
) )
] ]

View File

@ -30,6 +30,7 @@ from app.models.but_refcomp import (
parcours_formsemestre, parcours_formsemestre,
) )
from app.models.config import ScoDocSiteConfig from app.models.config import ScoDocSiteConfig
from app.models.departements import Departement
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.evaluations import Evaluation from app.models.evaluations import Evaluation
from app.models.formations import Formation from app.models.formations import Formation
@ -521,7 +522,7 @@ class FormSemestre(db.Model):
mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2, mois_pivot_periode=scu.MONTH_DEBUT_PERIODE2,
jour_pivot_annee=1, jour_pivot_annee=1,
jour_pivot_periode=1, jour_pivot_periode=1,
): ) -> tuple[int, int]:
"""Calcule la session associée à un formsemestre commençant en date_debut """Calcule la session associée à un formsemestre commençant en date_debut
sous la forme (année, période) sous la forme (année, période)
année: première année de l'année scolaire 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(), 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]: def etapes_apo_vdi(self) -> list[ApoEtapeVDI]:
"Liste des vdis" "Liste des vdis"
# was read_formsemestre_etapes # was read_formsemestre_etapes

View File

@ -149,21 +149,49 @@ def index_html(showcodes=0, showsemtable=0):
</p>""" </p>"""
) )
# #
if current_user.has_permission(Permission.EtudInscrit): H.append(
H.append( """<hr>
"""<hr>
<h3>Gestion des étudiants</h3> <h3>Gestion des étudiants</h3>
<ul> <ul>
<li><a class="stdlink" href="etudident_create_form">créer <em>un</em> nouvel étudiant</a> """
)
if current_user.has_permission(Permission.EtudInscrit):
H.append(
f"""
<li><a class="stdlink" href="{
url_for("scolar.etudident_create_form", scodoc_dept=g.scodoc_dept)
}">créer <em>un</em> nouvel étudiant</a>
</li> </li>
<li><a class="stdlink" href="form_students_import_excel">importer de nouveaux étudiants</a> <li><a class="stdlink" href="{
(ne pas utiliser sauf cas particulier, utilisez plutôt le lien dans url_for("scolar.form_students_import_excel", scodoc_dept=g.scodoc_dept)
}">importer de nouveaux étudiants</a>
(<em>ne pas utiliser</em> sauf cas particulier&nbsp;: utilisez plutôt le lien dans
le tableau de bord semestre si vous souhaitez inscrire les le tableau de bord semestre si vous souhaitez inscrire les
étudiants importés à un semestre) étudiants importés à un semestre)
</li> </li>
</ul>
""" """
) )
H.append(
f"""
<li><a class="stdlink" href="{
url_for("scolar.export_etudiants_courants", scodoc_dept=g.scodoc_dept)
}">exporter tableau des étudiants des semestres en cours</a>
</li>
"""
)
if current_user.has_permission(
Permission.EtudInscrit
) and sco_preferences.get_preference("portal_url"):
H.append(
f"""
<li><a class="stdlink" href="{
url_for("scolar.formsemestre_import_etud_admission",
scodoc_dept=g.scodoc_dept, tous_courants=1)
}">resynchroniser les données étudiants des semestres en cours depuis le portail</a>
</li>
"""
)
H.append("</ul>")
# #
if current_user.has_permission(Permission.EditApogee): if current_user.has_permission(Permission.EditApogee):
H.append( H.append(

View File

@ -793,21 +793,25 @@ def update_etape_formsemestre_inscription(ins, etud):
def formsemestre_import_etud_admission( 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 """Tente d'importer les données admission depuis le portail
pour tous les étudiants du semestre. pour tous les étudiants du semestre.
Si import_identite==True, recopie l'identité (nom/prenom/sexe/date_naissance) Si import_identite==True, recopie l'identité (nom/prenom/sexe/date_naissance)
de chaque étudiant depuis le portail. de chaque étudiant depuis le portail.
N'affecte pas les etudiants inconnus sur 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) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list( ins = sco_formsemestre_inscriptions.do_formsemestre_inscription_list(
{"formsemestre_id": formsemestre_id} {"formsemestre_id": formsemestre_id}
) )
log(f"formsemestre_import_etud_admission: {formsemestre_id} ({len(ins)} etuds)") log(f"formsemestre_import_etud_admission: {formsemestre_id} ({len(ins)} etuds)")
no_nip = [] # liste d'etudids sans code NIP etuds_no_nip: list[Identite] = []
unknowns = [] # etudiants avec NIP mais inconnus du portail etuds_unknown: list[Identite] = []
changed_mails: list[tuple[Identite, str]] = [] # modification d'adresse mails changed_mails: list[tuple[Identite, str]] = [] # modification d'adresse mails
# Essaie de recuperer les etudiants des étapes, car # 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) etud: Identite = Identite.query.get_or_404(etudid)
code_nip = etud.code_nip code_nip = etud.code_nip
if not code_nip: if not code_nip:
no_nip.append(etudid) etuds_no_nip.append(etud)
else: else:
data_apo = apo_etuds.get(code_nip) data_apo = apo_etuds.get(code_nip)
if not data_apo: if not data_apo:
@ -865,7 +869,7 @@ def formsemestre_import_etud_admission(
if adresse.email != data_apo["mail"]: if adresse.email != data_apo["mail"]:
changed_mails.append((etud, old_mail)) changed_mails.append((etud, old_mail))
else: else:
unknowns.append(code_nip) etuds_unknown.append(etud)
db.session.commit() db.session.commit()
sco_cache.invalidate_formsemestre(formsemestre_id=sem["formsemestre_id"]) sco_cache.invalidate_formsemestre(formsemestre_id=sem["formsemestre_id"])
return no_nip, unknowns, changed_mails return etuds_no_nip, etuds_unknown, changed_mails

View File

@ -1,4 +1,4 @@
/* /*
* DataTables style for ScoDoc gen_tables * DataTables style for ScoDoc gen_tables
* generated using https://datatables.net/manual/styling/theme-creator * generated using https://datatables.net/manual/styling/theme-creator
* and customized by hand * and customized by hand
@ -138,111 +138,111 @@ table.dataTable.display tbody tr:hover.selected {
background-color: #a9b7d1; background-color: #a9b7d1;
} }
table.dataTable.order-column tbody tr>.sorting_1, table.dataTable.order-column tbody tr > .sorting_1,
table.dataTable.order-column tbody tr>.sorting_2, table.dataTable.order-column tbody tr > .sorting_2,
table.dataTable.order-column tbody tr>.sorting_3, table.dataTable.order-column tbody tr > .sorting_3,
table.dataTable.display tbody tr>.sorting_1, table.dataTable.display tbody tr > .sorting_1,
table.dataTable.display tbody tr>.sorting_2, table.dataTable.display tbody tr > .sorting_2,
table.dataTable.display tbody tr>.sorting_3 { table.dataTable.display tbody tr > .sorting_3 {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
table.dataTable.order-column tbody tr.selected>.sorting_1, 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_2,
table.dataTable.order-column tbody tr.selected>.sorting_3, table.dataTable.order-column tbody tr.selected > .sorting_3,
table.dataTable.display tbody tr.selected>.sorting_1, table.dataTable.display tbody tr.selected > .sorting_1,
table.dataTable.display tbody tr.selected>.sorting_2, table.dataTable.display tbody tr.selected > .sorting_2,
table.dataTable.display tbody tr.selected>.sorting_3 { table.dataTable.display tbody tr.selected > .sorting_3 {
background-color: #acbad4; background-color: #acbad4;
} }
table.dataTable.display tbody tr.odd>.sorting_1, table.dataTable.display tbody tr.odd > .sorting_1,
table.dataTable.order-column.stripe tbody tr.odd>.sorting_1 { table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 {
background-color: #f1f1f1; background-color: #f1f1f1;
} }
table.dataTable.display tbody tr.odd>.sorting_2, table.dataTable.display tbody tr.odd > .sorting_2,
table.dataTable.order-column.stripe tbody tr.odd>.sorting_2 { table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 {
background-color: #f3f3f3; background-color: #f3f3f3;
} }
table.dataTable.display tbody tr.odd>.sorting_3, table.dataTable.display tbody tr.odd > .sorting_3,
table.dataTable.order-column.stripe tbody tr.odd>.sorting_3 { table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 {
background-color: whitesmoke; background-color: whitesmoke;
} }
table.dataTable.display 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 { table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 {
background-color: #a6b3cd; background-color: #a6b3cd;
} }
table.dataTable.display 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 { table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 {
background-color: #a7b5ce; background-color: #a7b5ce;
} }
table.dataTable.display 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 { table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 {
background-color: #a9b6d0; background-color: #a9b6d0;
} }
table.dataTable.display tbody tr.even>.sorting_1, table.dataTable.display tbody tr.even > .sorting_1,
table.dataTable.order-column.stripe tbody tr.even>.sorting_1 { table.dataTable.order-column.stripe tbody tr.even > .sorting_1 {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
table.dataTable.display tbody tr.even>.sorting_2, table.dataTable.display tbody tr.even > .sorting_2,
table.dataTable.order-column.stripe tbody tr.even>.sorting_2 { table.dataTable.order-column.stripe tbody tr.even > .sorting_2 {
background-color: #fbfbfb; background-color: #fbfbfb;
} }
table.dataTable.display tbody tr.even>.sorting_3, table.dataTable.display tbody tr.even > .sorting_3,
table.dataTable.order-column.stripe tbody tr.even>.sorting_3 { table.dataTable.order-column.stripe tbody tr.even > .sorting_3 {
background-color: #fdfdfd; background-color: #fdfdfd;
} }
table.dataTable.display 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 { table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 {
background-color: #acbad4; background-color: #acbad4;
} }
table.dataTable.display 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 { table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 {
background-color: #adbbd6; background-color: #adbbd6;
} }
table.dataTable.display 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 { table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 {
background-color: #afbdd8; background-color: #afbdd8;
} }
table.dataTable.display tbody tr:hover>.sorting_1, table.dataTable.display tbody tr:hover > .sorting_1,
table.dataTable.order-column.hover tbody tr:hover>.sorting_1 { table.dataTable.order-column.hover tbody tr:hover > .sorting_1 {
background-color: #eaeaea; background-color: #eaeaea;
} }
table.dataTable.display tbody tr:hover>.sorting_2, table.dataTable.display tbody tr:hover > .sorting_2,
table.dataTable.order-column.hover tbody tr:hover>.sorting_2 { table.dataTable.order-column.hover tbody tr:hover > .sorting_2 {
background-color: #ebebeb; background-color: #ebebeb;
} }
table.dataTable.display tbody tr:hover>.sorting_3, table.dataTable.display tbody tr:hover > .sorting_3,
table.dataTable.order-column.hover tbody tr:hover>.sorting_3 { table.dataTable.order-column.hover tbody tr:hover > .sorting_3 {
background-color: #eeeeee; background-color: #eeeeee;
} }
table.dataTable.display 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 { table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 {
background-color: #a1aec7; background-color: #a1aec7;
} }
table.dataTable.display 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 { table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 {
background-color: #a2afc8; background-color: #a2afc8;
} }
table.dataTable.display 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 { table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 {
background-color: #a4b2cb; background-color: #a4b2cb;
} }
@ -419,7 +419,13 @@ table.dataTable td {
color: #333333 !important; color: #333333 !important;
border: 1px solid #979797; border: 1px solid #979797;
background-color: white; 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+ */ /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, white 0%, gainsboro 100%); background: -webkit-linear-gradient(top, white 0%, gainsboro 100%);
/* Chrome10+,Safari5.1+ */ /* Chrome10+,Safari5.1+ */
@ -447,7 +453,13 @@ table.dataTable td {
color: white !important; color: white !important;
border: 1px solid #111111; border: 1px solid #111111;
background-color: #585858; 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+ */ /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #585858 0%, #111111 100%); background: -webkit-linear-gradient(top, #585858 0%, #111111 100%);
/* Chrome10+,Safari5.1+ */ /* Chrome10+,Safari5.1+ */
@ -464,7 +476,13 @@ table.dataTable td {
.dataTables_wrapper .dataTables_paginate .paginate_button:active { .dataTables_wrapper .dataTables_paginate .paginate_button:active {
outline: none; outline: none;
background-color: #2b2b2b; 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+ */ /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* Chrome10+,Safari5.1+ */ /* Chrome10+,Safari5.1+ */
@ -495,12 +513,50 @@ table.dataTable td {
text-align: center; text-align: center;
font-size: 1.2em; font-size: 1.2em;
background-color: white; 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-gradient(
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%); linear,
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%); left top,
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%); right top,
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%); color-stop(0%, rgba(255, 255, 255, 0)),
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%); 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, .dataTables_wrapper .dataTables_length,
@ -520,17 +576,69 @@ table.dataTable td {
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th, .dataTables_wrapper
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td, .dataTables_scroll
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th, div.dataTables_scrollBody
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td { > 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; vertical-align: middle;
} }
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing, .dataTables_wrapper
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing, .dataTables_scroll
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing, div.dataTables_scrollBody
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing { > 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; height: 0;
overflow: hidden; overflow: hidden;
margin: 0 !important; margin: 0 !important;
@ -541,8 +649,8 @@ table.dataTable td {
border-bottom: 1px solid #111111; border-bottom: 1px solid #111111;
} }
.dataTables_wrapper.no-footer div.dataTables_scrollHead>table, .dataTables_wrapper.no-footer div.dataTables_scrollHead > table,
.dataTables_wrapper.no-footer div.dataTables_scrollBody>table { .dataTables_wrapper.no-footer div.dataTables_scrollBody > table {
border-bottom: none; border-bottom: none;
} }
@ -555,7 +663,6 @@ table.dataTable td {
} }
@media screen and (max-width: 767px) { @media screen and (max-width: 767px) {
.dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_paginate { .dataTables_wrapper .dataTables_paginate {
float: none; float: none;
@ -568,7 +675,6 @@ table.dataTable td {
} }
@media screen and (max-width: 640px) { @media screen and (max-width: 640px) {
.dataTables_wrapper .dataTables_length, .dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter { .dataTables_wrapper .dataTables_filter {
float: none; float: none;
@ -580,8 +686,7 @@ table.dataTable td {
} }
} }
/* ------ Ajouts spécifiques pour ScoDoc:
/* ------ Ajouts spécifiques pour ScoDoc:
*/ */
table.gt_table td { 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.stripe.hover tbody tr.odd:hover td.sorting_1,
table.dataTable.order-column.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%); background-color: rgb(80%, 85%, 80%);
;
} }
/* Lignes paires */ /* 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.stripe.hover tbody tr.even:hover td.sorting_1,
table.dataTable.order-column.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%); background-color: rgb(85%, 85%, 85%);
;
} }
/* Reglage largeur de la table */ /* Reglage largeur de la table */
@ -652,4 +755,9 @@ table.dataTable.gt_table {
/* Tables non centrées (inutile) */ /* Tables non centrées (inutile) */
table.dataTable.gt_table.gt_left { table.dataTable.gt_table.gt_left {
margin-left: 16px; margin-left: 16px;
} }
table.dataTable.gt_table.gt_left td,
table.dataTable.gt_table.gt_left th {
text-align: left;
}
scodoc;css

View File

@ -1133,7 +1133,8 @@ a.redlink:hover {
text-decoration: underline; text-decoration: underline;
} }
a.discretelink { a.discretelink,
a:discretelink:visited {
color: black; color: black;
text-decoration: none; text-decoration: none;
} }
@ -1567,6 +1568,7 @@ h2.formsemestre,
#gtrcontent h2 { #gtrcontent h2 {
margin-top: 2px; margin-top: 2px;
font-size: 130%; font-size: 130%;
font-weight: bold;
} }
.formsemestre_page_title table.semtitle, .formsemestre_page_title table.semtitle,

View File

@ -7,8 +7,7 @@
"""Liste simple d'étudiants """Liste simple d'étudiants
""" """
from flask import g, url_for from app.models import FormSemestre, FormSemestreInscription, Identite
from app.models import Identite
from app.tables import table_builder as tb from app.tables import table_builder as tb
@ -26,6 +25,7 @@ class TableEtud(tb.Table):
with_foot_titles=False, with_foot_titles=False,
**kwargs, **kwargs,
): ):
etuds = etuds or []
self.rows: list["RowEtud"] = [] # juste pour que VSCode nous aide sur .rows self.rows: list["RowEtud"] = [] # juste pour que VSCode nous aide sur .rows
classes = classes or ["gt_table", "gt_left"] classes = classes or ["gt_table", "gt_left"]
super().__init__( super().__init__(
@ -46,10 +46,12 @@ class TableEtud(tb.Table):
class RowEtud(tb.Row): class RowEtud(tb.Row):
"Ligne de la table d'étudiants" "Ligne de la table d'étudiants"
# pour le moment très simple, extensible (codes, liens bulletins, ...) # pour le moment très simple, extensible (codes, liens bulletins, ...)
def __init__(self, table: TableEtud, etud: Identite, *args, **kwargs): def __init__(self, table: TableEtud, etud: Identite, *args, **kwargs):
super().__init__(table, etud.id, *args, **kwargs) super().__init__(table, etud.id, *args, **kwargs)
self.etud = etud self.etud = etud
self.target_url = etud.url_fiche()
def add_etud_cols(self): def add_etud_cols(self):
"""Ajoute colonnes étudiant: codes, noms""" """Ajoute colonnes étudiant: codes, noms"""
@ -85,7 +87,7 @@ class RowEtud(tb.Row):
etud.nom_disp(), etud.nom_disp(),
"identite_detail", "identite_detail",
data={"order": etud.sort_key}, data={"order": etud.sort_key},
target=url_bulletin, target=self.target_url,
target_attrs={"class": "etudinfo discretelink", "id": str(etud.id)}, target_attrs={"class": "etudinfo discretelink", "id": str(etud.id)},
) )
self.add_cell("prenom", "Prénom", etud.prenom, "identite_detail") 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) etuds = etuds_sorted_from_ids(etudids)
table = TableEtud(etuds) table = TableEtud(etuds)
return table.html() 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

View File

@ -105,6 +105,7 @@ from app.scodoc import sco_synchro_etuds
from app.scodoc import sco_trombino from app.scodoc import sco_trombino
from app.scodoc import sco_trombino_tours from app.scodoc import sco_trombino_tours
from app.scodoc import sco_up_to_date from app.scodoc import sco_up_to_date
from app.tables import list_etuds
def sco_publish(route, function, permission, methods=["GET"]): 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() 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"]) @bp.route("/form_students_import_excel", methods=["GET", "POST"])
@scodoc @scodoc
@permission_required(Permission.EtudInscrit) @permission_required(Permission.EtudInscrit)
@ -2361,35 +2377,57 @@ def form_students_import_infos_admissions(formsemestre_id=None):
@scodoc @scodoc
@permission_required(Permission.EtudChangeAdr) @permission_required(Permission.EtudChangeAdr)
@scodoc7func @scodoc7func
def formsemestre_import_etud_admission(formsemestre_id, import_email=True): def formsemestre_import_etud_admission(
"""Ré-importe donnees admissions par synchro Portail Apogée""" formsemestre_id=None, import_email=True, tous_courants=False
( ):
no_nip, """Ré-importe donnees admissions par synchro Portail Apogée.
unknowns, Si tous_courants, le fait pour tous les formsemestres courants du département
changed_mails, """
) = sco_synchro_etuds.formsemestre_import_etud_admission( if tous_courants:
formsemestre_id, import_identite=True, import_email=import_email departement = Departement.query.get(g.scodoc_dept_id)
) formsemestres = FormSemestre.get_dept_formsemestres_courants(departement)
H = [ else:
html_sco_header.html_sem_header("Ré-import données admission"), formsemestres = [FormSemestre.get_formsemestre(formsemestre_id)]
"<h3>Opération effectuée</h3>",
] diag_by_sem = {}
if no_nip: for formsemestre in formsemestres:
H.append("<p>Attention: étudiants sans NIP: " + str(no_nip) + "</p>") (
if unknowns: etuds_no_nip,
H.append( etuds_unknown,
"<p>Attention: étudiants inconnus du portail: codes NIP=" changed_mails,
+ str(unknowns) ) = sco_synchro_etuds.formsemestre_import_etud_admission(
+ "</p>" formsemestre.id, import_identite=True, import_email=import_email
) )
if changed_mails: diag = ""
H.append("<h3>Adresses mails modifiées:</h3><ul>") if etuds_no_nip:
for etud, old_mail in changed_mails: diag += f"""<p>Attention: étudiants sans NIP:
H.append( {', '.join([e.html_link_fiche() for e in etuds_no_nip])}
f"""<li>{etud.nom}: <tt>{old_mail}</tt> devient <tt>{etud.email}</tt></li>""" </p>"""
)
H.append("</ul>") if etuds_unknown:
return "\n".join(H) + html_sco_header.sco_footer() diag += f"""<p>Attention: étudiants inconnus du portail:
{', '.join([(e.html_link_fiche() + ' (nip= ' + e.code_nip + ')')
for e in etuds_unknown])}
</p>"""
if changed_mails:
diag += """<p>Adresses mails modifiées:</p><ul>"""
for etud, old_mail in changed_mails:
diag += f"""<li>{etud.nom}: <tt>{old_mail}</tt> devient <tt>{etud.email}</tt></li>"""
diag += "</ul>"
diag_by_sem[formsemestre.id] = diag
return f"""
{ html_sco_header.html_sem_header("Ré-import données admission") }
<h3>Opération effectuée</h3>
<p>Sur le(s) semestres(s):</p>
<ul>
<li>
{ '</li><li>'.join( [(s.html_link_status() + diag_by_sem[s.id]) for s in formsemestres ]) }
</li>
</ul>
{ html_sco_header.sco_footer() }
"""
sco_publish( sco_publish(