From f7db75e1a2b8358445e74f1a5668ea9c3b61a354 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 17 Jan 2022 22:32:44 +0100 Subject: [PATCH 01/21] Fix: exception si import notes sur etuds non inscrit --- app/scodoc/sco_exceptions.py | 10 +++++----- app/scodoc/sco_saisie_notes.py | 6 +++--- sco_version.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/scodoc/sco_exceptions.py b/app/scodoc/sco_exceptions.py index d975766ef..dd872a7aa 100644 --- a/app/scodoc/sco_exceptions.py +++ b/app/scodoc/sco_exceptions.py @@ -36,11 +36,6 @@ class ScoException(Exception): pass -class NoteProcessError(ScoException): - "misc errors in process" - pass - - class InvalidEtudId(NoteProcessError): pass @@ -56,6 +51,11 @@ class ScoValueError(ScoException): self.dest_url = dest_url +class NoteProcessError(ScoValueError): + "Valeurs notes invalides" + pass + + class ScoFormatError(ScoValueError): pass diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index 0fa105db8..d0a5407dd 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -487,10 +487,10 @@ def notes_add( } for (etudid, value) in notes: if check_inscription and (etudid not in inscrits): - raise NoteProcessError("etudiant non inscrit dans ce module") - if not ((value is None) or (type(value) == type(1.0))): + raise NoteProcessError(f"etudiant {etudid} non inscrit dans ce module") + if (value is not None) and not isinstance(value, float): raise NoteProcessError( - "etudiant %s: valeur de note invalide (%s)" % (etudid, value) + f"etudiant {etudid}: valeur de note invalide ({value})" ) # Recherche notes existantes notes_db = sco_evaluation_db.do_evaluation_get_all_notes(evaluation_id) diff --git a/sco_version.py b/sco_version.py index cab845d19..23676c26f 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.1.25" +SCOVERSION = "9.1.26" SCONAME = "ScoDoc" From 30e7fd516b254df657dc2c0a4c0d323db0115f5f Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 18 Jan 2022 20:23:47 +0100 Subject: [PATCH 02/21] exceptions decl. --- app/scodoc/sco_exceptions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/scodoc/sco_exceptions.py b/app/scodoc/sco_exceptions.py index dd872a7aa..5f64f57b2 100644 --- a/app/scodoc/sco_exceptions.py +++ b/app/scodoc/sco_exceptions.py @@ -36,10 +36,6 @@ class ScoException(Exception): pass -class InvalidEtudId(NoteProcessError): - pass - - class InvalidNoteValue(ScoException): pass @@ -56,6 +52,10 @@ class NoteProcessError(ScoValueError): pass +class InvalidEtudId(NoteProcessError): + pass + + class ScoFormatError(ScoValueError): pass From 01dcd8cccda5d953f462f67a11b60f2527739ef1 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 18 Jan 2022 21:38:00 +0100 Subject: [PATCH 03/21] =?UTF-8?q?Migration:=20tol=C3=A8re=20dates=20logs?= =?UTF-8?q?=20aberrantes,=20et=20=C3=A9limine=20relations=20manquantes=20d?= =?UTF-8?q?ans=20entreprises?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scodoc.py | 30 +++++++++++++++++++++++------- tools/import_scodoc7_dept.py | 8 +++++++- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/scodoc.py b/scodoc.py index 03f656a7c..976443a3e 100755 --- a/scodoc.py +++ b/scodoc.py @@ -278,20 +278,36 @@ def user_role(username, dept_acronym=None, add_role_name=None, remove_role_name= db.session.commit() +def abort_if_false(ctx, param, value): + if not value: + ctx.abort() + + @app.cli.command() +@click.option( + "--yes", + is_flag=True, + callback=abort_if_false, + expose_value=False, + prompt=f"""Attention: Cela va effacer toutes les données du département + (étudiants, notes, formations, etc) + Voulez-vous vraiment continuer ? + """, +) @click.argument("dept") def delete_dept(dept): # delete-dept """Delete existing departement""" from app.scodoc import notesdb as ndb from app.scodoc import sco_dept - click.confirm( - f"""Attention: Cela va effacer toutes les données du département {dept} - (étudiants, notes, formations, etc) - Voulez-vous vraiment continuer ? - """, - abort=True, - ) + if False: + click.confirm( + f"""Attention: Cela va effacer toutes les données du département {dept} + (étudiants, notes, formations, etc) + Voulez-vous vraiment continuer ? + """, + abort=True, + ) db.reflect() ndb.open_db_connection() d = models.Departement.query.filter_by(acronym=dept).first() diff --git a/tools/import_scodoc7_dept.py b/tools/import_scodoc7_dept.py index 597b9baf6..75f5e9a3b 100644 --- a/tools/import_scodoc7_dept.py +++ b/tools/import_scodoc7_dept.py @@ -170,6 +170,11 @@ def import_scodoc7_dept(dept_id: str, dept_db_uri=None): logging.info(f"connecting to database {dept_db_uri}") cnx = psycopg2.connect(dept_db_uri) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) + # FIX : des dates aberrantes (dans le futur) peuvent tenir en SQL mais pas en Python + cursor.execute( + """UPDATE scolar_events SET event_date='2021-09-30' WHERE event_date > '2200-01-01'""" + ) + cnx.commit() # Create dept: dept = models.Departement(acronym=dept_id, description="migré de ScoDoc7") db.session.add(dept) @@ -374,6 +379,8 @@ def convert_object( new_ref = id_from_scodoc7[old_ref] elif (not is_table) and table_name in { "scolog", + "entreprise_correspondant", + "entreprise_contact", "etud_annotations", "notes_notes_log", "scolar_news", @@ -389,7 +396,6 @@ def convert_object( new_ref = None elif is_table and table_name in { "notes_semset_formsemestre", - "entreprise_contact", }: # pour anciennes installs où des relations n'avait pas été déclarées clés étrangères # eg: notes_semset_formsemestre.semset_id n'était pas une clé From b53969dbddd964878cea7c2858cafca68ce2ea4a Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 18 Jan 2022 22:01:30 +0100 Subject: [PATCH 04/21] =?UTF-8?q?Option=20pour=20faire=20passer=20les=20?= =?UTF-8?q?=C3=A9tudiants=20m=C3=AAme=20sans=20d=C3=A9cision=20de=20jury?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scodoc/sco_inscr_passage.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/app/scodoc/sco_inscr_passage.py b/app/scodoc/sco_inscr_passage.py index ab27ecdae..7a60b67ae 100644 --- a/app/scodoc/sco_inscr_passage.py +++ b/app/scodoc/sco_inscr_passage.py @@ -49,9 +49,11 @@ from app.scodoc import sco_etud from app.scodoc.sco_exceptions import ScoValueError -def list_authorized_etuds_by_sem(sem, delai=274): +def list_authorized_etuds_by_sem(sem, delai=274, ignore_jury=False): """Liste des etudiants autorisés à s'inscrire dans sem. delai = nb de jours max entre la date de l'autorisation et celle de debut du semestre cible. + ignore_jury: si vrai, considère tous les étudiants comem autorisés, même + s'ils n'ont pas de décision de jury. """ src_sems = list_source_sems(sem, delai=delai) inscrits = list_inscrits(sem["formsemestre_id"]) @@ -59,7 +61,12 @@ def list_authorized_etuds_by_sem(sem, delai=274): candidats = {} # etudid : etud (tous les etudiants candidats) nb = 0 # debug for src in src_sems: - liste = list_etuds_from_sem(src, sem) + if ignore_jury: + # liste de tous les inscrits au semestre (sans dems) + liste = list_inscrits(src["formsemestre_id"]).values() + else: + # liste des étudiants autorisés par le jury à s'inscrire ici + liste = list_etuds_from_sem(src, sem) liste_filtree = [] for e in liste: # Filtre ceux qui se sont déjà inscrit dans un semestre APRES le semestre src @@ -125,7 +132,7 @@ def list_inscrits(formsemestre_id, with_dems=False): return inscr -def list_etuds_from_sem(src, dst): +def list_etuds_from_sem(src, dst) -> list[dict]: """Liste des etudiants du semestre src qui sont autorisés à passer dans le semestre dst.""" target = dst["semestre_id"] dpv = sco_pvjury.dict_pvjury(src["formsemestre_id"]) @@ -224,7 +231,7 @@ def do_desinscrit(sem, etudids): ) -def list_source_sems(sem, delai=None): +def list_source_sems(sem, delai=None) -> list[dict]: """Liste des semestres sources sem est le semestre destination """ @@ -265,6 +272,7 @@ def formsemestre_inscr_passage( inscrit_groupes=False, submitted=False, dialog_confirmed=False, + ignore_jury=False, ): """Form. pour inscription des etudiants d'un semestre dans un autre (donné par formsemestre_id). @@ -280,6 +288,7 @@ def formsemestre_inscr_passage( """ inscrit_groupes = int(inscrit_groupes) + ignore_jury = int(ignore_jury) sem = sco_formsemestre.get_formsemestre(formsemestre_id) # -- check lock if not sem["etat"]: @@ -295,7 +304,9 @@ def formsemestre_inscr_passage( elif etuds and isinstance(etuds[0], str): etuds = [int(x) for x in etuds] - auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem(sem) + auth_etuds_by_sem, inscrits, candidats = list_authorized_etuds_by_sem( + sem, ignore_jury=ignore_jury + ) etuds_set = set(etuds) candidats_set = set(candidats) inscrits_set = set(inscrits) @@ -323,6 +334,7 @@ def formsemestre_inscr_passage( candidats_non_inscrits, inscrits_ailleurs, inscrit_groupes=inscrit_groupes, + ignore_jury=ignore_jury, ) else: if not dialog_confirmed: @@ -411,18 +423,23 @@ def build_page( candidats_non_inscrits, inscrits_ailleurs, inscrit_groupes=False, + ignore_jury=False, ): inscrit_groupes = int(inscrit_groupes) + ignore_jury = int(ignore_jury) if inscrit_groupes: inscrit_groupes_checked = " checked" else: inscrit_groupes_checked = "" - + if ignore_jury: + ignore_jury_checked = " checked" + else: + ignore_jury_checked = "" H = [ html_sco_header.html_sem_header( "Passages dans le semestre", sem, with_page_header=False ), - """
""" % request.base_url, + """""" % request.base_url, """  aide @@ -430,6 +447,8 @@ def build_page( % sem, # " """inscrire aux mêmes groupes""" % inscrit_groupes_checked, + """inclure tous les étudiants (même sans décision de jury)""" + % ignore_jury_checked, """
Actuellement %s inscrits et %d candidats supplémentaires
""" From 0e6d5701688cfce13d668835f2f05390e7a0e279 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 18 Jan 2022 22:55:33 +0100 Subject: [PATCH 05/21] =?UTF-8?q?oubli=20dans=20le=20commit=20pr=C3=A9c?= =?UTF-8?q?=C3=A9dent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scodoc/sco_inscr_passage.py | 1 + sco_version.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scodoc/sco_inscr_passage.py b/app/scodoc/sco_inscr_passage.py index 7a60b67ae..8b0f6cbc0 100644 --- a/app/scodoc/sco_inscr_passage.py +++ b/app/scodoc/sco_inscr_passage.py @@ -375,6 +375,7 @@ def formsemestre_inscr_passage( "formsemestre_id": formsemestre_id, "etuds": ",".join([str(x) for x in etuds]), "inscrit_groupes": inscrit_groupes, + "ignore_jury": ignore_jury, "submitted": 1, }, ) diff --git a/sco_version.py b/sco_version.py index 23676c26f..88ff029d7 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.1.26" +SCOVERSION = "9.1.27" SCONAME = "ScoDoc" From f524dcaf589d74fc5ca6fe95b53917ce85ae38b4 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 19 Jan 2022 22:40:41 +0100 Subject: [PATCH 06/21] Variable SCODOC_MAIL_FROM pour les envois d'exceptions --- app/__init__.py | 2 +- app/auth/email.py | 2 +- app/scodoc/sco_preferences.py | 7 ++++--- config.py | 3 +++ sco_version.py | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 04e28207c..258720aca 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -249,7 +249,7 @@ def create_app(config_class=DevConfig): host_name = socket.gethostname() mail_handler = ScoSMTPHandler( mailhost=(app.config["MAIL_SERVER"], app.config["MAIL_PORT"]), - fromaddr="no-reply@" + app.config["MAIL_SERVER"], + fromaddr=app.config["SCODOC_MAIL_FROM"], toaddrs=["exception@scodoc.org"], subject="ScoDoc Exception", # unused see ScoSMTPHandler credentials=auth, diff --git a/app/auth/email.py b/app/auth/email.py index 9ac8a0173..617596910 100644 --- a/app/auth/email.py +++ b/app/auth/email.py @@ -8,7 +8,7 @@ def send_password_reset_email(user): token = user.get_reset_password_token() send_email( "[ScoDoc] Réinitialisation de votre mot de passe", - sender=current_app.config["ADMINS"][0], + sender=current_app.config["SCODOC_MAIL_FROM"], recipients=[user.email], text_body=render_template("email/reset_password.txt", user=user, token=token), html_body=render_template("email/reset_password.html", user=user, token=token), diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index b38fb736f..77242d656 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -111,8 +111,9 @@ get_base_preferences(formsemestre_id) """ import flask -from flask import g, url_for, request -from flask_login import current_user +from flask import g, request, current_app + +# from flask_login import current_user from app.models import Departement from app.scodoc import sco_cache @@ -1537,7 +1538,7 @@ class BasePreferences(object): ( "email_from_addr", { - "initvalue": "noreply@scodoc.example.com", + "initvalue": current_app.config["SCODOC_MAIL_FROM"], "title": "adresse mail origine", "size": 40, "explanation": "adresse expéditeur pour les envois par mails (bulletins)", diff --git a/config.py b/config.py index ae507b1e5..fca2fc514 100755 --- a/config.py +++ b/config.py @@ -26,6 +26,9 @@ class Config: SCODOC_ADMIN_LOGIN = os.environ.get("SCODOC_ADMIN_LOGIN") or "admin" ADMINS = [SCODOC_ADMIN_MAIL] SCODOC_ERR_MAIL = os.environ.get("SCODOC_ERR_MAIL") + # Le "from" des mails émis. Attention: peut être remplacée par la préférence email_from_addr: + SCODOC_MAIL_FROM = os.environ.get("SCODOC_MAIL_FROM") or ("no-reply@" + MAIL_SERVER) + BOOTSTRAP_SERVE_LOCAL = os.environ.get("BOOTSTRAP_SERVE_LOCAL") SCODOC_DIR = os.environ.get("SCODOC_DIR", "/opt/scodoc") SCODOC_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "/opt/scodoc-data") diff --git a/sco_version.py b/sco_version.py index 88ff029d7..5ddad6197 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.1.27" +SCOVERSION = "9.1.28" SCONAME = "ScoDoc" From 908f78477dd0a0399251db6dbd1f353f81d2a523 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 19 Jan 2022 23:02:15 +0100 Subject: [PATCH 07/21] =?UTF-8?q?Augmente=20taille=20max=20code=20Apog?= =?UTF-8?q?=C3=A9e=20+=20check=20edit=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/__init__.py | 2 +- app/scodoc/sco_edit_module.py | 9 +- app/scodoc/sco_edit_ue.py | 3 +- ...874ed6af64_augmente_taille_codes_apogee.py | 84 +++++++++++++++++++ 4 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 migrations/versions/28874ed6af64_augmente_taille_codes_apogee.py diff --git a/app/models/__init__.py b/app/models/__init__.py index 0fee7bc48..4a3328b32 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -6,7 +6,7 @@ XXX version préliminaire ScoDoc8 #sco8 sans département CODE_STR_LEN = 16 # chaine pour les codes SHORT_STR_LEN = 32 # courtes chaine, eg acronymes -APO_CODE_STR_LEN = 24 # nb de car max d'un code Apogée +APO_CODE_STR_LEN = 512 # nb de car max d'un code Apogée (il peut y en avoir plusieurs) GROUPNAME_STR_LEN = 64 from app.models.raw_sql_init import create_database_functions diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index 813320d53..25ec3d25f 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -32,15 +32,15 @@ import flask from flask import url_for, render_template from flask import g, request from flask_login import current_user + +from app import log +from app import models from app.models import APO_CODE_STR_LEN -from app.models import Matiere, Module, UniteEns +from app.models import Formation, Matiere, Module, UniteEns import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import ModuleType -from app import log -from app import models -from app.models import Formation from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.sco_permissions import Permission from app.scodoc.sco_exceptions import ( @@ -294,6 +294,7 @@ def module_create(matiere_id=None, module_type=None, semestre_id=None): "title": "Code Apogée", "size": 25, "explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules", + "validator": lambda val, _: len(val) < APO_CODE_STR_LEN, }, ), ( diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index a52826b70..c9a2da171 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -1216,7 +1216,8 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False): def edit_ue_set_code_apogee(id=None, value=None): "set UE code apogee" ue_id = id - value = value.strip("-_ \t") + value = value.strip("-_ \t")[:APO_CODE_STR_LEN] # tronque + log("edit_ue_set_code_apogee: ue_id=%s code_apogee=%s" % (ue_id, value)) ues = ue_list(args={"ue_id": ue_id}) diff --git a/migrations/versions/28874ed6af64_augmente_taille_codes_apogee.py b/migrations/versions/28874ed6af64_augmente_taille_codes_apogee.py new file mode 100644 index 000000000..7ac8c8c36 --- /dev/null +++ b/migrations/versions/28874ed6af64_augmente_taille_codes_apogee.py @@ -0,0 +1,84 @@ +"""augmente taille codes Apogée + +Revision ID: 28874ed6af64 +Revises: f40fbaf5831c +Create Date: 2022-01-19 22:57:59.678313 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "28874ed6af64" +down_revision = "f40fbaf5831c" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + op.alter_column( + "notes_formsemestre_etapes", + "etape_apo", + existing_type=sa.VARCHAR(length=24), + type_=sa.String(length=512), + existing_nullable=True, + ) + op.alter_column( + "notes_formsemestre_inscription", + "etape", + existing_type=sa.VARCHAR(length=24), + type_=sa.String(length=512), + existing_nullable=True, + ) + op.alter_column( + "notes_modules", + "code_apogee", + existing_type=sa.VARCHAR(length=24), + type_=sa.String(length=512), + existing_nullable=True, + ) + op.alter_column( + "notes_ue", + "code_apogee", + existing_type=sa.VARCHAR(length=24), + type_=sa.String(length=512), + existing_nullable=True, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "notes_ue", + "code_apogee", + existing_type=sa.String(length=512), + type_=sa.VARCHAR(length=24), + existing_nullable=True, + ) + op.alter_column( + "notes_modules", + "code_apogee", + existing_type=sa.String(length=512), + type_=sa.VARCHAR(length=24), + existing_nullable=True, + ) + op.alter_column( + "notes_formsemestre_inscription", + "etape", + existing_type=sa.String(length=512), + type_=sa.VARCHAR(length=24), + existing_nullable=True, + ) + op.alter_column( + "notes_formsemestre_etapes", + "etape_apo", + existing_type=sa.String(length=512), + type_=sa.VARCHAR(length=24), + existing_nullable=True, + ) + + # ### end Alembic commands ### From e5fb9f4365617d207bd20cbc3068f098fdf777c2 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 19 Jan 2022 23:56:32 +0100 Subject: [PATCH 08/21] Fix: ancien mode de calcul nt / cmp py3 --- app/scodoc/notes_table.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py index 996feddab..2209aea7d 100644 --- a/app/scodoc/notes_table.py +++ b/app/scodoc/notes_table.py @@ -814,7 +814,12 @@ class NotesTable: moy_ue_cap = ue_cap["moy"] mu["was_capitalized"] = True event_date = event_date or ue_cap["event_date"] - if (moy_ue_cap != "NA") and (moy_ue_cap > max_moy_ue): + if ( + (moy_ue_cap != "NA") + and isinstance(moy_ue_cap, float) + and isinstance(max_moy_ue, float) + and (moy_ue_cap > max_moy_ue) + ): # meilleure UE capitalisée event_date = ue_cap["event_date"] max_moy_ue = moy_ue_cap From fa5fcc8f571d274fb92d80b194a566edda899a62 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 20 Jan 2022 13:00:25 +0100 Subject: [PATCH 09/21] =?UTF-8?q?Edition=20modules:=20interdit=20changemen?= =?UTF-8?q?t=20de=20semestre=20si=20utilis=C3=A9s.=20+=20doc=20+=20bug=20p?= =?UTF-8?q?oids=20vers=20UE=20d'autres=20semestres?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/comp/moy_mod.py | 6 ++- app/models/evaluations.py | 11 ++++- app/models/formsemestre.py | 2 +- app/scodoc/sco_edit_module.py | 58 +++++++++++++++++++++----- app/scodoc/sco_evaluation_edit.py | 6 ++- app/scodoc/sco_moduleimpl_status.py | 4 +- app/static/css/scodoc.css | 10 +++++ app/templates/scodoc/help/modules.html | 20 +++++++++ sco_version.py | 2 +- 9 files changed, 102 insertions(+), 17 deletions(-) diff --git a/app/comp/moy_mod.py b/app/comp/moy_mod.py index f21e6acdd..0caadb9b5 100644 --- a/app/comp/moy_mod.py +++ b/app/comp/moy_mod.py @@ -40,6 +40,7 @@ from app import log from app import models from app.models import ModuleImpl, Evaluation, EvaluationUEPoids from app.scodoc import sco_utils as scu +from app.scodoc.sco_exceptions import ScoValueError def df_load_evaluations_poids( @@ -60,7 +61,10 @@ def df_load_evaluations_poids( for eval_poids in EvaluationUEPoids.query.join( EvaluationUEPoids.evaluation ).filter_by(moduleimpl_id=moduleimpl_id): - df[eval_poids.ue_id][eval_poids.evaluation_id] = eval_poids.poids + try: + df[eval_poids.ue_id][eval_poids.evaluation_id] = eval_poids.poids + except KeyError as exc: + pass # poids vers des UE qui n'existent plus ou sont dans un autre semestre... if default_poids is not None: df.fillna(value=default_poids, inplace=True) return df, ues diff --git a/app/models/evaluations.py b/app/models/evaluations.py index 4f06fb75f..b4e5f4e2f 100644 --- a/app/models/evaluations.py +++ b/app/models/evaluations.py @@ -103,7 +103,16 @@ class Evaluation(db.Model): Note: si les poids ne sont pas initialisés (poids par défaut), ils ne sont pas affichés. """ - return ", ".join([f"{p.ue.acronyme}: {p.poids}" for p in self.ue_poids]) + # restreint aux UE du semestre dans lequel est cette évaluation + # au cas où le module ait changé de semestre et qu'il reste des poids + evaluation_semestre_idx = self.moduleimpl.module.semestre_id + return ", ".join( + [ + f"{p.ue.acronyme}: {p.poids}" + for p in self.ue_poids + if evaluation_semestre_idx == p.ue.semestre_idx + ] + ) class EvaluationUEPoids(db.Model): diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index 450ea2ccf..ca159f0fd 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -224,7 +224,7 @@ class FormSemestre(db.Model): self.date_fin.year})""" def titre_num(self) -> str: - """Le titre est le semestre, ex ""DUT Informatique semestre 2"" """ + """Le titre et le semestre, ex ""DUT Informatique semestre 2"" """ if self.semestre_id == sco_codes_parcours.NO_SEMESTRE_ID: return self.titre return f"{self.titre} {self.formation.get_parcours().SESSION_NAME} {self.semestre_id}" diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index 25ec3d25f..5ebf599df 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -37,6 +37,7 @@ from app import log from app import models from app.models import APO_CODE_STR_LEN from app.models import Formation, Matiere, Module, UniteEns +from app.models import FormSemestre, ModuleImpl import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu @@ -473,16 +474,31 @@ def module_edit(module_id=None): formation_id = module["formation_id"] formation = sco_formations.formation_list(args={"formation_id": formation_id})[0] parcours = sco_codes_parcours.get_parcours_from_code(formation["type_parcours"]) - is_apc = parcours.APC_SAE - ues_matieres = ndb.SimpleDictFetch( - """SELECT ue.acronyme, mat.*, mat.id AS matiere_id - FROM notes_matieres mat, notes_ue ue - WHERE mat.ue_id = ue.id - AND ue.formation_id = %(formation_id)s - ORDER BY ue.numero, mat.numero - """, - {"formation_id": formation_id}, - ) + is_apc = parcours.APC_SAE # BUT + in_use = len(a_module.modimpls.all()) > 0 # il y a des modimpls + if in_use: + # matières du même semestre seulement + ues_matieres = ndb.SimpleDictFetch( + """SELECT ue.acronyme, mat.*, mat.id AS matiere_id + FROM notes_matieres mat, notes_ue ue + WHERE mat.ue_id = ue.id + AND ue.formation_id = %(formation_id)s + AND ue.semestre_idx = %(semestre_idx)s + ORDER BY ue.numero, mat.numero + """, + {"formation_id": formation_id, "semestre_idx": a_module.ue.semestre_idx}, + ) + else: + # matières de la formation + ues_matieres = ndb.SimpleDictFetch( + """SELECT ue.acronyme, mat.*, mat.id AS matiere_id + FROM notes_matieres mat, notes_ue ue + WHERE mat.ue_id = ue.id + AND ue.formation_id = %(formation_id)s + ORDER BY ue.numero, mat.numero + """, + {"formation_id": formation_id}, + ) mat_names = ["%s / %s" % (x["acronyme"], x["titre"]) for x in ues_matieres] ue_mat_ids = ["%s!%s" % (x["ue_id"], x["matiere_id"]) for x in ues_matieres] module["ue_matiere_id"] = "%s!%s" % (module["ue_id"], module["matiere_id"]) @@ -501,12 +517,25 @@ def module_edit(module_id=None): ), """

Modification du module %(titre)s""" % module, """ (formation %(acronyme)s, version %(version)s)

""" % formation, - render_template("scodoc/help/modules.html", is_apc=is_apc), + render_template( + "scodoc/help/modules.html", + is_apc=is_apc, + formsemestres=FormSemestre.query.filter( + ModuleImpl.formsemestre_id == FormSemestre.id, + ModuleImpl.module_id == module_id, + ).all(), + ), ] if not unlocked: H.append( """
Formation verrouillée, seuls certains éléments peuvent être modifiés
""" ) + if in_use: + H.append( + """
Module déjà utilisé dans des semestres, + soyez prudents ! +
""" + ) descr = [ ( @@ -679,6 +708,13 @@ def module_edit(module_id=None): else: # l'UE peut changer tf[2]["ue_id"], tf[2]["matiere_id"] = tf[2]["ue_matiere_id"].split("!") + old_ue_id = a_module.ue.id + new_ue_id = int(tf[2]["ue_id"]) + if (old_ue_id != new_ue_id) and in_use: + # pas changer de semestre un module utilisé ! + raise ScoValueError( + "Module utilisé: il ne peut pas être changé de semestre !" + ) # En APC, force le semestre égal à celui de l'UE if is_apc: selected_ue = UniteEns.query.get(tf[2]["ue_id"]) diff --git a/app/scodoc/sco_evaluation_edit.py b/app/scodoc/sco_evaluation_edit.py index c324a89be..ab178c150 100644 --- a/app/scodoc/sco_evaluation_edit.py +++ b/app/scodoc/sco_evaluation_edit.py @@ -143,6 +143,7 @@ def evaluation_create_form( if vals.get("tf_submitted", False) and "visibulletinlist" not in vals: vals["visibulletinlist"] = [] # + ue_coef_dict = {} if is_apc: # BUT: poids vers les UE ue_coef_dict = ModuleImpl.query.get(moduleimpl_id).module.get_ue_coef_dict() for ue in sem_ues: @@ -290,7 +291,10 @@ def evaluation_create_form( "title": f"Poids {ue.acronyme}", "size": 2, "type": "float", - "explanation": f"{ue.titre}", + "explanation": f""" + {ue_coef_dict.get(ue.id, 0.)} + {ue.titre} + """, "allow_null": False, }, ), diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index 27de3a699..d44b52352 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -397,7 +397,9 @@ def moduleimpl_status(moduleimpl_id=None, partition_id=None): eval_index = len(mod_evals) - 1 first_eval = True for eval in mod_evals: - evaluation = Evaluation.query.get(eval["evaluation_id"]) # TODO unifier + evaluation: Evaluation = Evaluation.query.get( + eval["evaluation_id"] + ) # TODO unifier etat = sco_evaluations.do_evaluation_etat( eval["evaluation_id"], partition_id=partition_id, diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index 72a918a69..4ff176753 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -1491,6 +1491,16 @@ table.moduleimpl_evaluations td.eval_poids { color:rgb(0, 0, 255); } +span.eval_coef_ue { + color:rgb(6, 73, 6); + font-style: normal; + font-size: 80%; + margin-right: 2em; +} +span.eval_coef_ue_titre { + +} + /* Formulaire edition des partitions */ form#editpart table { border: 1px solid gray; diff --git a/app/templates/scodoc/help/modules.html b/app/templates/scodoc/help/modules.html index d01a5d351..cd6e0767b 100644 --- a/app/templates/scodoc/help/modules.html +++ b/app/templates/scodoc/help/modules.html @@ -24,4 +24,24 @@ la documentation.

{%endif%} + + {% if formsemestres %} +

+ Ce module est utilisé dans des semestres déjà mis en place, il faut prêter attention + aux conséquences des changements effectués ici: par exemple les coefficients vont modifier + les notes moyennes calculées. Les modules déjà utilisés ne peuvent pas être changés de semestre, ni détruits. + Si vous souhaitez faire cela, allez d'abord modifier les semestres concernés pour déselectionner le module. +

+

Semestres utilisant ce module:

+ + {%endif%} + \ No newline at end of file diff --git a/sco_version.py b/sco_version.py index 5ddad6197..36fffe4e4 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.1.28" +SCOVERSION = "9.1.29" SCONAME = "ScoDoc" From 687d5d65695994eec80c5f06a97e6876c145732f Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 20 Jan 2022 13:14:04 +0100 Subject: [PATCH 10/21] =?UTF-8?q?BUT:=20Evite=20de=20planter=20si=20UE=20d?= =?UTF-8?q?e=20plusieurs=20semestres=20dans=20le=20m=C3=AAme=20formsemestr?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scodoc/notes_table.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py index 2209aea7d..4d586c2cc 100644 --- a/app/scodoc/notes_table.py +++ b/app/scodoc/notes_table.py @@ -1359,7 +1359,11 @@ class NotesTable: t[0] = results.etud_moy_gen[etudid] for i, ue in enumerate(ues, start=1): if ue["type"] != UE_SPORT: - t[i] = results.etud_moy_ue[ue["id"]][etudid] + # temporaire pour 9.1.29 ! + if ue["id"] in results.etud_moy_ue: + t[i] = results.etud_moy_ue[ue["id"]][etudid] + else: + t[i] = "" # re-trie selon la nouvelle moyenne générale: self.T.sort(key=self._row_key) # Remplace aussi le rang: From a60811ce69844f19cc20c818e9f35982e8369178 Mon Sep 17 00:00:00 2001 From: "pascal.bouron" Date: Thu, 20 Jan 2022 22:41:39 +0100 Subject: [PATCH 11/21] =?UTF-8?q?Mise=20=C3=A0=20jour=20de=20'app/scodoc/s?= =?UTF-8?q?co=5Fundo=5Fnotes.py'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajout evaluation_id dans export de "lister toutes les saisies" --- app/scodoc/sco_undo_notes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/scodoc/sco_undo_notes.py b/app/scodoc/sco_undo_notes.py index 56684e399..5d169b8bc 100644 --- a/app/scodoc/sco_undo_notes.py +++ b/app/scodoc/sco_undo_notes.py @@ -181,7 +181,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"): """ sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True) r = ndb.SimpleDictFetch( - """SELECT i.nom, i.prenom, code_nip, n.*, mod.titre, e.description, e.jour, u.user_name + """SELECT i.nom, i.prenom, code_nip, n.*, mod.titre, e.description, e.jour, u.user_name, e.id as evaluation_id FROM notes_notes n, notes_evaluation e, notes_moduleimpl mi, notes_modules mod, identite i, "user" u WHERE mi.id = e.moduleimpl_id @@ -202,6 +202,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"): "value", "user_name", "titre", + "evaluation_id", "description", "jour", "comment", @@ -214,6 +215,7 @@ def formsemestre_list_saisies_notes(formsemestre_id, format="html"): "value": "Note", "comment": "Remarque", "user_name": "Enseignant", + "evaluation_id": "evaluation_id", "titre": "Module", "description": "Evaluation", "jour": "Date éval.", From 90bff9ded6bdecbad4908490a42ac8565524f800 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 21 Jan 2022 00:46:45 +0100 Subject: [PATCH 12/21] =?UTF-8?q?Config;=20des=20codes=20Apog=C3=A9e.=20Cl?= =?UTF-8?q?oses=20#111.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/__init__.py | 4 +- app/models/preferences.py | 110 +------------------------------ app/scodoc/sco_apogee_csv.py | 49 +++----------- app/scodoc/sco_codes_parcours.py | 1 + app/scodoc/sco_groups.py | 8 ++- app/scodoc/sco_portal_apogee.py | 4 +- app/templates/configuration.html | 2 + app/views/scodoc.py | 53 +++++++++------ app/views/users.py | 2 +- scodoc.py | 8 --- 10 files changed, 59 insertions(+), 182 deletions(-) diff --git a/app/models/__init__.py b/app/models/__init__.py index 4a3328b32..364943aa8 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -12,7 +12,7 @@ GROUPNAME_STR_LEN = 64 from app.models.raw_sql_init import create_database_functions from app.models.absences import Absence, AbsenceNotification, BilletAbsence - +from app.models.config import ScoDocSiteConfig from app.models.departements import Departement from app.models.entreprises import ( @@ -63,7 +63,7 @@ from app.models.notes import ( NotesNotes, NotesNotesLog, ) -from app.models.preferences import ScoPreference, ScoDocSiteConfig +from app.models.preferences import ScoPreference from app.models.but_refcomp import ( ApcReferentielCompetences, diff --git a/app/models/preferences.py b/app/models/preferences.py index 59c82ec80..924f6e604 100644 --- a/app/models/preferences.py +++ b/app/models/preferences.py @@ -2,9 +2,8 @@ """Model : preferences """ -from app import db, log -from app.scodoc import bonus_sport -from app.scodoc.sco_exceptions import ScoValueError + +from app import db class ScoPreference(db.Model): @@ -19,108 +18,3 @@ class ScoPreference(db.Model): name = db.Column(db.String(128), nullable=False, index=True) value = db.Column(db.Text()) formsemestre_id = db.Column(db.Integer, db.ForeignKey("notes_formsemestre.id")) - - -class ScoDocSiteConfig(db.Model): - """Config. d'un site - Nouveau en ScoDoc 9: va regrouper les paramètres qui dans les versions - antérieures étaient dans scodoc_config.py - """ - - __tablename__ = "scodoc_site_config" - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(128), nullable=False, index=True) - value = db.Column(db.Text()) - - BONUS_SPORT = "bonus_sport_func_name" - NAMES = { - BONUS_SPORT: str, - "always_require_ine": bool, - "SCOLAR_FONT": str, - "SCOLAR_FONT_SIZE": str, - "SCOLAR_FONT_SIZE_FOOT": str, - "INSTITUTION_NAME": str, - "INSTITUTION_ADDRESS": str, - "INSTITUTION_CITY": str, - "DEFAULT_PDF_FOOTER_TEMPLATE": str, - } - - def __init__(self, name, value): - self.name = name - self.value = value - - def __repr__(self): - return f"<{self.__class__.__name__}('{self.name}', '{self.value}')>" - - def get_dict(self) -> dict: - "Returns all data as a dict name = value" - return { - c.name: self.NAMES.get(c.name, lambda x: x)(c.value) - for c in ScoDocSiteConfig.query.all() - } - - @classmethod - def set_bonus_sport_func(cls, func_name): - """Record bonus_sport config. - If func_name not defined, raise NameError - """ - if func_name not in cls.get_bonus_sport_func_names(): - raise NameError("invalid function name for bonus_sport") - c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first() - if c: - log("setting to " + func_name) - c.value = func_name - else: - c = ScoDocSiteConfig(cls.BONUS_SPORT, func_name) - db.session.add(c) - db.session.commit() - - @classmethod - def get_bonus_sport_func_name(cls): - """Get configured bonus function name, or None if None.""" - f = cls.get_bonus_sport_func_from_name() - if f is None: - return "" - else: - return f.__name__ - - @classmethod - def get_bonus_sport_func(cls): - """Get configured bonus function, or None if None.""" - return cls.get_bonus_sport_func_from_name() - - @classmethod - def get_bonus_sport_func_from_name(cls, func_name=None): - """returns bonus func with specified name. - If name not specified, return the configured function. - None if no bonus function configured. - Raises ScoValueError if func_name not found in module bonus_sport. - """ - if func_name is None: - c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first() - if c is None: - return None - func_name = c.value - if func_name == "": # pas de bonus défini - return None - try: - return getattr(bonus_sport, func_name) - except AttributeError: - raise ScoValueError( - f"""Fonction de calcul maison inexistante: {func_name}. - (contacter votre administrateur local).""" - ) - - @classmethod - def get_bonus_sport_func_names(cls): - """List available functions names - (starting with empty string to represent "no bonus function"). - """ - return [""] + sorted( - [ - getattr(bonus_sport, name).__name__ - for name in dir(bonus_sport) - if name.startswith("bonus_") - ] - ) diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py index f97cc895e..833a7841f 100644 --- a/app/scodoc/sco_apogee_csv.py +++ b/app/scodoc/sco_apogee_csv.py @@ -95,30 +95,21 @@ from flask import send_file # Pour la détection auto de l'encodage des fichiers Apogée: from chardet import detect as chardet_detect +from app.models.config import ScoDocSiteConfig import app.scodoc.sco_utils as scu -import app.scodoc.notesdb as ndb from app import log from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError from app.scodoc.gen_tables import GenTable from app.scodoc.sco_vdi import ApoEtapeVDI from app.scodoc.sco_codes_parcours import code_semestre_validant from app.scodoc.sco_codes_parcours import ( - ADC, - ADJ, - ADM, - AJ, - ATB, - ATJ, - ATT, - CMP, DEF, + DEM, NAR, RAT, ) from app.scodoc import sco_cache -from app.scodoc import sco_codes_parcours from app.scodoc import sco_formsemestre -from app.scodoc import sco_formsemestre_status from app.scodoc import sco_parcours_dut from app.scodoc import sco_etud @@ -132,24 +123,6 @@ APO_SEP = "\t" APO_NEWLINE = "\r\n" -def code_scodoc_to_apo(code): - """Conversion code jury ScoDoc en code Apogée""" - return { - ATT: "AJAC", - ATB: "AJAC", - ATJ: "AJAC", - ADM: "ADM", - ADJ: "ADM", - ADC: "ADMC", - AJ: "AJ", - CMP: "COMP", - "DEM": "NAR", - DEF: "NAR", - NAR: "NAR", - RAT: "ATT", - }.get(code, "DEF") - - def _apo_fmt_note(note): "Formatte une note pour Apogée (séparateur décimal: ',')" if not note and isinstance(note, float): @@ -449,7 +422,7 @@ class ApoEtud(dict): N=_apo_fmt_note(ue_status["moy"]), B=20, J="", - R=code_scodoc_to_apo(code_decision_ue), + R=ScoDocSiteConfig.get_code_apo(code_decision_ue), M="", ) else: @@ -475,13 +448,9 @@ class ApoEtud(dict): def comp_elt_semestre(self, nt, decision, etudid): """Calcul résultat apo semestre""" # resultat du semestre - decision_apo = code_scodoc_to_apo(decision["code"]) + decision_apo = ScoDocSiteConfig.get_code_apo(decision["code"]) note = nt.get_etud_moy_gen(etudid) - if ( - decision_apo == "DEF" - or decision["code"] == "DEM" - or decision["code"] == DEF - ): + if decision_apo == "DEF" or decision["code"] == DEM or decision["code"] == DEF: note_str = "0,01" # note non nulle pour les démissionnaires else: note_str = _apo_fmt_note(note) @@ -520,21 +489,21 @@ class ApoEtud(dict): # ou jury intermediaire et etudiant non redoublant... return self.comp_elt_semestre(cur_nt, cur_decision, etudid) - decision_apo = code_scodoc_to_apo(cur_decision["code"]) + decision_apo = ScoDocSiteConfig.get_code_apo(cur_decision["code"]) autre_nt = sco_cache.NotesTableCache.get(autre_sem["formsemestre_id"]) autre_decision = autre_nt.get_etud_decision_sem(etudid) if not autre_decision: # pas de decision dans l'autre => pas de résultat annuel return VOID_APO_RES - autre_decision_apo = code_scodoc_to_apo(autre_decision["code"]) + autre_decision_apo = ScoDocSiteConfig.get_code_apo(autre_decision["code"]) if ( autre_decision_apo == "DEF" - or autre_decision["code"] == "DEM" + or autre_decision["code"] == DEM or autre_decision["code"] == DEF ) or ( decision_apo == "DEF" - or cur_decision["code"] == "DEM" + or cur_decision["code"] == DEM or cur_decision["code"] == DEF ): note_str = "0,01" # note non nulle pour les démissionnaires diff --git a/app/scodoc/sco_codes_parcours.py b/app/scodoc/sco_codes_parcours.py index 4ff29bb79..38d3e4fe8 100644 --- a/app/scodoc/sco_codes_parcours.py +++ b/app/scodoc/sco_codes_parcours.py @@ -125,6 +125,7 @@ CMP = "CMP" # utile pour UE seulement (indique UE acquise car semestre acquis) NAR = "NAR" RAT = "RAT" # en attente rattrapage, sera ATT dans Apogée DEF = "DEF" # défaillance (n'est pas un code jury dans scodoc mais un état, comme inscrit ou demission) +DEM = "DEM" # codes actions REDOANNEE = "REDOANNEE" # redouble annee (va en Sn-1) diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index 8248491af..87d50e3f7 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -87,8 +87,10 @@ groupEditor = ndb.EditableTable( group_list = groupEditor.list -def get_group(group_id): +def get_group(group_id: int): """Returns group object, with partition""" + if not isinstance(group_id, int): + raise ValueError("invalid group_id (%s)" % group_id) r = ndb.SimpleDictFetch( """SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.* FROM group_descr gd, partition p @@ -687,6 +689,10 @@ def setGroups( group_id = fs[0].strip() if not group_id: continue + try: + group_id = int(group_id) + except ValueError as exc: + raise ValueError("invalid group_id: not an integer") group = get_group(group_id) # Anciens membres du groupe: old_members = get_group_members(group_id) diff --git a/app/scodoc/sco_portal_apogee.py b/app/scodoc/sco_portal_apogee.py index f78e90034..836be2ed9 100644 --- a/app/scodoc/sco_portal_apogee.py +++ b/app/scodoc/sco_portal_apogee.py @@ -169,7 +169,9 @@ def get_inscrits_etape(code_etape, anneeapogee=None, ntrials=2): if doc: break if not doc: - raise ScoValueError("pas de réponse du portail ! (timeout=%s)" % portal_timeout) + raise ScoValueError( + f"pas de réponse du portail !
(timeout={portal_timeout}, requête: {req})" + ) etuds = _normalize_apo_fields(xml_to_list_of_dicts(doc, req=req)) # Filtre sur annee inscription Apogee: diff --git a/app/templates/configuration.html b/app/templates/configuration.html index b874d48db..f9d060ad8 100644 --- a/app/templates/configuration.html +++ b/app/templates/configuration.html @@ -92,6 +92,8 @@
Les paramètres donnés ici s'appliquent à tout ScoDoc (tous les départements):
{{ render_field(form.bonus_sport_func_name, onChange="submit_form()")}} +

