diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 3aacb66a..b1fec609 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -302,22 +302,46 @@ class Identite(db.Model): else: date_ins = events[0].event_date situation += date_ins.strftime(" le %d/%m/%Y") + elif inscr.etat == scu.DEF: + situation = f"défaillant en {inscr.formsemestre.titre_mois()}" + event = ( + models.ScolarEvent.query.filter_by( + etudid=self.id, + formsemestre_id=inscr.formsemestre.id, + event_type="DEFAILLANCE", + ) + .order_by(models.ScolarEvent.event_date) + .first() + ) + if not event: + log( + f"*** situation inconsistante pour {self} (def mais pas d'event)" + ) + situation += "???" # ??? + else: + date_def = event.event_date + situation += date_def.strftime(" le %d/%m/%Y") + else: situation = f"démission de {inscr.formsemestre.titre_mois()}" # Cherche la date de demission dans scolar_events: - events = models.ScolarEvent.query.filter_by( - etudid=self.id, - formsemestre_id=inscr.formsemestre.id, - event_type="DEMISSION", - ).all() - if not events: + event = ( + models.ScolarEvent.query.filter_by( + etudid=self.id, + formsemestre_id=inscr.formsemestre.id, + event_type="DEMISSION", + ) + .order_by(models.ScolarEvent.event_date) + .first() + ) + if not event: log( f"*** situation inconsistante pour {self} (demission mais pas d'event)" ) - date_dem = "???" # ??? + situation += "???" # ??? else: - date_dem = events[0].event_date - situation += date_dem.strftime(" le %d/%m/%Y") + date_dem = event.event_date + situation += date_dem.strftime(" le %d/%m/%Y") else: situation = "non inscrit" + self.e diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py index 8c74fa65..14c47651 100644 --- a/app/scodoc/sco_dept.py +++ b/app/scodoc/sco_dept.py @@ -237,6 +237,8 @@ def _sem_table_gt(sems, showcodes=False): "titre_resp", "nb_inscrits", "etapes_apo_str", + "elt_annee_apo", + "elt_sem_apo", ) if showcodes: columns_ids = ("formsemestre_id",) + columns_ids @@ -253,6 +255,9 @@ def _sem_table_gt(sems, showcodes=False): "dash_mois_fin": "Année", "titre_resp": "Semestre", "nb_inscrits": "N", + "etapes_apo_str": "Étape Apo.", + "elt_annee_apo": "Elt. année Apo.", + "elt_sem_apo": "Elt. sem. Apo.", }, columns_ids=columns_ids, rows=sems, @@ -260,7 +265,11 @@ def _sem_table_gt(sems, showcodes=False): html_class_ignore_default=True, html_class=html_class, html_sortable=True, - html_table_attrs=f"""data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}" """, + html_table_attrs=f""" + data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}" + data-elt_annee_apo_save_url="{url_for('notes.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept)}" + data-elt_sem_apo_save_url="{url_for('notes.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept)}" + """, html_with_td_classes=True, preferences=sco_preferences.SemPreferences(), ) @@ -298,6 +307,12 @@ def _style_sems(sems): sem[ "_etapes_apo_str_td_attrs" ] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['etapes_apo_str']}" """ + sem[ + "_elt_annee_apo_td_attrs" + ] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_annee_apo']}" """ + sem[ + "_elt_sem_apo_td_attrs" + ] = f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """ def delete_dept(dept_id: int): diff --git a/app/scodoc/sco_formation_recap.py b/app/scodoc/sco_formation_recap.py index 16aa336d..fedf7a48 100644 --- a/app/scodoc/sco_formation_recap.py +++ b/app/scodoc/sco_formation_recap.py @@ -30,6 +30,7 @@ import io from zipfile import ZipFile, BadZipfile +from flask import Response from flask import send_file, url_for from flask import g, request from flask_login import current_user @@ -44,7 +45,7 @@ import app.scodoc.sco_utils as scu # ---- Table recap formation -def formation_table_recap(formation_id, format="html"): +def formation_table_recap(formation_id, format="html") -> Response: """Table recapitulant formation.""" T = [] formation = Formation.query.get_or_404(formation_id) @@ -70,7 +71,7 @@ def formation_table_recap(formation_id, format="html"): "_apo_td_attrs": f""" data-oid="{ue.id}" data-value="{ue.code_apogee or ''}" """, "coef": ue.coefficient or "", "ects": ue.ects, - "_css_row_class": f"ue ue_", + "_css_row_class": "ue", } ) li += 1 diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index 98fc64a2..11792057 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -95,9 +95,12 @@ _formsemestreEditor = ndb.EditableTable( def get_formsemestre(formsemestre_id, raise_soft_exc=False): "list ONE formsemestre" + if formsemestre_id is None: + raise ValueError(f"get_formsemestre: id manquant") if formsemestre_id in g.stored_get_formsemestre: return g.stored_get_formsemestre[formsemestre_id] if not isinstance(formsemestre_id, int): + log(f"get_formsemestre: invalid id '{formsemestre_id}'") raise ScoInvalidIdType("formsemestre_id must be an integer !") sems = do_formsemestre_list(args={"formsemestre_id": formsemestre_id}) if not sems: diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index 98c67431..299318ed 100644 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -966,6 +966,7 @@ Il y a des notes en attente ! Le classement des étudiants n'a qu'une valeur ind def formsemestre_status(formsemestre_id=None): """Tableau de bord semestre HTML""" # porté du DTML + sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True) modimpls = sco_moduleimpl.moduleimpl_withmodule_list( formsemestre_id=formsemestre_id @@ -987,7 +988,9 @@ def formsemestre_status(formsemestre_id=None): use_ue_coefs = sco_preferences.get_preference("use_ue_coefs", formsemestre_id) H = [ - html_sco_header.sco_header(page_title="Semestre %s" % sem["titreannee"]), + html_sco_header.sco_header( + page_title=f"{formsemestre.sem_modalite()} {formsemestre.titre_annee()}" + ), '
', formsemestre_status_head( formsemestre_id=formsemestre_id, page_title="Tableau de bord" diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py index 83dd79d6..179014cf 100644 --- a/app/scodoc/sco_recapcomplet.py +++ b/app/scodoc/sco_recapcomplet.py @@ -103,7 +103,7 @@ def formsemestre_recapcomplet( return data H = [ html_sco_header.sco_header( - page_title="Récapitulatif", + page_title=f"{formsemestre.sem_modalite()}: moyennes", no_side_bar=True, init_qtip=True, javascripts=["js/etud_info.js", "js/table_recap.js"], diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py index 6483ffcf..67ac5a6c 100644 --- a/app/scodoc/sco_synchro_etuds.py +++ b/app/scodoc/sco_synchro_etuds.py @@ -704,6 +704,7 @@ def do_import_etuds_from_portal(sem, a_importer, etudsapo_ident): typ=ScolarNews.NEWS_INSCR, text="Import Apogée de %d étudiants en " % len(created_etudids), obj=sem["formsemestre_id"], + max_frequency=10 * 60, # 10' ) diff --git a/app/static/js/scolar_index.js b/app/static/js/scolar_index.js index 600a7cf5..90c20b45 100644 --- a/app/static/js/scolar_index.js +++ b/app/static/js/scolar_index.js @@ -1,5 +1,7 @@ /* Page accueil département */ var apo_editor = null; +var elt_annee_apo_editor = null; +var elt_sem_apo_editor = null; $(document).ready(function () { var table_options = { @@ -17,8 +19,14 @@ $(document).ready(function () { $('table.semlist').DataTable(table_options); let table_editable = document.querySelector("table#semlist.apo_editable"); if (table_editable) { - let apo_save_url = document.querySelector("table#semlist.apo_editable").dataset.apo_save_url; - apo_editor = new ScoFieldEditor(".etapes_apo_str", apo_save_url, false); + let save_url = document.querySelector("table#semlist.apo_editable").dataset.apo_save_url; + apo_editor = new ScoFieldEditor(".etapes_apo_str", save_url, false); + + save_url = document.querySelector("table#semlist.apo_editable").dataset.elt_annee_apo_save_url; + elt_annee_apo_editor = new ScoFieldEditor(".elt_annee_apo", save_url, false); + + save_url = document.querySelector("table#semlist.apo_editable").dataset.elt_sem_apo_save_url; + elt_sem_apo_editor = new ScoFieldEditor(".elt_sem_apo", save_url, false); } }); diff --git a/app/templates/sco_value_error.html b/app/templates/sco_value_error.html index 8a54f619..524f4d88 100644 --- a/app/templates/sco_value_error.html +++ b/app/templates/sco_value_error.html @@ -1,6 +1,5 @@ {# -*- mode: jinja-html -*- #} {% extends 'base.html' %} -{% import 'bootstrap/wtf.html' as wtf %} {% block app_content %} diff --git a/app/views/notes.py b/app/views/notes.py index 4fc69bdc..d6c6e1b0 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -290,10 +290,9 @@ def formsemestre_bulletinetud( code_ine=None, ): format = format or "html" - if not formsemestre_id: - flask.abort(404, "argument manquant: formsemestre_id") + if not isinstance(formsemestre_id, int): - raise ScoInvalidIdType("formsemestre_id must be an integer !") + raise ValueError("formsemestre_id must be an integer !") formsemestre = FormSemestre.query.get_or_404(formsemestre_id) if etudid: etud = models.Identite.query.get_or_404(etudid) @@ -481,11 +480,16 @@ sco_publish( Permission.ScoView, methods=["GET", "POST"], ) -sco_publish( - "/formation_table_recap", - sco_formation_recap.formation_table_recap, - Permission.ScoView, -) + + +@bp.route("/formation_table_recap") +@scodoc +@permission_required(Permission.ScoView) +@scodoc7func +def formation_table_recap(formation_id, format="html"): + return sco_formation_recap.formation_table_recap(formation_id, format="html") + + sco_publish( "/export_recap_formations_annee_scolaire", sco_formation_recap.export_recap_formations_annee_scolaire, @@ -2459,6 +2463,51 @@ def formsemestre_set_apo_etapes(): ScolarNews.add( typ=ScolarNews.NEWS_APO, text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})", + max_frequency=10 * 60, + ) + return ("", 204) + + +@bp.route("/formsemestre_set_elt_annee_apo", methods=["POST"]) +@scodoc +@permission_required(Permission.ScoEditApo) +def formsemestre_set_elt_annee_apo(): + """Change les codes étapes du semestre indiqué. + Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules + """ + oid = int(request.form.get("oid")) + value = (request.form.get("value") or "").strip() + formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid) + if value != formsemestre.elt_annee_apo: + formsemestre.elt_annee_apo = value + db.session.add(formsemestre) + db.session.commit() + ScolarNews.add( + typ=ScolarNews.NEWS_APO, + text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})", + max_frequency=10 * 60, + ) + return ("", 204) + + +@bp.route("/formsemestre_set_elt_sem_apo", methods=["POST"]) +@scodoc +@permission_required(Permission.ScoEditApo) +def formsemestre_set_elt_sem_apo(): + """Change les codes étapes du semestre indiqué. + Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules + """ + oid = int(request.form.get("oid")) + value = (request.form.get("value") or "").strip() + formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid) + if value != formsemestre.elt_sem_apo: + formsemestre.elt_sem_apo = value + db.session.add(formsemestre) + db.session.commit() + ScolarNews.add( + typ=ScolarNews.NEWS_APO, + text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})", + max_frequency=10 * 60, ) return ("", 204) @@ -2480,6 +2529,7 @@ def ue_set_apo(): ScolarNews.add( typ=ScolarNews.NEWS_FORM, text=f"Modification code Apogée d'UE dans la formation {ue.formation.titre} ({ue.formation.acronyme})", + max_frequency=10 * 60, ) return ("", 204) @@ -2501,6 +2551,7 @@ def module_set_apo(): ScolarNews.add( typ=ScolarNews.NEWS_FORM, text=f"Modification code Apogée d'UE dans la formation {mod.formation.titre} ({mod.formation.acronyme})", + max_frequency=10 * 60, ) return ("", 204) diff --git a/requirements-3.9.txt b/requirements-3.9.txt index 2f91f07a..4ca2adc2 100755 --- a/requirements-3.9.txt +++ b/requirements-3.9.txt @@ -1,74 +1,87 @@ -alembic==1.7.5 +alembic==1.7.7 astroid==2.11.2 +async-timeout==4.0.2 attrs==21.4.0 Babel==2.9.1 +black==22.3.0 blinker==1.4 certifi==2021.10.8 cffi==1.15.0 chardet==4.0.0 -charset-normalizer==2.0.9 -click==8.0.3 +charset-normalizer==2.0.12 +click==8.1.2 cracklib==2.9.3 -cryptography==36.0.1 +cryptography==36.0.2 Deprecated==1.2.13 -dnspython==2.1.0 +dill==0.3.4 dominate==2.6.0 email-validator==1.1.3 et-xmlfile==1.1.0 -Flask==2.0.2 +Flask==2.1.1 Flask-Babel==2.0.0 Flask-Bootstrap==3.3.7.1 Flask-Caching==1.10.1 Flask-HTTPAuth==4.5.0 -Flask-Login==0.5.0 +Flask-Login==0.6.0 Flask-Mail==0.9.1 Flask-Migrate==3.1.0 Flask-Moment==1.0.2 Flask-SQLAlchemy==2.5.1 -Flask-WTF==1.0.0 +Flask-WTF==1.0.1 greenlet==1.1.2 gunicorn==20.1.0 icalendar==4.0.9 idna==3.3 +importlib-metadata==4.11.3 iniconfig==1.1.1 isort==5.10.1 -itsdangerous==2.0.1 -Jinja2==3.0.3 +itsdangerous==2.1.2 +Jinja2==3.1.1 lazy-object-proxy==1.7.1 lxml==4.8.0 -Mako==1.1.6 -MarkupSafe==2.0.1 -mccabe==0.6.1 -numpy==1.22.0 +Mako==1.2.0 +MarkupSafe==2.1.1 +mccabe==0.7.0 +mypy==0.942 +mypy-extensions==0.4.3 +numpy==1.22.3 openpyxl==3.0.9 packaging==21.3 -pandas==1.3.5 -Pillow==8.4.0 +pandas==1.4.2 +pathspec==0.9.0 +Pillow==9.1.0 +pkg_resources==0.0.0 +platformdirs==2.5.1 pluggy==1.0.0 psycopg2==2.9.3 py==1.11.0 pycparser==2.21 pydot==1.4.2 PyJWT==2.3.0 -pyOpenSSL==21.0.0 -pyparsing==3.0.6 -pytest==6.2.5 +pylint==2.13.5 +pylint-flask==0.6 +pylint-flask-sqlalchemy==0.2.0 +pylint-plugin-utils==0.7 +pyOpenSSL==22.0.0 +pyparsing==3.0.8 +pytest==7.1.1 python-dateutil==2.8.2 python-docx==0.8.11 -python-dotenv==0.19.2 +python-dotenv==0.20.0 python-editor==1.0.4 -pytz==2021.3 -redis==4.1.0 -reportlab==3.6.5 -requests==2.26.0 +pytz==2022.1 +redis==4.2.2 +reportlab==3.6.9 +requests==2.27.1 rq==1.10.1 six==1.16.0 -SQLAlchemy==1.4.29 +SQLAlchemy==1.4.35 toml==0.10.2 tornado==6.1 -typing-extensions==4.0.1 -urllib3==1.26.7 +typing_extensions==4.1.1 +urllib3==1.26.9 visitor==0.1.3 -Werkzeug==2.0.2 -wrapt==1.13.3 +Werkzeug==2.1.1 +wrapt==1.14.0 WTForms==3.0.1 +zipp==3.8.0 diff --git a/sco_version.py b/sco_version.py index a8d3aae1..24931a73 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.2.6" +SCOVERSION = "9.2.7" SCONAME = "ScoDoc"