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
),
- """
{%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"