Exports Apogée

+

configuration des codes de décision

Bibliothèque de logos

{% for dept_entry in form.depts.entries %} {% set dept_form = dept_entry.form %} diff --git a/app/views/scodoc.py b/app/views/scodoc.py index a7aedaa88..02bc1fb11 100644 --- a/app/views/scodoc.py +++ b/app/views/scodoc.py @@ -33,49 +33,38 @@ Emmanuel Viennet, 2021 import datetime import io -import wtforms.validators - -from app.auth.models import User -import os - import flask from flask import abort, flash, url_for, redirect, render_template, send_file from flask import request -from flask.app import Flask import flask_login from flask_login.utils import login_required, current_user -from flask_wtf import FlaskForm -from flask_wtf.file import FileField, FileAllowed -from werkzeug.exceptions import BadRequest, NotFound -from wtforms import SelectField, SubmitField, FormField, validators, Form, FieldList -from wtforms.fields import IntegerField -from wtforms.fields.simple import BooleanField, StringField, TextAreaField, HiddenField -from wtforms.validators import ValidationError, DataRequired, Email, EqualTo +from PIL import Image as PILImage + +from werkzeug.exceptions import BadRequest, NotFound + -import app from app import db +from app.auth.models import User from app.forms.main import config_forms from app.forms.main.create_dept import CreateDeptForm +from app.forms.main.config_apo import CodesDecisionsForm +from app import models from app.models import Departement, Identite from app.models import departements from app.models import FormSemestre, FormSemestreInscription -import sco_version -from app.scodoc import sco_logos +from app.models import ScoDocSiteConfig +from app.scodoc import sco_codes_parcours, sco_logos from app.scodoc import sco_find_etud from app.scodoc import sco_utils as scu from app.decorators import ( admin_required, scodoc7func, scodoc, - permission_required_compat_scodoc7, - permission_required, ) from app.scodoc.sco_exceptions import AccessDenied -from app.scodoc.sco_logos import find_logo from app.scodoc.sco_permissions import Permission from app.views import scodoc_bp as bp - -from PIL import Image as PILImage +import sco_version @bp.route("/") @@ -132,6 +121,28 @@ def toggle_dept_vis(dept_id): return redirect(url_for("scodoc.index")) +@bp.route("/ScoDoc/config_codes_decisions", methods=["GET", "POST"]) +@admin_required +def config_codes_decisions(): + """Form config codes decisions""" + form = CodesDecisionsForm() + if request.method == "POST" and form.cancel.data: # cancel button + return redirect(url_for("scodoc.index")) + if form.validate_on_submit(): + for code in models.config.CODES_SCODOC_TO_APO: + ScoDocSiteConfig.set_code_apo(code, getattr(form, code).data) + flash(f"Codes décisions enregistrés.") + return redirect(url_for("scodoc.index")) + elif request.method == "GET": + for code in models.config.CODES_SCODOC_TO_APO: + getattr(form, code).data = ScoDocSiteConfig.get_code_apo(code) + return render_template( + "config_codes_decisions.html", + form=form, + title="Configuration des codes de décisions", + ) + + @bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"]) @login_required def table_etud_in_accessible_depts(): diff --git a/app/views/users.py b/app/views/users.py index cc9b9db01..fe65348cf 100644 --- a/app/views/users.py +++ b/app/views/users.py @@ -81,7 +81,7 @@ _l = _ class ChangePasswordForm(FlaskForm): user_name = HiddenField() old_password = PasswordField(_l("Identifiez-vous")) - new_password = PasswordField(_l("Nouveau mot de passe")) + new_password = PasswordField(_l("Nouveau mot de passe de l'utilisateur")) bis_password = PasswordField( _l("Répéter"), validators=[ diff --git a/scodoc.py b/scodoc.py index 976443a3e..f18f0892b 100755 --- a/scodoc.py +++ b/scodoc.py @@ -300,14 +300,6 @@ def delete_dept(dept): # delete-dept from app.scodoc import notesdb as ndb from app.scodoc import sco_dept - if False: - click.confirm( - f"""Attention: Cela va effacer toutes les données du département {dept} - (étudiants, notes, formations, etc) - Voulez-vous vraiment continuer ? - """, - abort=True, - ) db.reflect() ndb.open_db_connection() d = models.Departement.query.filter_by(acronym=dept).first() From 51933d057b0c88d5bdcfd2c5aff3a94c3accafcb Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 21 Jan 2022 10:27:47 +0100 Subject: [PATCH 13/21] Morceaux manquants. --- app/forms/main/config_apo.py | 76 +++++++++ app/models/config.py | 178 ++++++++++++++++++++++ app/templates/config_codes_decisions.html | 23 +++ sco_version.py | 2 +- 4 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 app/forms/main/config_apo.py create mode 100644 app/models/config.py create mode 100644 app/templates/config_codes_decisions.html diff --git a/app/forms/main/config_apo.py b/app/forms/main/config_apo.py new file mode 100644 index 000000000..43ed62824 --- /dev/null +++ b/app/forms/main/config_apo.py @@ -0,0 +1,76 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# ScoDoc +# +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +""" +Formulaires configuration Exports Apogée (codes) +""" +import re + +from flask import flash, url_for, redirect, render_template +from flask_wtf import FlaskForm +from wtforms import SubmitField, validators +from wtforms.fields.simple import StringField + +from app import models +from app.models import ScoDocSiteConfig +from app.models import SHORT_STR_LEN + +from app.scodoc import sco_utils as scu + + +def _build_code_field(code): + return StringField( + label=code, + validators=[ + validators.regexp( + r"^[A-Z0-9_]*$", + message="Ne doit comporter que majuscules et des chiffres", + ), + validators.Length( + max=SHORT_STR_LEN, + message=f"L'acronyme ne doit pas dépasser {SHORT_STR_LEN} caractères", + ), + validators.DataRequired("code requis"), + ], + ) + + +class CodesDecisionsForm(FlaskForm): + ADC = _build_code_field("ADC") + ADJ = _build_code_field("ADJ") + ADM = _build_code_field("ADM") + AJ = _build_code_field("AJ") + ATB = _build_code_field("ATB") + ATJ = _build_code_field("ATJ") + ATT = _build_code_field("ATT") + CMP = _build_code_field("CMP") + DEF = _build_code_field("DEF") + DEM = _build_code_field("DEF") + NAR = _build_code_field("NAR") + RAT = _build_code_field("RAT") + submit = SubmitField("Valider") + cancel = SubmitField("Annuler", render_kw={"formnovalidate": True}) diff --git a/app/models/config.py b/app/models/config.py new file mode 100644 index 000000000..af04ee51e --- /dev/null +++ b/app/models/config.py @@ -0,0 +1,178 @@ +# -*- coding: UTF-8 -* + +"""Model : site config WORK IN PROGRESS #WIP +""" + +from app import db, log +from app.scodoc import bonus_sport +from app.scodoc.sco_exceptions import ScoValueError +import functools + +from app.scodoc.sco_codes_parcours import ( + ADC, + ADJ, + ADM, + AJ, + ATB, + ATJ, + ATT, + CMP, + DEF, + DEM, + NAR, + RAT, +) + +CODES_SCODOC_TO_APO = { + ADC: "ADMC", + ADJ: "ADM", + ADM: "ADM", + AJ: "AJ", + ATB: "AJAC", + ATJ: "AJAC", + ATT: "AJAC", + CMP: "COMP", + DEF: "NAR", + DEM: "NAR", + NAR: "NAR", + RAT: "ATT", +} + + +def code_scodoc_to_apo_default(code): + """Conversion code jury ScoDoc en code Apogée + (codes par défaut, c'est configurable via ScoDocSiteConfig.get_code_apo) + """ + return CODES_SCODOC_TO_APO.get(code, "DEF") + + +class ScoDocSiteConfig(db.Model): + """Config. d'un site + Nouveau en ScoDoc 9: va regrouper les paramètres qui dans les versions + antérieures étaient dans scodoc_config.py + """ + + __tablename__ = "scodoc_site_config" + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(128), nullable=False, index=True) + value = db.Column(db.Text()) + + BONUS_SPORT = "bonus_sport_func_name" + NAMES = { + BONUS_SPORT: str, + "always_require_ine": bool, + "SCOLAR_FONT": str, + "SCOLAR_FONT_SIZE": str, + "SCOLAR_FONT_SIZE_FOOT": str, + "INSTITUTION_NAME": str, + "INSTITUTION_ADDRESS": str, + "INSTITUTION_CITY": str, + "DEFAULT_PDF_FOOTER_TEMPLATE": str, + } + + def __init__(self, name, value): + self.name = name + self.value = value + + def __repr__(self): + return f"<{self.__class__.__name__}('{self.name}', '{self.value}')>" + + @classmethod + def get_dict(cls) -> dict: + "Returns all data as a dict name = value" + return { + c.name: cls.NAMES.get(c.name, lambda x: x)(c.value) + for c in ScoDocSiteConfig.query.all() + } + + @classmethod + def set_bonus_sport_func(cls, func_name): + """Record bonus_sport config. + If func_name not defined, raise NameError + """ + if func_name not in cls.get_bonus_sport_func_names(): + raise NameError("invalid function name for bonus_sport") + c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first() + if c: + log("setting to " + func_name) + c.value = func_name + else: + c = ScoDocSiteConfig(cls.BONUS_SPORT, func_name) + db.session.add(c) + db.session.commit() + + @classmethod + def get_bonus_sport_func_name(cls): + """Get configured bonus function name, or None if None.""" + f = cls.get_bonus_sport_func_from_name() + if f is None: + return "" + else: + return f.__name__ + + @classmethod + def get_bonus_sport_func(cls): + """Get configured bonus function, or None if None.""" + return cls.get_bonus_sport_func_from_name() + + @classmethod + def get_bonus_sport_func_from_name(cls, func_name=None): + """returns bonus func with specified name. + If name not specified, return the configured function. + None if no bonus function configured. + Raises ScoValueError if func_name not found in module bonus_sport. + """ + if func_name is None: + c = ScoDocSiteConfig.query.filter_by(name=cls.BONUS_SPORT).first() + if c is None: + return None + func_name = c.value + if func_name == "": # pas de bonus défini + return None + try: + return getattr(bonus_sport, func_name) + except AttributeError: + raise ScoValueError( + f"""Fonction de calcul maison inexistante: {func_name}. + (contacter votre administrateur local).""" + ) + + @classmethod + def get_bonus_sport_func_names(cls): + """List available functions names + (starting with empty string to represent "no bonus function"). + """ + return [""] + sorted( + [ + getattr(bonus_sport, name).__name__ + for name in dir(bonus_sport) + if name.startswith("bonus_") + ] + ) + + @classmethod + def get_code_apo(cls, code: str) -> str: + """La représentation d'un code pour les exports Apogée. + Par exemple, à l'iUT du H., le code ADM est réprésenté par VAL + Les codes par défaut sont donnés dans sco_apogee_csv. + + """ + cfg = ScoDocSiteConfig.query.filter_by(name=code).first() + if not cfg: + code_apo = code_scodoc_to_apo_default(code) + else: + code_apo = cfg.value + return code_apo + + @classmethod + def set_code_apo(cls, code: str, code_apo: str): + """Enregistre nouvelle représentation du code""" + if code_apo != cls.get_code_apo(code): + cfg = ScoDocSiteConfig.query.filter_by(name=code).first() + if cfg is None: + cfg = ScoDocSiteConfig(code, code_apo) + else: + cfg.value = code_apo + db.session.add(cfg) + db.session.commit() diff --git a/app/templates/config_codes_decisions.html b/app/templates/config_codes_decisions.html new file mode 100644 index 000000000..0c2f32b24 --- /dev/null +++ b/app/templates/config_codes_decisions.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% import 'bootstrap/wtf.html' as wtf %} + +{% block app_content %} +

