diff --git a/app/models/etudiants.py b/app/models/etudiants.py
index 3aacb66a9..b1fec6092 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 8c74fa65b..14c476513 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 16aa336d2..fedf7a489 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 98fc64a2a..11792057c 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 98c67431b..299318ed7 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 83dd79d66..179014cfa 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 6483ffcff..67ac5a6c7 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 600a7cf5a..90c20b45f 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 8a54f6192..524f4d883 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 4fc69bdc8..d6c6e1b0d 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 2f91f07a7..4ca2adc21 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 a8d3aae11..24931a73b 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"