Configuration des codes de décision exportés vers Apogée

+ + +
+

Ces codes (ADM, AJ, ...) sont utilisés pour représenter les décisions de jury +et les validations de semestres ou d'UE. les valeurs indiquées ici sont utilisées +dans les exports Apogée. +

+

Ne les modifier que si vous savez ce que vous faites ! +

+
+
+
+ {{ wtf.quick_form(form) }} +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/sco_version.py b/sco_version.py index 36fffe4e4..39b1b3401 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.1.29" +SCOVERSION = "9.1.30" SCONAME = "ScoDoc" From d64ecdffcba503e33afa505580d6e1af40f9c45e Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 21 Jan 2022 18:09:15 +0100 Subject: [PATCH 14/21] =?UTF-8?q?Fix:=20acc=C3=A8s=20aux=20groupes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scodoc/sco_groups.py | 2 -- sco_version.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index 87d50e3f7..b06a8e469 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -89,8 +89,6 @@ group_list = groupEditor.list def get_group(group_id: int): """Returns group object, with partition""" - if not isinstance(group_id, int): - raise ValueError("invalid group_id (%s)" % group_id) r = ndb.SimpleDictFetch( """SELECT gd.id AS group_id, gd.*, p.id AS partition_id, p.* FROM group_descr gd, partition p diff --git a/sco_version.py b/sco_version.py index 39b1b3401..3c171d1f5 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.1.30" +SCOVERSION = "9.1.31" SCONAME = "ScoDoc" From 4993dc4df3bff05c2038b270a6c2e4f2a0e29d98 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 21 Jan 2022 18:46:00 +0100 Subject: [PATCH 15/21] setGroups: ignore groupes invalides --- app/scodoc/sco_groups.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index b06a8e469..e14ae5266 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -690,7 +690,8 @@ def setGroups( try: group_id = int(group_id) except ValueError as exc: - raise ValueError("invalid group_id: not an integer") + log("setGroups: ignoring invalid group_id={group_id}") + continue group = get_group(group_id) # Anciens membres du groupe: old_members = get_group_members(group_id) From 7c89b9a8d327a290e50bb8b674f4ffc1f70472ca Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 21 Jan 2022 22:03:16 +0100 Subject: [PATCH 16/21] Message d'erreur si upload notes xls avec etudid invalide --- app/scodoc/sco_saisie_notes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index d0a5407dd..9ccbd9c8d 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -153,7 +153,10 @@ def _check_notes(notes, evaluation, mod): for (etudid, note) in notes: note = str(note).strip().upper() - etudid = int(etudid) # + try: + etudid = int(etudid) # + except ValueError as exc: + raise ScoValueError(f"Code étudiant ({etudid}) invalide") if note[:3] == "DEM": continue # skip ! if note: From 66a1ba46c33342ed73b05c33924aa2819818c912 Mon Sep 17 00:00:00 2001 From: Jean-Marie PLACE Date: Fri, 21 Jan 2022 23:25:02 +0100 Subject: [PATCH 17/21] convert to RGB (from ARGB) when saving as JPEG --- app/views/scodoc.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/scodoc.py b/app/views/scodoc.py index 02bc1fb11..394dfe833 100644 --- a/app/views/scodoc.py +++ b/app/views/scodoc.py @@ -266,14 +266,16 @@ def _return_logo(name="header", dept_id="", small=False, strict: bool = True): suffix = logo.suffix if small: with PILImage.open(logo.filepath) as im: - im.thumbnail(SMALL_SIZE) - stream = io.BytesIO() # on garde le même format (on pourrait plus simplement générer systématiquement du JPEG) fmt = { # adapt suffix to be compliant with PIL save format "PNG": "PNG", "JPG": "JPEG", "JPEG": "JPEG", }[suffix.upper()] + if fmt == "JPEG": + im = im.convert("RGB") + im.thumbnail(SMALL_SIZE) + stream = io.BytesIO() im.save(stream, fmt) stream.seek(0) return send_file(stream, mimetype=f"image/{fmt}") From 4df39361fbc9b7fae6d12adbe8fdafe12051bf98 Mon Sep 17 00:00:00 2001 From: Jean-Marie PLACE Date: Sat, 22 Jan 2022 00:14:39 +0100 Subject: [PATCH 18/21] now support several departement for a nip when load formsemestre_bulletinetud --- app/views/notes.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/notes.py b/app/views/notes.py index 35c9ca837..8b0509666 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -290,9 +290,12 @@ def formsemestre_bulletinetud( if etudid: etud = models.Identite.query.get_or_404(etudid) elif code_nip: - etud = models.Identite.query.filter_by( - code_nip=str(code_nip) - ).first_or_404() + dept = formsemestre.dept_id + etud = ( + models.Identite.query.filter_by(code_nip=str(code_nip)) + .filter_by(dept_id=dept) + .first_or_404() + ) elif code_ine: etud = models.Identite.query.filter_by( code_ine=str(code_ine) From 53ae043ffa60f044d7d86eeedbefb53b114efa58 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 22 Jan 2022 11:34:57 +0100 Subject: [PATCH 19/21] Fix: sanitize_old_formation --- app/models/formations.py | 4 ++-- sco_version.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/formations.py b/app/models/formations.py index e2273c3b6..b69d566a6 100644 --- a/app/models/formations.py +++ b/app/models/formations.py @@ -1,6 +1,7 @@ """ScoDoc 9 models : Formations """ +import app from app import db from app.comp import df_cache from app.models import SHORT_STR_LEN @@ -141,8 +142,7 @@ class Formation(db.Model): db.session.add(ue) db.session.commit() - if change: - self.invalidate_module_coefs() + app.clear_scodoc_cache() class Matiere(db.Model): diff --git a/sco_version.py b/sco_version.py index 3c171d1f5..55a2c7850 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.1.31" +SCOVERSION = "9.1.32" SCONAME = "ScoDoc" From 3e20bd8198917edc67d7d24a0660df941b129403 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 22 Jan 2022 12:15:03 +0100 Subject: [PATCH 20/21] Explication des codes jury --- app/forms/main/config_apo.py | 2 ++ app/scodoc/sco_codes_parcours.py | 14 +++++++++----- app/scodoc/sco_formsemestre_validation.py | 2 +- app/scodoc/sco_pvjury.py | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/forms/main/config_apo.py b/app/forms/main/config_apo.py index 43ed62824..a655f450f 100644 --- a/app/forms/main/config_apo.py +++ b/app/forms/main/config_apo.py @@ -39,12 +39,14 @@ from app import models from app.models import ScoDocSiteConfig from app.models import SHORT_STR_LEN +from app.scodoc import sco_codes_parcours from app.scodoc import sco_utils as scu def _build_code_field(code): return StringField( label=code, + description=sco_codes_parcours.CODES_EXPL[code], validators=[ validators.regexp( r"^[A-Z0-9_]*$", diff --git a/app/scodoc/sco_codes_parcours.py b/app/scodoc/sco_codes_parcours.py index 38d3e4fe8..6bcb8cc32 100644 --- a/app/scodoc/sco_codes_parcours.py +++ b/app/scodoc/sco_codes_parcours.py @@ -141,22 +141,26 @@ BUG = "BUG" ALL = "ALL" +# Explication des codes (de demestre ou d'UE) CODES_EXPL = { - ADM: "Validé", ADC: "Validé par compensation", ADJ: "Validé par le Jury", - ATT: "Décision en attente d'un autre semestre (faute d'atteindre la moyenne)", + ADM: "Validé", + AJ: "Ajourné", ATB: "Décision en attente d'un autre semestre (au moins une UE sous la barre)", ATJ: "Décision en attente d'un autre semestre (assiduité insuffisante)", - AJ: "Ajourné", - NAR: "Echec, non autorisé à redoubler", - RAT: "En attente d'un rattrapage", + ATT: "Décision en attente d'un autre semestre (faute d'atteindre la moyenne)", + CMP: "Code UE acquise car semestre acquis", DEF: "Défaillant", + NAR: "Échec, non autorisé à redoubler", + RAT: "En attente d'un rattrapage", } # Nota: ces explications sont personnalisables via le fichier # de config locale /opt/scodoc/var/scodoc/config/scodoc_local.py # variable: CONFIG.CODES_EXP +# Les codes de semestres: +CODES_JURY_SEM = {ADC, ADJ, ADM, AJ, ATB, ATJ, ATT, DEF, NAR, RAT} CODES_SEM_VALIDES = {ADM: True, ADC: True, ADJ: True} # semestre validé CODES_SEM_ATTENTES = {ATT: True, ATB: True, ATJ: True} # semestre en attente diff --git a/app/scodoc/sco_formsemestre_validation.py b/app/scodoc/sco_formsemestre_validation.py index 86525c5c8..3f4eb79f8 100644 --- a/app/scodoc/sco_formsemestre_validation.py +++ b/app/scodoc/sco_formsemestre_validation.py @@ -738,7 +738,7 @@ def form_decision_manuelle(Se, formsemestre_id, etudid, desturl="", sortcol=None ) # Choix code semestre: - codes = list(sco_codes_parcours.CODES_EXPL.keys()) + codes = list(sco_codes_parcours.CODES_JURY_SEM) codes.sort() # fortuitement, cet ordre convient bien ! H.append( diff --git a/app/scodoc/sco_pvjury.py b/app/scodoc/sco_pvjury.py index d193b3733..e2f28c695 100644 --- a/app/scodoc/sco_pvjury.py +++ b/app/scodoc/sco_pvjury.py @@ -567,7 +567,7 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True): if "prev_decision" in row and row["prev_decision"]: counts[row["prev_decision"]] += 0 # Légende des codes - codes = list(counts.keys()) # sco_codes_parcours.CODES_EXPL.keys() + codes = list(counts.keys()) codes.sort() H.append("

Explication des codes

") lines = [] From 264ef7e1ff8fcc6ddbf4caf977c4a255bc6015e2 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 22 Jan 2022 17:37:04 +0100 Subject: [PATCH 21/21] formsemestre_bulletinetud avec arg INE: filtre sur dept --- app/views/notes.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/views/notes.py b/app/views/notes.py index 8b0509666..4be64afbb 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -290,16 +290,17 @@ def formsemestre_bulletinetud( if etudid: etud = models.Identite.query.get_or_404(etudid) elif code_nip: - dept = formsemestre.dept_id etud = ( models.Identite.query.filter_by(code_nip=str(code_nip)) - .filter_by(dept_id=dept) + .filter_by(dept_id=formsemestre.dept_id) .first_or_404() ) elif code_ine: - etud = models.Identite.query.filter_by( - code_ine=str(code_ine) - ).first_or_404() + etud = ( + models.Identite.query.filter_by(code_ine=str(code_ine)) + .filter_by(dept_id=formsemestre.dept_id) + .first_or_404() + ) else: raise ScoValueError( "Paramètre manquant: spécifier code_nip ou etudid ou code_ine"