From 44b757f4473d3c9d2025f104ae7a2959f535e529 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 14 Feb 2021 22:38:50 +0100 Subject: [PATCH 01/32] removed some useless fields --- config/postupgrade-db.py | 21 ------------------- .../Bulletins-Orleans/index.php | 3 +-- misc/createtables.sql | 12 +++++++++++ 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/config/postupgrade-db.py b/config/postupgrade-db.py index e423984cdd..1c69351dc1 100755 --- a/config/postupgrade-db.py +++ b/config/postupgrade-db.py @@ -583,27 +583,6 @@ for dept in get_depts(): ], ) - # add etape_apo2 - check_field( - cnx, - "notes_formsemestre", - "etape_apo2", - ["alter table notes_formsemestre add column etape_apo2 text"], - ) - # add etape_apo3 - check_field( - cnx, - "notes_formsemestre", - "etape_apo3", - ["alter table notes_formsemestre add column etape_apo3 text"], - ) - # add etape_apo4 - check_field( - cnx, - "notes_formsemestre", - "etape_apo4", - ["alter table notes_formsemestre add column etape_apo4 text"], - ) # add publish_incomplete check_field( cnx, diff --git a/misc/PublicationBulletins/Bulletins-Orleans/index.php b/misc/PublicationBulletins/Bulletins-Orleans/index.php index 5e3259b142..891f7744d7 100644 --- a/misc/PublicationBulletins/Bulletins-Orleans/index.php +++ b/misc/PublicationBulletins/Bulletins-Orleans/index.php @@ -99,8 +99,7 @@ function get_semestre_info($sem, $dept) { // Renvoi les informations détaillées d'un semestre // Ne nécessite pas d'authentification avec sco_user et sco_pw - Il est possible de choisir le format XML ou JSON. // formsemestre_list -// Paramètres (tous optionnels): formsesmestre_id, formation_id, etape_apo, etape_apo2 -// Coquille dans la doc : formsesmestre_id +// Paramètres (tous optionnels): formsemestre_id, formation_id, etape_apo // Résultat: liste des semestres correspondant. // Exemple: formsemestre_list?format=xml&etape_apo=V1RT global $sco_pw; diff --git a/misc/createtables.sql b/misc/createtables.sql index bc937d0d27..c42e50fd3b 100644 --- a/misc/createtables.sql +++ b/misc/createtables.sql @@ -35,6 +35,18 @@ CREATE FUNCTION notes_newid_etud( text ) returns text as ' as result; ' language SQL; +-- Fonction pour anonymisation: +-- inspirée par https://www.simononsoftware.com/random-string-in-postgresql/ +CREATE FUNCTION random_text_md5( integer ) returns text + LANGUAGE SQL + AS $$ + select upper( substring( (SELECT string_agg(md5(random()::TEXT), '') + FROM generate_series( + 1, + CEIL($1 / 32.)::integer) + ), 1, $1) ); + $$; + -- Preferences CREATE TABLE sco_prefs ( pref_id text DEFAULT notes_newid('PREF'::text) UNIQUE NOT NULL, From 203c8696382bd95b44536929e27fce524b11dcfe Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 15 Feb 2021 15:08:53 +0100 Subject: [PATCH 02/32] ensure INSTANCE_HOME is correct in all use cases --- config/scodocutils.py | 7 +++++++ 1 file changed, 7 insertions(+) mode change 100644 => 100755 config/scodocutils.py diff --git a/config/scodocutils.py b/config/scodocutils.py old mode 100644 new mode 100755 index 98a5be9594..51d6c71d6a --- a/config/scodocutils.py +++ b/config/scodocutils.py @@ -9,6 +9,12 @@ import sys, os, psycopg2, glob, subprocess, traceback, time sys.path.append("..") +# INSTANCE_HOME est nécessaire pour sco_utils.py +# note: avec le python 2.7 de Zope2, l'import de pyscopg2 change +# INSTANCE_HOME dans l'environnement ! +# Ici on le fixe à la "bonne" valeur pour ScoDoc7. +os.environ["INSTANCE_HOME"] = "/opt/scodoc" + def log(msg): sys.stdout.flush() @@ -26,6 +32,7 @@ if not SCODOC_VAR_DIR: sys.exit(1) SCODOC_LOGOS_DIR = os.environ.get("SCODOC_LOGOS_DIR", "") + def get_dept_cnx_str(dept): "db cnx string for dept" f = os.path.join(SCODOC_VAR_DIR, "config", "depts", dept + ".cfg") From 62d637cd8bebdf2a4deaa82924d068f7851e3d30 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 15 Feb 2021 15:15:56 +0100 Subject: [PATCH 03/32] disable spell checking on some input fields --- html_sidebar.py | 2 +- sco_dept.py | 2 +- sco_find_etud.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/html_sidebar.py b/html_sidebar.py index e9e022fcb9..1216eae319 100644 --- a/html_sidebar.py +++ b/html_sidebar.py @@ -89,7 +89,7 @@ def sidebar(context, REQUEST=None): H.append( """
Chercher étudiant:
-
+
""" diff --git a/sco_dept.py b/sco_dept.py index 7e7db01822..7564ab2a5a 100644 --- a/sco_dept.py +++ b/sco_dept.py @@ -130,7 +130,7 @@ def index_html(context, REQUEST=None, showcodes=0, showsemtable=0): H.append( """

-Chercher étape courante: +Chercher étape courante:
""" diff --git a/sco_find_etud.py b/sco_find_etud.py index 0af146e217..52b5800b9c 100644 --- a/sco_find_etud.py +++ b/sco_find_etud.py @@ -58,7 +58,7 @@ def form_search_etud( H.append( """
%s - +
(entrer une partie du nom) """ @@ -294,7 +294,7 @@ def form_search_etud_in_accessible_depts(context, REQUEST): return "" return """ Chercher étudiant: - +
(entrer une partie du nom ou le code NIP, cherche dans tous les départements autorisés) """ From 2c8f8a125ad16b6c75bd9ebc9760534a75fb88c3 Mon Sep 17 00:00:00 2001 From: IDK Date: Mon, 15 Feb 2021 15:21:51 +0100 Subject: [PATCH 04/32] fixed file mode --- config/scodocutils.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 config/scodocutils.py diff --git a/config/scodocutils.py b/config/scodocutils.py old mode 100755 new mode 100644 From 276a21ec49573500a435a4507d314ba167019de8 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 16 Feb 2021 15:16:01 +0100 Subject: [PATCH 05/32] test billets absences --- scotests/test_basic.py | 6 ++++- scotests/test_bonusmalus.py | 6 +++-- scotests/test_capitalisation.py | 6 +++-- scotests/test_demissions.py | 44 +++++++++++++++++++++++++++++++-- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/scotests/test_basic.py b/scotests/test_basic.py index c98cb32176..d16941e3fd 100644 --- a/scotests/test_basic.py +++ b/scotests/test_basic.py @@ -11,12 +11,16 @@ Utiliser comme: """ import random +# La variable context est définie par le script de lancement +# l'affecte ainsi pour évietr les warnins pylint: +context = context # pylint: disable=undefined-variable +REQUEST = REQUEST # pylint: disable=undefined-variable import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error import sco_utils import sco_abs import sco_abs_views -G = sco_fake_gen.ScoFake(context.Notes) # pylint: disable=undefined-variable +G = sco_fake_gen.ScoFake(context.Notes) G.verbose = False # --- Création d'étudiants diff --git a/scotests/test_bonusmalus.py b/scotests/test_bonusmalus.py index c2a629d0df..22588f9434 100644 --- a/scotests/test_bonusmalus.py +++ b/scotests/test_bonusmalus.py @@ -11,8 +11,10 @@ Utiliser comme: """ - -import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error +# La variable context est définie par le script de lancement +# l'affecte ainsi pour évietr les warnins pylint: +context = context # pylint: disable=undefined-variable +import scotests.sco_fake_gen as sco_fake_gen import sco_utils as scu import sco_moduleimpl diff --git a/scotests/test_capitalisation.py b/scotests/test_capitalisation.py index cd2a2280f2..2bb88fbd61 100644 --- a/scotests/test_capitalisation.py +++ b/scotests/test_capitalisation.py @@ -10,13 +10,15 @@ Utiliser comme: scotests/scointeractive.sh -r TEST00 scotests/test_capitalisation.py """ - +# La variable context est définie par le script de lancement +# l'affecte ainsi pour évietr les warnins pylint: +context = context # pylint: disable=undefined-variable import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error import sco_utils import sco_codes_parcours import sco_modalites -G = sco_fake_gen.ScoFake(context.Notes) # pylint: disable=undefined-variable +G = sco_fake_gen.ScoFake(context.Notes) G.verbose = False # --- Création d'étudiants diff --git a/scotests/test_demissions.py b/scotests/test_demissions.py index 6028a05df6..41f681e2a2 100644 --- a/scotests/test_demissions.py +++ b/scotests/test_demissions.py @@ -12,16 +12,24 @@ Utiliser comme: scotests/scointeractive.sh -r TEST00 scotests/test_demissions.py """ +import datetime +import re +import json +# La variable context est définie par le script de lancement +# l'affecte ainsi pour évietr les warnins pylint: +context = context # pylint: disable=undefined-variable +REQUEST = REQUEST # pylint: disable=undefined-variable import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error import sco_utils import sco_bulletins -G = sco_fake_gen.ScoFake(context.Notes) # pylint: disable=undefined-variable +G = sco_fake_gen.ScoFake(context.Notes) G.verbose = False +nb_etuds = 10 # --- Création d'étudiants -etuds = [G.create_etud(code_nip=None) for _ in range(2)] +etuds = [G.create_etud(code_nip=None) for _ in range(nb_etuds)] # --- Mise en place formation form, ue_list, mod_list = G.setup_formation( nb_semestre=1, titre="Essai 1", acronyme="ESS01" @@ -59,3 +67,35 @@ print(bul["moy_gen"]) assert bul["moy_gen"] == "NA" assert bul["ins"][0]["etat"] == "D" + +# ------------ Billets d'absences +etud = etuds[1] # non demissionnaire +d = sem["date_debut_iso"] +d_beg = datetime.datetime(*[int(x) for x in d.split("-")]) +d_end = d_beg + datetime.timedelta(2) +description = "billet test 0" +x = context.Absences.AddBilletAbsence( + d_beg.isoformat(), + d_end.isoformat(), + description=description, + etudid=etud["etudid"], + REQUEST=REQUEST, +) +# +billet_id = re.search(r"billet_id value=\"([A-Z0-9]+)\"", x).group(1) +context.Absences.deleteBilletAbsence(billet_id, REQUEST=REQUEST, dialog_confirmed=True) +j = context.Absences.listeBilletsEtud( + etudid=etud["etudid"], REQUEST=REQUEST, format="json" +) +assert len(json.loads(j)) == 0 +x = context.Absences.AddBilletAbsence( + d_beg.isoformat(), + d_end.isoformat(), + description=description, + etudid=etud["etudid"], + REQUEST=REQUEST, +) +j = context.Absences.listeBilletsEtud( + etudid=etud["etudid"], REQUEST=REQUEST, format="json" +) +assert json.loads(j)[0]["description"] == description From 626d70e72ce3537bf04c184029a7f74497b42899 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 16 Feb 2021 15:16:57 +0100 Subject: [PATCH 06/32] pylint --- sco_archives.py | 4 ++-- sco_recapcomplet.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sco_archives.py b/sco_archives.py index 987797a032..18d89bfe85 100644 --- a/sco_archives.py +++ b/sco_archives.py @@ -325,7 +325,7 @@ def do_formsemestre_archive( if data: PVArchive.store(archive_id, "Decisions_Jury.xls", data) # Classeur bulletins (PDF) - data, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( + data, _ = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( context, formsemestre_id, REQUEST, version=bulVersion ) if data: @@ -548,7 +548,7 @@ def formsemestre_delete_archive( raise AccessDenied( "opération non autorisée pour %s" % str(REQUEST.AUTHENTICATED_USER) ) - sem = sco_formsemestre.get_formsemestre( + _ = sco_formsemestre.get_formsemestre( context, formsemestre_id ) # check formsemestre_id archive_id = PVArchive.get_id_from_name(context, formsemestre_id, archive_name) diff --git a/sco_recapcomplet.py b/sco_recapcomplet.py index 6fb23f7439..1edfb54ce1 100644 --- a/sco_recapcomplet.py +++ b/sco_recapcomplet.py @@ -416,7 +416,7 @@ def make_formsemestre_recapcomplet( l.append(fmtnum(scu.fmt_note(t[0], keep_numeric=keep_numeric))) # moy_gen # Ajoute rangs dans groupes seulement si CSV ou XLS if format[:3] == "xls" or format == "csv": - rang_gr, ninscrits_gr, gr_name = sco_bulletins.get_etud_rangs_groups( + rang_gr, _, gr_name = sco_bulletins.get_etud_rangs_groups( context, etudid, formsemestre_id, partitions, partitions_etud_groups, nt ) From 20ec08490b4d6839820f86becabea9657242ec3d Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 16 Feb 2021 15:17:47 +0100 Subject: [PATCH 07/32] upgrade old zope dateutils --- config/upgrade.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/upgrade.sh b/config/upgrade.sh index 894fc07859..67a5008489 100755 --- a/config/upgrade.sh +++ b/config/upgrade.sh @@ -119,6 +119,8 @@ then /opt/zope213/bin/pip install requests fi +/opt/zope213/bin/pip install --upgrade python-dateutil + # Ensure www-data can duplicate databases (for dumps) su -c $'psql -c \'alter role "www-data" with CREATEDB;\'' "$POSTGRES_SUPERUSER" #' From 5aacc2db63c4ceb5cbebc04cfd8ce6d7892eaf2e Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 16 Feb 2021 15:18:06 +0100 Subject: [PATCH 08/32] Fix: civilite --- pe_jurype.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pe_jurype.py b/pe_jurype.py index 0b0fad771a..62863b4819 100644 --- a/pe_jurype.py +++ b/pe_jurype.py @@ -813,6 +813,7 @@ class JuryPE: "nom": etudinfo["nom"], "prenom": etudinfo["prenom"], "civilite": etudinfo["civilite"], + "civilite_str": etudinfo["civilite_str"], "age": str(pe_tools.calcul_age(etudinfo["date_naissance"])), "lycee": etudinfo["nomlycee"] + ( From cf83228dcb645d6b27355023a266b41561ebeea2 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 16 Feb 2021 15:22:50 +0100 Subject: [PATCH 09/32] Fix: export recap en CSV --- sco_recapcomplet.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sco_recapcomplet.py b/sco_recapcomplet.py index 1edfb54ce1..90e1832f4a 100644 --- a/sco_recapcomplet.py +++ b/sco_recapcomplet.py @@ -758,7 +758,9 @@ def make_formsemestre_recapcomplet( H.append("") return "\n".join(H), "", "html" elif format == "csv": - CSV = scu.CSV_LINESEP.join([scu.CSV_FIELDSEP.join(str(x)) for x in F]) + CSV = scu.CSV_LINESEP.join( + [scu.CSV_FIELDSEP.join([str(x) for x in l]) for l in F] + ) semname = sem["titre_num"].replace(" ", "_") date = time.strftime("%d-%m-%Y") filename = "notes_modules-%s-%s.csv" % (semname, date) From 012bb0292a33c95495d1eddcb46f564bf392eb7d Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 16 Feb 2021 19:45:40 +0100 Subject: [PATCH 10/32] Ne met pas en attente les evals immediates sans aucune notes. --- sco_evaluations.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/sco_evaluations.py b/sco_evaluations.py index 5e5b17e4f9..21595fe0d6 100644 --- a/sco_evaluations.py +++ b/sco_evaluations.py @@ -133,11 +133,6 @@ def do_evaluation_etat( à ce module ont des notes) evalattente est vrai s'il ne manque que des notes en attente """ - # global _DEE_TOT - # t0=time.time() - # if evaluation_id == 'GEAEVAL82883': - # log('do_evaluation_etat: evaluation_id=%s partition_id=%s sfp=%s' % (evaluation_id, partition_id, select_first_partition)) - nb_inscrits = len( sco_groups.do_evaluation_listeetuds_groups( context, evaluation_id, getallstudents=True @@ -238,7 +233,7 @@ def do_evaluation_etat( else: complete = True if ( - TotalNbMissing > 0 + TotalNbAtt > 0 and (TotalNbMissing == TotalNbAtt or E["publish_incomplete"] != "0") and not is_malus ): From 96b22ac5ca8b8fa47752c7d08f2beb9766b713cc Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 16 Feb 2021 19:46:34 +0100 Subject: [PATCH 11/32] =?UTF-8?q?Bul.=20PDF:=20meilleure=20affichage=20lig?= =?UTF-8?q?ne=20moyenne=20g=C3=A9n=C3=A9rale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sco_bulletins_standard.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sco_bulletins_standard.py b/sco_bulletins_standard.py index eb937a2fda..d0ac4485bb 100644 --- a/sco_bulletins_standard.py +++ b/sco_bulletins_standard.py @@ -378,7 +378,8 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator): P[-1]["_pdf_style"].append( ("LINEBELOW", (0, 0), (-1, 0), self.PDF_LINEWIDTH, self.PDF_LINECOLOR) ) - + # Espacement sous la ligne moyenne générale: + P[-1]["_pdf_style"].append(("BOTTOMPADDING", (0, 1), (-1, 1), 8)) # Moyenne générale: nbabs = I["nbabs"] nbabsjust = I["nbabsjust"] @@ -392,8 +393,10 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator): "abs": "%s / %s" % (nbabs, nbabsjust), "_css_row_class": "notes_bulletin_row_gen", "_titre_colspan": 2, - "_pdf_row_markup": ['font size="12"', "b"], # bold, size 12 - "_pdf_style": [("LINEABOVE", (0, 1), (-1, 1), 1, self.PDF_LINECOLOR)], + "_pdf_row_markup": ["b"], # bold. On peut ajouter 'font size="12"' + "_pdf_style": [ + ("LINEABOVE", (0, 1), (-1, 1), 1, self.PDF_LINECOLOR), + ], } P.append(t) From 5ffeae3e1fab446e71a87a4650e0eeb2d5bc4c5a Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 16 Feb 2021 22:07:56 +0100 Subject: [PATCH 12/32] Fix: autocomplete recherche etudiant --- sco_find_etud.py | 12 ++++--- static/js/scodoc.js | 87 +++++++++++++++++++++++---------------------- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/sco_find_etud.py b/sco_find_etud.py index 52b5800b9c..27ccf8370f 100644 --- a/sco_find_etud.py +++ b/sco_find_etud.py @@ -108,7 +108,7 @@ def search_etud_in_dept( REQUEST=None, ): """Page recherche d'un etudiant - expnom est un regexp sur le nom ou un code_nip + expnom est un regexp sur le nom ou un code_nip ou un etudid dest_url est la page sur laquelle on sera redirigé après choix parameters spécifie des arguments additionnels à passer à l'URL (en plus de etudid) """ @@ -134,8 +134,10 @@ def search_etud_in_dept( etuds = search_etuds_infos(context, code_nip=expnom, REQUEST=REQUEST) elif expnom: etuds = search_etuds_infos(context, expnom=expnom, REQUEST=REQUEST) - else: - etuds = [] + if expnom and not etuds: + etuds = context.getEtudInfo(filled=1, etudid=expnom, REQUEST=REQUEST) + if len(etuds) != 1: + etuds = [] if len(etuds) == 1: # va directement a la destination return REQUEST.RESPONSE.redirect( @@ -268,14 +270,14 @@ def search_etud_by_name(context, term, REQUEST=None): else: r = ndb.SimpleDictFetch( context, - "SELECT nom, prenom FROM identite WHERE nom LIKE %(beginning)s ORDER BY nom", + "SELECT etudid, nom, prenom FROM identite WHERE nom LIKE %(beginning)s ORDER BY nom", {"beginning": term + "%"}, ) data = [ { "label": "%s %s" % (x["nom"], scolars.format_prenom(x["prenom"])), - "value": x["nom"], + "value": x["etudid"], } for x in r ] diff --git a/static/js/scodoc.js b/static/js/scodoc.js index 7ae653eee0..c4def3d6de 100644 --- a/static/js/scodoc.js +++ b/static/js/scodoc.js @@ -1,7 +1,7 @@ // JS for all ScoDoc pages (using jQuery UI) -$(function() { +$(function () { // Autocomplete recherche etudiants par nom $("#in-expnom").autocomplete( { @@ -9,49 +9,50 @@ $(function() { minLength: 2, // min nb of chars before suggest position: { collision: 'flip' }, // automatic menu position up/down source: "search_etud_by_name", - select: function(event, ui) { - $("#form-chercheetud").submit(); + select: function (event, ui) { + $("#in-expnom").val(ui.item.value); + $("#form-chercheetud").submit(); } - }); - + }); + // Date picker $(".datepicker").datepicker({ - showOn: 'button', - buttonImage: '/ScoDoc/static/icons/calendar_img.png', + showOn: 'button', + buttonImage: '/ScoDoc/static/icons/calendar_img.png', buttonImageOnly: true, - dateFormat: 'dd/mm/yy', - duration : 'fast', + dateFormat: 'dd/mm/yy', + duration: 'fast', }); - $('.datepicker').datepicker('option', $.extend({showMonthAfterYear: false}, - $.datepicker.regional['fr'])); + $('.datepicker').datepicker('option', $.extend({ showMonthAfterYear: false }, + $.datepicker.regional['fr'])); /* Barre menu */ - var sco_menu_position = {my: "left top", at: "left bottom"}; + var sco_menu_position = { my: "left top", at: "left bottom" }; $("#sco_menu").menu({ position: sco_menu_position, - blur: function() { + blur: function () { $(this).menu("option", "position", sco_menu_position); }, - focus: function(e, ui) { + focus: function (e, ui) { if ($("#sco_menu").get(0) !== $(ui).get(0).item.parent().get(0)) { - $(this).menu("option", "position", {my: "left top", at: "right top"}); + $(this).menu("option", "position", { my: "left top", at: "right top" }); } } - }).mouseleave(function(x, y) { - $( "#sco_menu" ).menu('collapseAll'); - }); + }).mouseleave(function (x, y) { + $("#sco_menu").menu('collapseAll'); + }); $("#sco_menu > li > a > span").switchClass("ui-icon-carat-1-e", "ui-icon-carat-1-s"); /* Les menus isoles dropdown */ $(".sco_dropdown_menu").menu({ position: sco_menu_position - }).mouseleave(function(x, y) { - $( ".sco_dropdown_menu" ).menu('collapseAll'); + }).mouseleave(function (x, y) { + $(".sco_dropdown_menu").menu('collapseAll'); } - ); + ); $(".sco_dropdown_menu > li > a > span").switchClass("ui-icon-carat-1-e", "ui-icon-carat-1-s"); - + }); @@ -65,14 +66,14 @@ function sco_message(msg, color) { $('#sco_msg').css('color', color); } setTimeout( - function() { + function () { $('#sco_msg').fadeOut( 'slow', - function() { + function () { $('#sco_msg').html(''); } - ); - }, + ); + }, 2000 // <-- duree affichage en milliseconds ); } @@ -81,30 +82,30 @@ function sco_message(msg, color) { function get_query_args() { var s = window.location.search; // eg "?x=1&y=2" var vars = {}; - s.replace( - /[?&]+([^=&]+)=?([^&]*)?/gi, // regexp - function( m, key, value ) { // callback - vars[key] = value !== undefined ? value : ''; - } + s.replace( + /[?&]+([^=&]+)=?([^&]*)?/gi, // regexp + function (m, key, value) { // callback + vars[key] = value !== undefined ? value : ''; + } ); return vars; } // Tables (gen_tables) -$(function() { - $('table.gt_table').DataTable( { - "paging" : false, - "searching" : false, - "info" : false, +$(function () { + $('table.gt_table').DataTable({ + "paging": false, + "searching": false, + "info": false, /* "autoWidth" : false, */ - "fixedHeader" : { + "fixedHeader": { "header": true, "footer": true }, "orderCellsTop": true, // cellules ligne 1 pour tri - "aaSorting": [ ], // Prevent initial sorting - } ); + "aaSorting": [], // Prevent initial sorting + }); }); @@ -112,9 +113,9 @@ $(function() { function readOnlyTags(nodes) { // nodes are textareas, hide them and create a span showing tags for (var i = 0; i < nodes.length; i++) { - var node = $(nodes[i]); - node.hide(); - var tags = nodes[i].value.split(','); - node.after('' + tags.join('') + ''); + var node = $(nodes[i]); + node.hide(); + var tags = nodes[i].value.split(','); + node.after('' + tags.join('') + ''); } } From de429812316fdee9d012cbae71aa23f70dfe4356 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 16 Feb 2021 22:38:08 +0100 Subject: [PATCH 13/32] Fix: saisie absence sur module sans inscrits --- ZAbsences.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ZAbsences.py b/ZAbsences.py index c67857c23f..049875327d 100644 --- a/ZAbsences.py +++ b/ZAbsences.py @@ -718,7 +718,12 @@ class ZAbsences( ) ] ) - etuds = [e for e in etuds if e["etudid"] in mod_inscrits] + etuds_inscrits_module = [e for e in etuds if e["etudid"] in mod_inscrits] + if etuds_inscrits_module: + etuds = etuds_inscrits_module + else: + # Si aucun etudiant n'est inscrit au module choisi... + moduleimpl_id = None nt = self.Notes._getNotesCache().get_NotesTable(self.Notes, formsemestre_id) sem = sco_formsemestre.do_formsemestre_list( self, {"formsemestre_id": formsemestre_id} From 7622cd1a7ee4d410c70000d96bf0ca99739a991b Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 18 Feb 2021 08:07:21 +0100 Subject: [PATCH 14/32] optimized etud search box --- html_sidebar.py | 2 +- sco_find_etud.py | 115 ++++++++++++++++------------------------------- 2 files changed, 39 insertions(+), 78 deletions(-) diff --git a/html_sidebar.py b/html_sidebar.py index 1216eae319..0079b562cf 100644 --- a/html_sidebar.py +++ b/html_sidebar.py @@ -88,7 +88,7 @@ def sidebar(context, REQUEST=None): H.append( """
Chercher étudiant:
- +
diff --git a/sco_find_etud.py b/sco_find_etud.py index 27ccf8370f..7038a16cef 100644 --- a/sco_find_etud.py +++ b/sco_find_etud.py @@ -96,73 +96,52 @@ def form_search_etud( return "\n".join(H) -# was chercheEtud() -def search_etud_in_dept( - context, - expnom=None, - dest_url="ficheEtud", - parameters={}, - parameters_keys="", - add_headers=True, # complete page - title=None, - REQUEST=None, -): - """Page recherche d'un etudiant - expnom est un regexp sur le nom ou un code_nip ou un etudid - dest_url est la page sur laquelle on sera redirigé après choix - parameters spécifie des arguments additionnels à passer à l'URL (en plus de etudid) +def search_etud_in_dept(context, expnom="", REQUEST=None): + """Page recherche d'un etudiant. + + Affiche la fiche de l'étudiant, ou, si la recherche donne plusieurs résultats, la liste des étudianst + correspondants. + Appelée par boite de recherche barre latérale gauche. + + Args: + expnom: string, regexp sur le nom ou un code_nip ou un etudid """ - if type(expnom) == ListType: - expnom = expnom[0] - q = [] - if parameters: - for param in parameters.keys(): - q.append("%s=%s" % (param, parameters[param])) - elif parameters_keys: - for key in parameters_keys.split(","): - v = REQUEST.form.get(key, False) - if v: - q.append("%s=%s" % (key, v)) - query_string = "&".join(q) - - no_side_bar = True - H = [] - if title: - H.append("

%s

" % title) - - if scu.is_valid_code_nip(expnom): - etuds = search_etuds_infos(context, code_nip=expnom, REQUEST=REQUEST) - elif expnom: - etuds = search_etuds_infos(context, expnom=expnom, REQUEST=REQUEST) - if expnom and not etuds: + dest_url = "ficheEtud" + if len(expnom) > 1: etuds = context.getEtudInfo(filled=1, etudid=expnom, REQUEST=REQUEST) if len(etuds) != 1: - etuds = [] + if scu.is_valid_code_nip(expnom): + etuds = search_etuds_infos(context, code_nip=expnom, REQUEST=REQUEST) + else: + etuds = search_etuds_infos(context, expnom=expnom, REQUEST=REQUEST) + else: + etuds = [] # si expnom est trop court, n'affiche rien + if len(etuds) == 1: # va directement a la destination - return REQUEST.RESPONSE.redirect( - dest_url + "?etudid=%s&" % etuds[0]["etudid"] + query_string - ) + return context.ficheEtud(etudid=etuds[0]["etudid"], REQUEST=REQUEST) + H = [ + context.sco_header( + page_title="Recherche d'un étudiant", + no_side_bar=True, + init_qtip=True, + javascripts=["js/etud_info.js"], + REQUEST=REQUEST, + ), + """

%d résultats pour "%s": choisissez un étudiant:

""" + % (len(etuds), expnom), + form_search_etud( + context, + dest_url=dest_url, + REQUEST=REQUEST, + title="Autre recherche", + ), + ] if len(etuds) > 0: # Choix dans la liste des résultats: - H.append( - """

%d résultats pour "%s": choisissez un étudiant:

""" - % (len(etuds), expnom) - ) - H.append( - form_search_etud( - context, - dest_url=dest_url, - parameters=parameters, - parameters_keys=parameters_keys, - REQUEST=REQUEST, - title="Autre recherche", - ) - ) - for e in etuds: - target = dest_url + "?etudid=%s&" % e["etudid"] + query_string + target = dest_url + "?etudid=%s&" % e["etudid"] e["_nomprenom_target"] = target e["inscription_target"] = target e["_nomprenom_td_attrs"] = 'id="%s" class="etudinfo"' % (e["etudid"]) @@ -187,34 +166,16 @@ def search_etud_in_dept( form_search_etud( context, dest_url=dest_url, - parameters=parameters, - parameters_keys=parameters_keys, REQUEST=REQUEST, title="Autre recherche", ) ) - else: H.append('

Aucun résultat pour "%s".

' % expnom) - add_headers = True - no_side_bar = False H.append( """

La recherche porte sur tout ou partie du NOM ou du NIP de l'étudiant

""" ) - if add_headers: - return ( - context.sco_header( - REQUEST, - page_title="Choix d'un étudiant", - init_qtip=True, - javascripts=["js/etud_info.js"], - no_side_bar=no_side_bar, - ) - + "\n".join(H) - + context.sco_footer(REQUEST) - ) - else: - return "\n".join(H) + return "\n".join(H) + context.sco_footer(REQUEST) # Was chercheEtudsInfo() From 8ea64acf07cf73af49f56cfd003d0d00ad56c1c4 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 18 Feb 2021 08:08:29 +0100 Subject: [PATCH 15/32] Fix: filter out dups in sem resps or etapes --- sco_formsemestre.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sco_formsemestre.py b/sco_formsemestre.py index 8b13e91ac2..2a5db9c1ca 100644 --- a/sco_formsemestre.py +++ b/sco_formsemestre.py @@ -312,8 +312,11 @@ def _write_formsemestre_aux(context, sem, fieldname, valuename): """fieldname: 'etapes' ou 'responsables' valuename: 'etape_apo' ou 'responsable_id' """ - if not "etapes" in sem: + if not fieldname in sem: return + # uniquify + values = set([str(x) for x in sem[fieldname]]) + cnx = context.GetDBConnexion(autocommit=False) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) tablename = "notes_formsemestre_" + fieldname @@ -322,7 +325,7 @@ def _write_formsemestre_aux(context, sem, fieldname, valuename): "DELETE from " + tablename + " where formsemestre_id = %(formsemestre_id)s", {"formsemestre_id": sem["formsemestre_id"]}, ) - for item in sem[fieldname]: + for item in values: if item: cursor.execute( "INSERT INTO " @@ -332,7 +335,7 @@ def _write_formsemestre_aux(context, sem, fieldname, valuename): + ") VALUES (%(formsemestre_id)s, %(" + valuename + ")s)", - {"formsemestre_id": sem["formsemestre_id"], valuename: str(item)}, + {"formsemestre_id": sem["formsemestre_id"], valuename: item}, ) except: log("Warning: exception in write_formsemestre_aux !") From 0571e8d8e28031459ecb13ec480e0438eba596a8 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 18 Feb 2021 08:10:21 +0100 Subject: [PATCH 16/32] Fix: choix groupe saisie absences hed-bdo (DS) --- sco_groups_view.py | 8 +++----- sco_moduleimpl_status.py | 5 +++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/sco_groups_view.py b/sco_groups_view.py index 627a7c489c..fa806f089c 100644 --- a/sco_groups_view.py +++ b/sco_groups_view.py @@ -784,9 +784,7 @@ def groups_table( context.Notes, etud, groups_infos.formsemestre_id ) m["parcours"] = Se.get_parcours_descr() - m["codeparcours"], decisions_jury = sco_report.get_codeparcoursetud( - context.Notes, etud - ) + m["codeparcours"], _ = sco_report.get_codeparcoursetud(context.Notes, etud) def dicttakestr(d, keys): r = [] @@ -933,7 +931,7 @@ def form_choix_saisie_semaine(context, groups_infos, REQUEST=None): DateJour = time.strftime("%d/%m/%Y") datelundi = sco_abs.ddmmyyyy(DateJour).prev_monday() - FA = [] # formulaire avec menu saisi hebdo des absences + FA = [] # formulaire avec menu saisie hebdo des absences FA.append('
') FA.append('' % datelundi) FA.append('' % moduleimpl_id) @@ -955,7 +953,7 @@ def export_groups_as_moodle_csv(context, formsemestre_id=None, REQUEST=None): """ if not formsemestre_id: raise ScoValueError("missing parameter: formsemestre_id") - partitions, partitions_etud_groups = sco_groups.get_formsemestre_groups( + _, partitions_etud_groups = sco_groups.get_formsemestre_groups( context, formsemestre_id, with_default=True ) sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) diff --git a/sco_moduleimpl_status.py b/sco_moduleimpl_status.py index 1601652009..62400761eb 100644 --- a/sco_moduleimpl_status.py +++ b/sco_moduleimpl_status.py @@ -259,9 +259,10 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No ScoAbsChange, context ) and sco_formsemestre.sem_est_courant(context, sem): datelundi = sco_abs.ddmmyyyy(time.strftime("%d/%m/%Y")).prev_monday() + group_id = sco_groups.get_default_group(context, formsemestre_id) H.append( - 'Saisie Absences hebdo.' - % (formsemestre_id, moduleimpl_id, datelundi) + 'Saisie Absences hebdo.' + % (formsemestre_id, moduleimpl_id, datelundi, group_id) ) H.append("") From 8711d88352d6a2a857613f7d6f5c2a49d6fdc948 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 18 Feb 2021 08:31:21 +0100 Subject: [PATCH 17/32] speedup dateutil version check --- config/upgrade.sh | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) mode change 100755 => 100644 config/upgrade.sh diff --git a/config/upgrade.sh b/config/upgrade.sh old mode 100755 new mode 100644 index 67a5008489..564b3e359e --- a/config/upgrade.sh +++ b/config/upgrade.sh @@ -14,7 +14,7 @@ cd /opt/scodoc/Products/ScoDoc/config source config.sh source utils.sh -check_uid_root $0 +check_uid_root "$0" if [ -z "$SCODOC_UPGRADE_RUNNING" ] then @@ -23,7 +23,8 @@ fi # Upgrade svn working copy if possible svnver=$(svn --version --quiet) -if [[ ${svnver} > "1.7" ]] +# shellcheck disable=SC2072 +if [[ ${svnver} > "1.7" ]] then (cd "$SCODOC_DIR"; find . -name .svn -type d -exec dirname {} \; | xargs svn upgrade) fi @@ -63,7 +64,7 @@ CMD="curl --fail --connect-timeout 5 --silent http://scodoc.iutv.univ-paris13.fr #echo $CMD SVERSION="$(${CMD})" -if [ $? == 0 ]; then +if [ "$?" == 0 ]; then #echo "answer=${SVERSION}" echo "${SVERSION}" > "${SCODOC_VERSION_DIR}"/scodoc.sn else @@ -72,13 +73,13 @@ fi # Check that no Zope "access" file has been forgotten in the way: -if [ -e $SCODOC_DIR/../../access ] +if [ -e "$SCODOC_DIR"/../../access ] then - mv $SCODOC_DIR/../../access $SCODOC_DIR/../../access.bak + mv "$SCODOC_DIR"/../../access "$SCODOC_DIR"/../../access.bak fi # Fix some permissions which may have been altered in the way: -chsh -s /bin/sh $POSTGRES_USER # www-data, nologin in Debian 9 +chsh -s /bin/sh "$POSTGRES_USER" # www-data, nologin in Debian 9 chown root.www-data "$SCODOC_DIR" # important to create .pyc chmod 775 "${SCODOC_DIR}" chmod a+r "$SCODOC_DIR"/*.py @@ -119,7 +120,9 @@ then /opt/zope213/bin/pip install requests fi -/opt/zope213/bin/pip install --upgrade python-dateutil +# upgrade old dateutil (check version manually to speedup) +v=$(/opt/zope213/bin/python -c "import dateutil; print dateutil.__version__") +[[ "$v" < "2.8.1" ]] && /opt/zope213/bin/pip install --upgrade python-dateutil # Ensure www-data can duplicate databases (for dumps) su -c $'psql -c \'alter role "www-data" with CREATEDB;\'' "$POSTGRES_SUPERUSER" @@ -130,7 +133,7 @@ echo "Executing post-upgrade script..." "$SCODOC_DIR"/config/postupgrade.py echo "Executing post-upgrade database script..." -su -c "$SCODOC_DIR/config/postupgrade-db.py" $POSTGRES_USER +su -c "$SCODOC_DIR/config/postupgrade-db.py" "$POSTGRES_USER" # echo From 264fd60336a7690929e16e8bb0b4860ded00ffff Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 19 Feb 2021 00:13:47 +0100 Subject: [PATCH 18/32] choix groupe sur formulaire saisie absences hebdo --- ZAbsences.py | 124 +++++++++++++++++++++++++++++------------- gen_tables.py | 8 --- static/js/abs_ajax.js | 34 +++++++----- 3 files changed, 106 insertions(+), 60 deletions(-) diff --git a/ZAbsences.py b/ZAbsences.py index 049875327d..e9cd2f378d 100644 --- a/ZAbsences.py +++ b/ZAbsences.py @@ -67,6 +67,7 @@ from sco_permissions import ScoAbsAddBillet, ScoAbsChange, ScoView from sco_exceptions import ScoValueError, ScoInvalidDateError from TrivialFormulator import TrivialFormulator, TF from gen_tables import GenTable +import html_sco_header import scolars import sco_formsemestre import sco_moduleimpl @@ -78,6 +79,8 @@ import sco_compute_moy import sco_abs from sco_abs import ddmmyyyy +CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS + def _toboolean(x): "convert a value to boolean (ensure backward compat with OLD intranet code)" @@ -343,11 +346,17 @@ class ZAbsences( ) cnx.commit() - security.declareProtected(ScoView, "CountAbs") + def ListAbsInRange( + self, etudid, debut, fin, matin=None, moduleimpl_id=None, cursor=None + ): + """Liste des absences entre deux dates. - def CountAbs(self, etudid, debut, fin, matin=None, moduleimpl_id=None): - """CountAbs - matin= 1 ou 0. + Args: + etudid + debut string iso date ("2020-03-12") + end string iso date ("2020-03-12") + matin None, True, False + moduleimpl_id """ if matin != None: matin = _toboolean(matin) @@ -358,10 +367,11 @@ class ZAbsences( modul = " AND A.MODULEIMPL_ID = %(moduleimpl_id)s " else: modul = "" - cnx = self.GetDBConnexion() - cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) + if not cursor: + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor.execute( - """SELECT COUNT(*) AS NbAbs FROM ( + """ SELECT DISTINCT A.JOUR, A.MATIN FROM ABSENCES A WHERE A.ETUDID = %(etudid)s @@ -370,13 +380,27 @@ class ZAbsences( + modul + """ AND A.JOUR BETWEEN %(debut)s AND %(fin)s - ) AS tmp """, vars(), ) - res = cursor.fetchone()[0] + res = cursor.dictfetchall() return res + security.declareProtected(ScoView, "CountAbs") + + def CountAbs(self, etudid, debut, fin, matin=None, moduleimpl_id=None): + """CountAbs + matin= 1 ou 0. + + Returns: + An integer. + """ + return len( + self.ListAbsInRange( + etudid, debut, fin, matin=matin, moduleimpl_id=moduleimpl_id + ) + ) + security.declareProtected(ScoView, "CountAbsJust") def CountAbsJust(self, etudid, debut, fin, matin=None, moduleimpl_id=None): @@ -743,27 +767,48 @@ class ZAbsences( else: p = "du groupe" gr_tit = ( - p + '' + groups_infos.groups_titles + "" + p + ' ' + groups_infos.groups_titles + "" ) H = [ self.sco_header( page_title="Saisie hebdomadaire des absences", init_qtip=True, - javascripts=["js/etud_info.js", "js/abs_ajax.js"], + javascripts=html_sco_header.BOOTSTRAP_MULTISELECT_JS + + [ + "js/etud_info.js", + "js/abs_ajax.js", + "js/groups_view.js", + ], + cssstyles=CSSSTYLES, no_side_bar=1, REQUEST=REQUEST, ), """' ) i = 1 + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) for etud in etuds: i += 1 etudid = etud["etudid"] @@ -1101,17 +1149,20 @@ class ZAbsences( '' % (tr_class, etudid, etudid, etud["nomprenom"], capstr) ) - for date in dates: + etud_abs = self.ListAbsInRange( + etudid, begin, end, moduleimpl_id=moduleimpl_id, cursor=cursor + ) + for d in odates: + date = d.strftime("%Y-%m-%d") # matin - if self.CountAbs(etudid, date, date, True, moduleimpl_id=moduleimpl_id): + is_abs = {"jour": d, "matin": True} in etud_abs + if is_abs: checked = "checked" else: checked = "" # bulle lors du passage souris - coljour = sco_abs.DAYNAMES[ - (calendar.weekday(int(date[:4]), int(date[5:7]), int(date[8:]))) - ] - datecol = coljour + " " + date[8:] + "/" + date[5:7] + "/" + date[:4] + coljour = sco_abs.DAYNAMES[(calendar.weekday(d.year, d.month, d.day))] + datecol = coljour + " " + d.strftime("%d/%m/%Y") bulle_am = '"' + etud["nomprenom"] + " - " + datecol + ' (matin)"' bulle_pm = '"' + etud["nomprenom"] + " - " + datecol + ' (ap.midi)"' @@ -1127,9 +1178,8 @@ class ZAbsences( ) ) # après-midi - if self.CountAbs( - etudid, date, date, False, moduleimpl_id=moduleimpl_id - ): + is_abs = {"jour": d, "matin": False} in etud_abs + if is_abs: checked = "checked" else: checked = "" diff --git a/gen_tables.py b/gen_tables.py index fbd8309c7d..4e013c6780 100644 --- a/gen_tables.py +++ b/gen_tables.py @@ -534,14 +534,6 @@ class GenTable: ) ] pdf_style_list += self.pdf_table_style - # log('len(Pt)=%s' % len(Pt)) - # log( 'line lens=%s' % [ len(x) for x in Pt ] ) - # log( 'style=\n%s' % pdf_style_list) - # col_min = min([x[1][0] for x in pdf_style_list]) - # col_max = max([x[2][0] for x in pdf_style_list]) - # lin_min = min([x[1][1] for x in pdf_style_list]) - # lin_max = max([x[2][1] for x in pdf_style_list]) - # log('col_min=%s col_max=%s lin_min=%s lin_max=%s' % (col_min, col_max, lin_min, lin_max)) T = Table(Pt, repeatRows=1, colWidths=self.pdf_col_widths, style=pdf_style_list) objects = [] diff --git a/static/js/abs_ajax.js b/static/js/abs_ajax.js index 5b8f030f2d..a6f99fe8b6 100644 --- a/static/js/abs_ajax.js +++ b/static/js/abs_ajax.js @@ -2,20 +2,20 @@ // JS Ajax code for SignaleAbsenceGrSemestre // Contributed by YLB -function ajaxFunction(mod, etudid, dat){ +function ajaxFunction(mod, etudid, dat) { var ajaxRequest; // The variable that makes Ajax possible! - - try{ + + try { // Opera 8.0+, Firefox, Safari ajaxRequest = new XMLHttpRequest(); - } catch (e){ + } catch (e) { // Internet Explorer Browsers - try{ + try { ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { - try{ + try { ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e){ + } catch (e) { // Something went wrong alert("Your browser broke!"); return false; @@ -23,22 +23,26 @@ function ajaxFunction(mod, etudid, dat){ } } // Create a function that will receive data sent from the server - ajaxRequest.onreadystatechange = function(){ - if(ajaxRequest.readyState == 4 && ajaxRequest.status == 200){ - document.getElementById("AjaxDiv").innerHTML=ajaxRequest.responseText; + ajaxRequest.onreadystatechange = function () { + if (ajaxRequest.readyState == 4 && ajaxRequest.status == 200) { + document.getElementById("AjaxDiv").innerHTML = ajaxRequest.responseText; } } ajaxRequest.open("POST", "doSignaleAbsenceGrSemestre", true); - ajaxRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded"); - oForm = document.forms[0]; + ajaxRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + oForm = document.forms[1]; oSelectOne = oForm.elements["moduleimpl_id"]; index = oSelectOne.selectedIndex; - modul_id = oSelectOne.options[index].value; + modul_id = oSelectOne.options[index].value; if (mod == 'add') { - ajaxRequest.send("reply=0&moduleimpl_id=" + modul_id +"&abslist:list=" + etudid + ":" + dat); + ajaxRequest.send("reply=0&moduleimpl_id=" + modul_id + "&abslist:list=" + etudid + ":" + dat); } if (mod == 'remove') { - ajaxRequest.send("reply=0&moduleimpl_id=" + modul_id +"&etudids=" + etudid + "&dates=" + dat); + ajaxRequest.send("reply=0&moduleimpl_id=" + modul_id + "&etudids=" + etudid + "&dates=" + dat); } } +// ----- +function change_moduleimpl(url) { + document.location = url + '&moduleimpl_id=' + document.getElementById('moduleimpl_id').value; +} From cf4cefe46bcdd3a1ec25204b973101dce3801a44 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 22 Feb 2021 10:17:44 +0100 Subject: [PATCH 19/32] Fix: regression formsemestre edit --- sco_formsemestre_edit.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sco_formsemestre_edit.py b/sco_formsemestre_edit.py index 09e9f44f33..0c10c14177 100644 --- a/sco_formsemestre_edit.py +++ b/sco_formsemestre_edit.py @@ -758,8 +758,12 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False): "inscription module:module_id=%s,moduleimpl_id=%s: %s" % (module_id, moduleimpl_id, etudids) ) - context.do_moduleimpl_inscrit_etuds( - moduleimpl_id, formsemestre_id, etudids, REQUEST=REQUEST + sco_moduleimpl.do_moduleimpl_inscrit_etuds( + context, + moduleimpl_id, + formsemestre_id, + etudids, + REQUEST=REQUEST, ) msg += [ "inscription de %d étudiants au module %s" From 575722dd0f80ec37f76933810ab8968bf0b1f07c Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 22 Feb 2021 11:21:29 +0100 Subject: [PATCH 20/32] Fix: affichage abs box (sidebar) apres recherche rapide --- sco_page_etud.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sco_page_etud.py b/sco_page_etud.py index 8fb242dd88..91d6457a78 100644 --- a/sco_page_etud.py +++ b/sco_page_etud.py @@ -153,6 +153,10 @@ def ficheEtud(context, etudid=None, REQUEST=None): "fiche d'informations sur un etudiant" authuser = REQUEST.AUTHENTICATED_USER cnx = context.GetDBConnexion() + if etudid and REQUEST: + # la sidebar est differente s'il y a ou pas un etudid + # voir html_sidebar.sidebar() + REQUEST.form["etudid"] = etudid args = make_etud_args(etudid=etudid, REQUEST=REQUEST) etuds = scolars.etudident_list(cnx, args) if not etuds: From 4b858591f41f0fa07ab71afa25293c2f4da0deab Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 22 Feb 2021 15:22:30 +0100 Subject: [PATCH 21/32] signature PV jury: inversion nom et titre --- sco_pvpdf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sco_pvpdf.py b/sco_pvpdf.py index 391e8c057f..dcccb6c6c5 100644 --- a/sco_pvpdf.py +++ b/sco_pvpdf.py @@ -824,10 +824,10 @@ def _pvjury_pdf_type( # Signature du directeur objects += sco_pdf.makeParas( """ - Le %s, %s""" + %s, %s""" % ( - context.get_preference("DirectorTitle", formsemestre_id) or "", context.get_preference("DirectorName", formsemestre_id) or "", + context.get_preference("DirectorTitle", formsemestre_id) or "", ), style, ) From fca2740518464b900bd95da3a006408bc780913d Mon Sep 17 00:00:00 2001 From: IDK Date: Wed, 24 Feb 2021 09:18:53 +0100 Subject: [PATCH 22/32] Fix: URL --- ZScolar.py | 4 ++++ html_sidebar.py | 21 ++++++++++----------- sco_dept.py | 17 +++++++++++------ sco_evaluations.py | 4 +++- sco_formsemestre_custommenu.py | 2 +- sco_formsemestre_edit.py | 2 +- sco_formsemestre_status.py | 5 +---- sco_formsemestre_validation.py | 8 ++++---- sco_preferences.py | 2 +- 9 files changed, 36 insertions(+), 29 deletions(-) diff --git a/ZScolar.py b/ZScolar.py index 6cc176cce6..ad1672963f 100644 --- a/ZScolar.py +++ b/ZScolar.py @@ -394,6 +394,10 @@ REQUEST.URL0=%s
""" return self.ScoURL() + "/Entreprises" + def AbsencesURL(self): + """URL of Absences""" + return self.ScoURL() + "/Absences" + def UsersURL(self): """URL of Users e.g. https://scodoc.xxx.fr/ScoDoc/DEPT/Scolarite/Users diff --git a/html_sidebar.py b/html_sidebar.py index 0079b562cf..5fc75035d2 100644 --- a/html_sidebar.py +++ b/html_sidebar.py @@ -42,7 +42,13 @@ Génération de la "sidebar" (marge gauche des pages HTML) def sidebar_common(context, REQUEST=None): "partie commune a toutes les sidebar" authuser = REQUEST.AUTHENTICATED_USER - params = {"ScoURL": context.ScoURL(), "authuser": str(authuser)} + params = { + "ScoURL": context.ScoURL(), + "UsersURL": context.UsersURL(), + "NotesURL": context.NotesURL(), + "AbsencesURL": context.AbsencesURL(), + "authuser": str(authuser), + } H = [ 'ScoDoc', '' @@ -50,8 +56,8 @@ def sidebar_common(context, REQUEST=None): context.sidebar_dept(REQUEST), """

Scolarité

Semestres
- Programmes
- Absences
+ Programmes
+ Absences
""" % params, ] @@ -60,14 +66,7 @@ def sidebar_common(context, REQUEST=None): ScoUsersView, context ): H.append( - """Utilisateurs
""" - % params - ) - - if 0: # XXX experimental - H.append( - """Services
""" - % params + """Utilisateurs
""" % params ) if authuser.has_permission(ScoChangePreferences, context): diff --git a/sco_dept.py b/sco_dept.py index 7564ab2a5a..b194183398 100644 --- a/sco_dept.py +++ b/sco_dept.py @@ -129,11 +129,12 @@ def index_html(context, REQUEST=None, showcodes=0, showsemtable=0): ) H.append( - """

+ """

Chercher étape courante: """ + % context.NotesURL() ) # authuser = REQUEST.AUTHENTICATED_USER @@ -155,9 +156,10 @@ Chercher étape courante:

Exports Apogée

""" + % context.NotesURL() ) # H.append( @@ -175,9 +177,9 @@ Chercher étape courante: %(tmpcode)s -
+ - @@ -199,6 +201,7 @@ def _sem_table(context, sems): cur_idx = sem["semestre_id"] else: sem["trclass"] = "" + sem["notes_url"] = context.NotesURL() H.append(tmpl % sem) H.append("
-

Saisie des absences %s %s, - semaine du lundi %s

- -

Annuler

- -

- - """ - % (gr_tit, sem["titre_num"], datelundi, REQUEST.URL0), +

Saisie des absences %s %s, + semaine du lundi %s

+
+ + + + + + Groupes: %s + +
+ """ + % ( + gr_tit, + sem["titre_num"], + datelundi, + groups_infos.formsemestre_id, + datelundi, + destination, + moduleimpl_id or "", + sco_groups_view.menu_groups_choice( + self, groups_infos, submit_on_change=True + ), + ), ] # modimpls_list = [] @@ -802,12 +847,12 @@ class ZAbsences( sel = "selected" # aucun module specifie H.append( - """ - Module concerné par ces absences (optionnel): -

""" + """Module concerné: + +
""" % {"menu_module": menu_module, "url": base_url, "sel": sel} ) @@ -831,7 +876,6 @@ class ZAbsences( REQUEST=None, ): """Saisie des absences sur une journée sur un semestre (ou intervalle de dates) entier""" - # log('SignaleAbsenceGrSemestre: moduleimpl_id=%s destination=%s' % (moduleimpl_id, destination)) groups_infos = sco_groups_view.DisplayedGroupsInfos( self, group_ids, REQUEST=REQUEST ) @@ -1011,7 +1055,7 @@ class ZAbsences( Args: etuds: liste des étudiants - dates: liste de dates iso, par exemple: [ '2020-12-24', ... ] + dates: liste ordonnée de dates iso, par exemple: [ '2020-12-24', ... ] moduleimpl_id: optionnel, module concerné. """ H = [ @@ -1048,6 +1092,8 @@ class ZAbsences( ] # Dates odates = [datetime.date(*[int(x) for x in d.split("-")]) for d in dates] + begin = dates[0] + end = dates[-1] # Titres colonnes noms_jours = [] # eg [ "Lundi", "mardi", "Samedi", ... ] jn = sco_abs.day_names(self) @@ -1076,6 +1122,8 @@ class ZAbsences( '
Aucun étudiant inscrit !
%s%s%(lockimg)s %(groupicon)s%(lockimg)s %(groupicon)s %(mois_debut)s- %(mois_fin)s%(titre_num)s + %(titre_num)s (%(responsable_name)s)
") return "\n".join(H) @@ -245,14 +248,16 @@ def _sem_table_gt(context, sems, showcodes=False): def _style_sems(context, sems): """ajoute quelques attributs de présentation pour la table""" for sem in sems: + sem["notes_url"] = context.NotesURL() sem["_groupicon_target"] = ( - "Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s" % sem + "%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s" + % sem ) sem["_formsemestre_id_class"] = "blacktt" sem["dash_mois_fin"] = ' %(anneescolaire)s' % sem sem["_dash_mois_fin_class"] = "datesem" sem["titre_resp"] = ( - """%(titre_num)s + """%(titre_num)s (%(responsable_name)s)""" % sem ) diff --git a/sco_evaluations.py b/sco_evaluations.py index 21595fe0d6..0c5bdc769d 100644 --- a/sco_evaluations.py +++ b/sco_evaluations.py @@ -108,7 +108,9 @@ def do_evaluation_delete(context, REQUEST, evaluation_id): # news mod = context.do_module_list(args={"module_id": M["module_id"]})[0] mod["moduleimpl_id"] = M["moduleimpl_id"] - mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod + mod["url"] = ( + context.NotesURL() + "/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod + ) sco_news.add( context, REQUEST, diff --git a/sco_formsemestre_custommenu.py b/sco_formsemestre_custommenu.py index ae828c8def..5918a9c33c 100644 --- a/sco_formsemestre_custommenu.py +++ b/sco_formsemestre_custommenu.py @@ -81,7 +81,7 @@ def formsemestre_custommenu_edit(context, formsemestre_id, REQUEST=None): """Dialog to edit the custom menu""" sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) dest_url = ( - context.NotesURL() + "formsemestre_status?formsemestre_id=%s" % formsemestre_id + context.NotesURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id ) H = [ context.html_sem_header(REQUEST, "Modification du menu du semestre ", sem), diff --git a/sco_formsemestre_edit.py b/sco_formsemestre_edit.py index 0c10c14177..86692b0863 100644 --- a/sco_formsemestre_edit.py +++ b/sco_formsemestre_edit.py @@ -1265,7 +1265,7 @@ def formsemestre_delete(context, formsemestre_id, REQUEST=None): elif tf[0] == -1: # cancel return REQUEST.RESPONSE.redirect( context.NotesURL() - + "formsemestre_status?formsemestre_id=" + + "/formsemestre_status?formsemestre_id=" + formsemestre_id ) else: diff --git a/sco_formsemestre_status.py b/sco_formsemestre_status.py index 99267a291f..3ff709d2f1 100644 --- a/sco_formsemestre_status.py +++ b/sco_formsemestre_status.py @@ -505,10 +505,7 @@ def formsemestre_page_title(context, REQUEST): def fill_formsemestre(context, sem, REQUEST=None): """Add some useful fields to help display formsemestres""" - # Notes URL - notes_url = context.absolute_url() - if "/Notes" not in notes_url: - notes_url += "/Notes" + notes_url = context.NotesURL() sem["notes_url"] = notes_url formsemestre_id = sem["formsemestre_id"] if sem["etat"] != "1": diff --git a/sco_formsemestre_validation.py b/sco_formsemestre_validation.py index 97b67a6f3f..151abaa232 100644 --- a/sco_formsemestre_validation.py +++ b/sco_formsemestre_validation.py @@ -1163,8 +1163,8 @@ def formsemestre_validate_previous_ue(context, formsemestre_id, etudid, REQUEST= return "\n".join(H) + tf[1] + X + warn + context.sco_footer(REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect( - context.ScoURL() - + "/Notes/formsemestre_status?formsemestre_id=" + context.NotesURL() + + "/formsemestre_status?formsemestre_id=" + formsemestre_id ) else: @@ -1310,8 +1310,8 @@ def etud_ue_suppress_validation(context, etudid, formsemestre_id, ue_id, REQUEST _invalidate_etud_formation_caches(context, etudid, sem["formation_id"]) return REQUEST.RESPONSE.redirect( - context.ScoURL() - + "/Notes/formsemestre_validate_previous_ue?etudid=%s&formsemestre_id=%s" + context.NotesURL() + + "/formsemestre_validate_previous_ue?etudid=%s&formsemestre_id=%s" % (etudid, formsemestre_id) ) diff --git a/sco_preferences.py b/sco_preferences.py index 49b29ff935..3b356cf5d9 100644 --- a/sco_preferences.py +++ b/sco_preferences.py @@ -2047,7 +2047,7 @@ function set_global_pref(el, pref_name) { ) dest_url = ( self.context.NotesURL() - + "formsemestre_status?formsemestre_id=%s" % self.formsemestre_id + + "/formsemestre_status?formsemestre_id=%s" % self.formsemestre_id ) if tf[0] == 0: return "\n".join(H) + tf[1] + self.context.sco_footer(REQUEST) From 86a517d40df8a8de7aba62e2aca6dbf0fc1e001a Mon Sep 17 00:00:00 2001 From: IDK Date: Wed, 24 Feb 2021 09:33:16 +0100 Subject: [PATCH 23/32] Enrichissement bulletins JSON (contrib. CM St Etienne) --- sco_bulletins_json.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sco_bulletins_json.py b/sco_bulletins_json.py index d21c958673..156e5264fc 100644 --- a/sco_bulletins_json.py +++ b/sco_bulletins_json.py @@ -209,6 +209,7 @@ def formsemestre_bulletinetud_published_dict( value=scu.fmt_note(ue_status["cur_moy_ue"]), min=scu.fmt_note(ue["min"]), max=scu.fmt_note(ue["max"]), + moy=scu.fmt_note(ue["moy"]), # CM : ajout pour faire apparaitre la moyenne des UE ), rang=str(nt.ue_rangs[ue["ue_id"]][0][etudid]), effectif=str(nt.ue_rangs[ue["ue_id"]][1]), @@ -275,6 +276,7 @@ def formsemestre_bulletinetud_published_dict( ), coefficient=e["coefficient"], evaluation_type=e["evaluation_type"], + evaluation_id=e["evaluation_id"], # CM : ajout pour permettre de faire le lien sur les bulletins en ligne avec l'évaluation description=scu.quote_xml_attr(e["description"]), note=val, ) From d27b8d127d3c55c8be97cc59eb4bcd3ae62cf0d6 Mon Sep 17 00:00:00 2001 From: IDK Date: Fri, 26 Feb 2021 13:54:16 +0100 Subject: [PATCH 24/32] Fix: form saise abs --- ZAbsences.py | 4 ++-- static/js/abs_ajax.js | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ZAbsences.py b/ZAbsences.py index e9cd2f378d..52a68c6a24 100644 --- a/ZAbsences.py +++ b/ZAbsences.py @@ -795,7 +795,7 @@ class ZAbsences( Groupes: %s -
+ """ % ( gr_tit, @@ -983,7 +983,7 @@ class ZAbsences( les %s

%s - + """ % (gr_tit, sem["titre_num"], dayname, url_link_semaines, msg), ] diff --git a/static/js/abs_ajax.js b/static/js/abs_ajax.js index a6f99fe8b6..bea7c45616 100644 --- a/static/js/abs_ajax.js +++ b/static/js/abs_ajax.js @@ -30,10 +30,9 @@ function ajaxFunction(mod, etudid, dat) { } ajaxRequest.open("POST", "doSignaleAbsenceGrSemestre", true); ajaxRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - oForm = document.forms[1]; - oSelectOne = oForm.elements["moduleimpl_id"]; - index = oSelectOne.selectedIndex; - modul_id = oSelectOne.options[index].value; + var oSelectOne = $("#abs_form")[0].elements["moduleimpl_id"]; + var index = oSelectOne.selectedIndex; + var modul_id = oSelectOne.options[index].value; if (mod == 'add') { ajaxRequest.send("reply=0&moduleimpl_id=" + modul_id + "&abslist:list=" + etudid + ":" + dat); } From 61ff72082f2d57bba7bc6f87da414a2a82f04961 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 2 Mar 2021 23:53:43 +0100 Subject: [PATCH 25/32] =?UTF-8?q?Fix:=20notes=20a=20prise=20en=20compte=20?= =?UTF-8?q?imm=C3=A9diate.=20+=20tests=20de=20ces=20cas.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sco_evaluations.py | 10 +++----- scotests/test_basic.py | 57 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/sco_evaluations.py b/sco_evaluations.py index 0c5bdc769d..82667bb580 100644 --- a/sco_evaluations.py +++ b/sco_evaluations.py @@ -235,13 +235,16 @@ def do_evaluation_etat( else: complete = True if ( - TotalNbAtt > 0 + TotalNbMissing > 0 and (TotalNbMissing == TotalNbAtt or E["publish_incomplete"] != "0") and not is_malus ): evalattente = True else: evalattente = False + # mais ne met pas en attente les evals immediates sans aucune notes: + if E["publish_incomplete"] != "0" and nb_notes == 0: + evalattente = False # Calcul moyenne dans chaque groupe de TD gr_moyennes = [] # group : {moy,median, nb_notes} @@ -265,11 +268,6 @@ def do_evaluation_etat( } ) gr_moyennes.sort(key=operator.itemgetter("group_name")) - # log('gr_moyennes=%s' % gr_moyennes) - # _DEE_TOT += (time.time() - t0) - # log('%s\t_DEE_TOT=%f' % (evaluation_id, _DEE_TOT)) - # if evaluation_id == 'GEAEVAL82883': - # logCallStack() # retourne mapping return { diff --git a/scotests/test_basic.py b/scotests/test_basic.py index d16941e3fd..8cfbd31c2b 100644 --- a/scotests/test_basic.py +++ b/scotests/test_basic.py @@ -19,6 +19,8 @@ import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error import sco_utils import sco_abs import sco_abs_views +import sco_bulletins +import sco_evaluations G = sco_fake_gen.ScoFake(context.Notes) G.verbose = False @@ -65,12 +67,65 @@ e = G.create_evaluation( coefficient=1.0, ) -# --- Saisie notes +# --- Saisie toutes les notes de l'évaluation for etud in etuds: nb_changed, nb_suppress, existing_decisions = G.create_note( evaluation=e, etud=etud, note=float(random.randint(0, 20)) ) +# --- Vérifie que les notes sont prises en compte: +b = sco_bulletins.formsemestre_bulletinetud_dict( + context.Notes, sem["formsemestre_id"], etud["etudid"], REQUEST=REQUEST +) +# Toute les notes sont saisies, donc eval complète +etat = sco_evaluations.do_evaluation_etat(context.Notes, e["evaluation_id"]) +assert etat["evalcomplete"] +# Un seul module, donc moy gen == note module +assert b["ues"][0]["cur_moy_ue_txt"] == b["ues"][0]["modules"][0]["mod_moy_txt"] +# Note au module égale à celle de l'éval +assert ( + b["ues"][0]["modules"][0]["mod_moy_txt"] + == b["ues"][0]["modules"][0]["evaluations"][0]["note_txt"] +) + + +# --- Une autre évaluation +e2 = G.create_evaluation( + moduleimpl_id=mi["moduleimpl_id"], + jour="02/01/2020", + description="evaluation test 2", + coefficient=1.0, +) +# Saisie les notes des 5 premiers étudiants: +for etud in etuds[:5]: + nb_changed, nb_suppress, existing_decisions = G.create_note( + evaluation=e2, etud=etud, note=float(random.randint(0, 20)) + ) +# Cette éval n'est pas complète +etat = sco_evaluations.do_evaluation_etat(context.Notes, e2["evaluation_id"]) +assert etat["evalcomplete"] == False +# la première éval est toujours complète: +etat = sco_evaluations.do_evaluation_etat(context.Notes, e["evaluation_id"]) +assert etat["evalcomplete"] + +# Modifie l'évaluation 2 pour "prise en compte immédiate" +e2["publish_incomplete"] = "1" +context.Notes.do_evaluation_edit(REQUEST, e2) +etat = sco_evaluations.do_evaluation_etat(context.Notes, e2["evaluation_id"]) +assert etat["evalcomplete"] == False +assert etat["nb_att"] == 0 # il n'y a pas de notes (explicitement) en attente +assert etat["evalattente"] # mais l'eval est en attente (prise en compte immédiate) + +# Saisie des notes qui manquent: +for etud in etuds[5:]: + nb_changed, nb_suppress, existing_decisions = G.create_note( + evaluation=e2, etud=etud, note=float(random.randint(0, 20)) + ) +etat = sco_evaluations.do_evaluation_etat(context.Notes, e2["evaluation_id"]) +assert etat["evalcomplete"] +assert etat["nb_att"] == 0 +assert not etat["evalattente"] # toutes les notes sont présentes + # --- Saisie absences etudid = etuds[0]["etudid"] From c5fe6ca674411b5112e6cbdba292ae1bca5dabb4 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 11 Mar 2021 14:49:37 +0100 Subject: [PATCH 26/32] =?UTF-8?q?Evaluations=20de=20deuxi=C3=A8me=20sessio?= =?UTF-8?q?n=20(pour=20Masters)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.py | 3 ++- sco_bulletins.py | 2 ++ sco_compute_moy.py | 18 ++++++++++++++---- sco_evaluations.py | 27 ++++++++++++++++++++++----- sco_moduleimpl_status.py | 13 ++++++++++--- sco_utils.py | 1 + static/css/scodoc.css | 7 ++++++- 7 files changed, 57 insertions(+), 14 deletions(-) diff --git a/VERSION.py b/VERSION.py index 3ff1eed40e..b1d6df673f 100644 --- a/VERSION.py +++ b/VERSION.py @@ -8,13 +8,14 @@ SCONAME = "ScoDoc" SCONEWS = """

Année 2021

    +
  • Évaluations de type "deuxième session"
  • Gestion du genre neutre (pas d'affichage de la civilité)
  • Diverses corrections (PV de jurys, ...)
  • Modernisation du code Python

Année 2020

    -
  • Corrections d'erreurs, améliorations saise absences< et affichage bulletins
  • +
  • Corrections d'erreurs, améliorations saisie absences et affichage bulletins
  • Nouveau site scodoc.org pour la documentation
  • Enregistrement de semestres extérieurs
  • Améliorations PV de Jury
  • diff --git a/sco_bulletins.py b/sco_bulletins.py index 83c39f0700..fc2fb92a6b 100644 --- a/sco_bulletins.py +++ b/sco_bulletins.py @@ -548,6 +548,8 @@ def _ue_mod_bulletin(context, etudid, formsemestre_id, ue_id, modimpls, nt, vers e["coef_txt"] = scu.fmt_coef(e["coefficient"]) if e["evaluation_type"] == scu.EVALUATION_RATTRAPAGE: e["coef_txt"] = "rat." + elif e["evaluation_type"] == scu.EVALUATION_SESSION2: + e["coef_txt"] = "sess. 2" if e["etat"]["evalattente"]: mod_attente = True # une eval en attente dans ce module if (not is_malus) or (val != "NP"): diff --git a/sco_compute_moy.py b/sco_compute_moy.py index 368506fc52..904436b3ab 100644 --- a/sco_compute_moy.py +++ b/sco_compute_moy.py @@ -38,6 +38,7 @@ from sco_utils import ( NOTES_NEUTRALISE, EVALUATION_NORMALE, EVALUATION_RATTRAPAGE, + EVALUATION_SESSION2, ) from sco_exceptions import ScoException from notesdb import EditableTable, quote_html @@ -242,7 +243,10 @@ def do_moduleimpl_moyennes(context, nt, mod): if e["etat"]["evalattente"]: attente = True - if e["evaluation_type"] == EVALUATION_RATTRAPAGE: + if ( + e["evaluation_type"] == EVALUATION_RATTRAPAGE + or e["evaluation_type"] == EVALUATION_SESSION2 + ): if eval_rattr: # !!! plusieurs rattrapages ! diag_info.update( @@ -344,7 +348,7 @@ def do_moduleimpl_moyennes(context, nt, mod): if diag_info: diag_info["moduleimpl_id"] = moduleimpl_id R[etudid] = user_moy - # Note de rattrapage ? + # Note de rattrapage ou dexuième session ? if eval_rattr: if eval_rattr["notes"].has_key(etudid): note = eval_rattr["notes"][etudid]["value"] @@ -353,8 +357,14 @@ def do_moduleimpl_moyennes(context, nt, mod): R[etudid] = note else: note_sur_20 = note * 20.0 / eval_rattr["note_max"] - if note_sur_20 > R[etudid]: - # log('note_sur_20=%s' % note_sur_20) + if eval_rattr["evaluation_type"] == EVALUATION_RATTRAPAGE: + # rattrapage classique: prend la meilleure note entre moyenne + # module et note eval rattrapage + if note_sur_20 > R[etudid]: + # log('note_sur_20=%s' % note_sur_20) + R[etudid] = note_sur_20 + elif eval_rattr["evaluation_type"] == EVALUATION_SESSION2: + # rattrapage type "deuxième session": remplace la note moyenne R[etudid] = note_sur_20 return R, valid_evals, attente, diag_info diff --git a/sco_evaluations.py b/sco_evaluations.py index 82667bb580..1fe86a0f95 100644 --- a/sco_evaluations.py +++ b/sco_evaluations.py @@ -229,6 +229,7 @@ def do_evaluation_etat( if ( (TotalNbMissing > 0) and (E["evaluation_type"] != scu.EVALUATION_RATTRAPAGE) + and (E["evaluation_type"] != scu.EVALUATION_SESSION2) and not is_malus ): complete = False @@ -891,12 +892,20 @@ def evaluation_create_form( notes, en sus des moyennes de modules. Attention, cette option n'empêche pas la publication sur les bulletins en version "longue" (la note est donc visible par les étudiants sur le portail).

    - La modalité "rattrapage" permet de définir une évaluation dont les notes remplaceront les moyennes du modules - si elles sont meilleures que celles calculées. Dans ce cas, le coefficient est ignoré, et toutes les notes n'ont + Les modalités "rattrapage" et "deuxième session" définissent des évaluations prises en compte de + façon spéciale:

    +
      +
    • les notes d'une évaluation de "rattrapage" remplaceront les moyennes du module + si elles sont meilleures que celles calculées.
    • +
    • les notes de "deuxième session" remplacent, lorsqu'elles sont saisies, la moyenne de l'étudiant + à ce module, même si la note de deuxième session est plus faible.
    • +
    +

    + Dans ces deux cas, le coefficient est ignoré, et toutes les notes n'ont pas besoin d'être rentrées.

    - Les évaluations des modules de type "malus" sont spéciales: le coefficient n'est pas utilisé. + Par ailleurs, les évaluations des modules de type "malus" sont toujours spéciales: le coefficient n'est pas utilisé. Les notes de malus sont toujours comprises entre -20 et 20. Les points sont soustraits à la moyenne de l'UE à laquelle appartient le module malus (si la note est négative, la moyenne est donc augmentée).

    @@ -1019,9 +1028,17 @@ def evaluation_create_form( { "input_type": "menu", "title": "Modalité", - "allowed_values": (scu.EVALUATION_NORMALE, scu.EVALUATION_RATTRAPAGE), + "allowed_values": ( + scu.EVALUATION_NORMALE, + scu.EVALUATION_RATTRAPAGE, + scu.EVALUATION_SESSION2, + ), "type": "int", - "labels": ("Normale", "Rattrapage"), + "labels": ( + "Normale", + "Rattrapage (remplace si meilleure note)", + "Deuxième session (remplace toujours)", + ), }, ), ] diff --git a/sco_moduleimpl_status.py b/sco_moduleimpl_status.py index 62400761eb..ba32596da9 100644 --- a/sco_moduleimpl_status.py +++ b/sco_moduleimpl_status.py @@ -34,6 +34,7 @@ import sco_utils as scu from sco_utils import ( EVALUATION_NORMALE, EVALUATION_RATTRAPAGE, + EVALUATION_SESSION2, ) from sco_permissions import ScoEtudInscrit, ScoAbsChange from notes_log import log @@ -341,8 +342,8 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No partition_id=partition_id, select_first_partition=True, ) - if eval["evaluation_type"] == EVALUATION_RATTRAPAGE: - tr_class = "mievr_rattr" + if eval["evaluation_type"] in (EVALUATION_RATTRAPAGE, EVALUATION_SESSION2): + tr_class = "mievr mievr_rattr" else: tr_class = "mievr" tr_class_1 = "mievr" @@ -361,7 +362,13 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No ) H.append("    %(description)s" % eval) if eval["evaluation_type"] == EVALUATION_RATTRAPAGE: - H.append("""rattrapage""") + H.append( + """rattrapage""" + ) + elif eval["evaluation_type"] == EVALUATION_SESSION2: + H.append( + """session 2""" + ) if etat["last_modif"]: H.append( """(dernière modif le %s)""" diff --git a/sco_utils.py b/sco_utils.py index 60b26cbcb8..6a30c3cb25 100644 --- a/sco_utils.py +++ b/sco_utils.py @@ -111,6 +111,7 @@ NOTES_MENTIONS_LABS = ( EVALUATION_NORMALE = 0 EVALUATION_RATTRAPAGE = 1 +EVALUATION_SESSION2 = 2 def fmt_note(val, note_max=None, keep_numeric=False): diff --git a/static/css/scodoc.css b/static/css/scodoc.css index deb9cf3c75..a3c5b031dc 100644 --- a/static/css/scodoc.css +++ b/static/css/scodoc.css @@ -1261,9 +1261,14 @@ tr.mievr_rattr { background-color:#dddddd; } span.mievr_rattr { + display: inline-block; font-weight: bold; - color: blue; + font-size: 80%; + color: white; + background-color: orangered; margin-left: 2em; + margin-top: 1px; + margin-bottom: 2px;; border: 1px solid red; padding: 1px 3px 1px 3px; } From 7bed86f620fa78b0df09c98a7399ba603256d027 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Mon, 19 Apr 2021 11:26:22 +0200 Subject: [PATCH 27/32] add a test: autorisation saisie note vs decision jury --- sco_compute_moy.py | 2 +- scotests/test_basic.py | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/sco_compute_moy.py b/sco_compute_moy.py index 904436b3ab..7dba22a570 100644 --- a/sco_compute_moy.py +++ b/sco_compute_moy.py @@ -348,7 +348,7 @@ def do_moduleimpl_moyennes(context, nt, mod): if diag_info: diag_info["moduleimpl_id"] = moduleimpl_id R[etudid] = user_moy - # Note de rattrapage ou dexuième session ? + # Note de rattrapage ou deuxième session ? if eval_rattr: if eval_rattr["notes"].has_key(etudid): note = eval_rattr["notes"][etudid]["value"] diff --git a/scotests/test_basic.py b/scotests/test_basic.py index 8cfbd31c2b..d4ca91a728 100644 --- a/scotests/test_basic.py +++ b/scotests/test_basic.py @@ -21,6 +21,9 @@ import sco_abs import sco_abs_views import sco_bulletins import sco_evaluations +import sco_codes_parcours +import sco_parcours_dut +import sco_formsemestre_validation G = sco_fake_gen.ScoFake(context.Notes) G.verbose = False @@ -150,3 +153,27 @@ _ = sco_abs_views.doJustifAbsence( a = sco_abs.getAbsSemEtud(context.Absences, sem, etudid) assert a.CountAbs() == 3 assert a.CountAbsJust() == 1 + +# --- Permission saisie notes et décisions de jury, avec ou sans démission ou défaillance +# on n'a pas encore saisi de décisions +assert not sco_parcours_dut.formsemestre_has_decisions(context, sem["formsemestre_id"]) +# Saisie d'un décision AJ, non assidu +etudid = etuds[-1]["etudid"] +sco_parcours_dut.formsemestre_validate_ues( + context.Notes, + sem["formsemestre_id"], + etudid, + sco_codes_parcours.AJ, + False, + REQUEST=REQUEST, +) +assert sco_parcours_dut.formsemestre_has_decisions( + context.Notes, sem["formsemestre_id"] +) +# Suppression de la décision +sco_formsemestre_validation.formsemestre_validation_suppress_etud( + context.Notes, sem["formsemestre_id"], etudid +) +assert not sco_parcours_dut.formsemestre_has_decisions( + context.Notes, sem["formsemestre_id"] +) From 314ed4ab7d3e7525b205662e40a1323b38105095 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 22 Apr 2021 22:14:25 +0200 Subject: [PATCH 28/32] Fix scripts --- config/create_dept.sh | 2 +- config/delete_dept.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/create_dept.sh b/config/create_dept.sh index 52775272f3..e89834f9b3 100755 --- a/config/create_dept.sh +++ b/config/create_dept.sh @@ -9,7 +9,7 @@ # E. Viennet, Juin 2008 # -set -euo pipefail +set -eo pipefail source config.sh source utils.sh diff --git a/config/delete_dept.sh b/config/delete_dept.sh index cec81d01d9..46c55c015c 100755 --- a/config/delete_dept.sh +++ b/config/delete_dept.sh @@ -60,7 +60,7 @@ then # suppression de la base postgres db_name=$(sed '/^dbname=*/!d; s///;q' < "$cfg_pathname") - if su -c "psql -lt" "$POSTGRES_SUPERUSER" | cut -d \| -f 1 | grep -wq SCORT + if su -c "psql -lt" "$POSTGRES_SUPERUSER" | cut -d \| -f 1 | grep -wq "$db_name" then echo "Suppression de la base postgres $db_name ..." su -c "dropdb $db_name" "$POSTGRES_SUPERUSER" || terminate "ne peux supprimer base de donnees $db_name" From 4a5d4adee156c75fae26029e08bb435063616604 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 23 Apr 2021 00:06:07 +0200 Subject: [PATCH 29/32] =?UTF-8?q?Fix:=20bug=20association=20sem.=20=C3=A0?= =?UTF-8?q?=20nouvelle=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sco_formsemestre_edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sco_formsemestre_edit.py b/sco_formsemestre_edit.py index 86692b0863..d8e57bca15 100644 --- a/sco_formsemestre_edit.py +++ b/sco_formsemestre_edit.py @@ -1199,7 +1199,7 @@ def _reassociate_moduleimpls( ) for mod in modimpls: mod["module_id"] = modules_old2new[mod["module_id"]] - context.do_moduleimpl_edit(mod, formsemestre_id=formsemestre_id, cnx=cnx) + sco_moduleimpl.do_moduleimpl_edit(context, mod, formsemestre_id=formsemestre_id) # update decisions: events = scolars.scolar_events_list(cnx, args={"formsemestre_id": formsemestre_id}) for e in events: From 9e65fa765406e39186905002eeceac3195619f95 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 23 Apr 2021 10:24:45 +0200 Subject: [PATCH 30/32] Fixes #70 --- ZopeProducts/exUserFolder/exUserFolder.py | 6 +-- config/fix_bug70_db.py | 64 +++++++++++++++++++++++ config/postupgrade.py | 28 +++++++--- config/scodocutils.py | 1 + debug.py | 10 ++-- notes_table.py | 3 ++ scotests/scointeractive.sh | 49 +++++++++++------ scotests/test_capitalisation.py | 2 +- 8 files changed, 133 insertions(+), 30 deletions(-) create mode 100644 config/fix_bug70_db.py mode change 100755 => 100644 config/postupgrade.py mode change 100644 => 100755 config/scodocutils.py diff --git a/ZopeProducts/exUserFolder/exUserFolder.py b/ZopeProducts/exUserFolder/exUserFolder.py index 1c330c96ef..a5b07c8ec2 100644 --- a/ZopeProducts/exUserFolder/exUserFolder.py +++ b/ZopeProducts/exUserFolder/exUserFolder.py @@ -251,8 +251,8 @@ class exUserFolder(Folder,BasicUserFolder,BasicGroupFolderMixin, ('Manager',)), ('View', ('manage_changePassword', - 'manage_forgotPassword', 'docLogin','docLoginRedirect', - 'docLogout', 'logout', 'DialogHeader', + 'manage_forgotPassword','docLoginRedirect', + 'logout', 'DialogHeader', 'DialogFooter', 'manage_signupUser', 'MessageDialog', 'redirectToLogin','manage_changeProps'), ('Anonymous', 'Authenticated', 'Manager')), @@ -269,7 +269,7 @@ class exUserFolder(Folder,BasicUserFolder,BasicGroupFolderMixin, ('Access contents information', ('hasProperty', 'propertyIds', 'propertyValues','propertyItems', 'getProperty', 'getPropertyType', - 'propertyMap', 'docLogin','docLoginRedirect', + 'propertyMap', 'docLoginRedirect', 'DialogHeader', 'DialogFooter', 'MessageDialog', 'redirectToLogin',), ('Anonymous', 'Authenticated', 'Manager')), diff --git a/config/fix_bug70_db.py b/config/fix_bug70_db.py new file mode 100644 index 0000000000..4c0d640337 --- /dev/null +++ b/config/fix_bug70_db.py @@ -0,0 +1,64 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +"""Fix bug #70 + +Utiliser comme: + scotests/scointeractive.sh DEPT config/fix_bug70_db.py + +""" +context = context.Notes # pylint: disable=undefined-variable +REQUEST = REQUEST # pylint: disable=undefined-variable +import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error +import os +import sys +import sco_utils +import notesdb +import sco_formsemestre +import sco_formsemestre_edit +import sco_moduleimpl + +G = sco_fake_gen.ScoFake(context.Notes) + + +def fix_formsemestre_formation_bug70(formsemestre_id): + """Le bug #70 a pu entrainer des incohérences + lors du clonage avorté de semestres. + Cette fonction réassocie le semestre à la formation + à laquelle appartiennent ses modulesimpls. + 2021-04-23 + """ + sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) + cursor = notesdb.SimpleQuery( + context, + """SELECT m.formation_id + FROM notes_modules m, notes_moduleimpl mi + WHERE mi.module_id = m.module_id + AND mi.formsemestre_id = %(formsemestre_id)s + """, + {"formsemestre_id": formsemestre_id}, + ) + modimpls_formations = set([x[0] for x in cursor]) + if len(modimpls_formations) > 1: + # this is should not occur + G.log( + "Warning: fix_formsemestre_formation_bug70: modules from several formations in sem %s" + % formsemestre_id + ) + elif len(modimpls_formations) == 1: + modimpls_formation_id = modimpls_formations.pop() + if modimpls_formation_id != sem["formation_id"]: + # Bug #70: fix + G.log("fix_formsemestre_formation_bug70: fixing %s" % formsemestre_id) + sem["formation_id"] = modimpls_formation_id + context.do_formsemestre_edit(sem, html_quote=False) + + +formsemestre_ids = [ + x[0] + for x in notesdb.SimpleQuery( + context, "SELECT formsemestre_id FROM notes_formsemestre", {} + ) +] +for formsemestre_id in formsemestre_ids: + fix_formsemestre_formation_bug70(formsemestre_id) diff --git a/config/postupgrade.py b/config/postupgrade.py old mode 100755 new mode 100644 index 1137357ac8..b4bc02bb32 --- a/config/postupgrade.py +++ b/config/postupgrade.py @@ -11,21 +11,22 @@ _before_ upgrading the database. E. Viennet, June 2008 Mar 2017: suppress upgrade of very old Apache configs Aug 2020: move photos to .../var/scodoc/ +Apr 2021: bug #70 """ import os import sys import glob -import shutil -from scodocutils import log, SCODOC_DIR, SCODOC_VAR_DIR, SCODOC_LOGOS_DIR +import shutil +from scodocutils import log, SCODOC_DIR, SCODOC_VAR_DIR, SCODOC_LOGOS_DIR, SCO_TMPDIR if os.getuid() != 0: - log('postupgrade.py: must be run as root') + log("postupgrade.py: must be run as root") sys.exit(1) # --- # Migrate photos (2020-08-16, svn 1908) old_photo_dir = os.path.join(SCODOC_DIR, "static", "photos") -photo_dirs = glob.glob( old_photo_dir + "/F*") +photo_dirs = glob.glob(old_photo_dir + "/F*") if photo_dirs: log("Moving photos to new directory...") shutil.move(old_photo_dir, SCODOC_VAR_DIR) @@ -33,7 +34,7 @@ if photo_dirs: # Migrate depts (2020-08-17, svn 1909) old_depts_dir = os.path.join(SCODOC_DIR, "config", "depts") -cfg_files = glob.glob( old_depts_dir + "/*.cfg") +cfg_files = glob.glob(old_depts_dir + "/*.cfg") depts_dir = os.path.join(SCODOC_VAR_DIR, "config/depts/") for cfg in cfg_files: log("Moving %s to new directory..." % cfg) @@ -41,7 +42,7 @@ for cfg in cfg_files: # Move logos if not os.path.exists(SCODOC_LOGOS_DIR): - old_logos = os.path.join(SCODOC_DIR,"logos") + old_logos = os.path.join(SCODOC_DIR, "logos") if os.path.exists(old_logos): log("Moving logos to new directory...") dest = os.path.normpath(os.path.join(SCODOC_LOGOS_DIR, "..")) @@ -50,10 +51,23 @@ if not os.path.exists(SCODOC_LOGOS_DIR): log("Warning: logos directory is missing (%s)" % SCODOC_LOGOS_DIR) # Move dept-specific logos -for d in glob.glob( SCODOC_DIR + "/logos_*" ): +for d in glob.glob(SCODOC_DIR + "/logos_*"): log("Moving %s to %s" % (d, SCODOC_LOGOS_DIR)) shutil.move(d, SCODOC_LOGOS_DIR) +# Fix bug #70 +depts = [ + os.path.splitext(os.path.basename(f))[0] for f in glob.glob(depts_dir + "/*.cfg") +] +for dept in depts: + fixed_filename = SCO_TMPDIR + "/.%s_bug70_fixed" % dept + if not os.path.exists(fixed_filename): + log("fixing #70 on %s" % dept) + os.system("../scotests/scointeractive.sh -x %s config/fix_bug70_db.py" % dept) + # n'essaie qu'une foixs, même en cas d'échec + f = open(fixed_filename, "a") + f.close() + # Continue here... # --- diff --git a/config/scodocutils.py b/config/scodocutils.py old mode 100644 new mode 100755 index 51d6c71d6a..5141d00037 --- a/config/scodocutils.py +++ b/config/scodocutils.py @@ -30,6 +30,7 @@ SCODOC_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "") if not SCODOC_VAR_DIR: log("Error: environment variable SCODOC_VAR_DIR is not defined") sys.exit(1) +SCO_TMPDIR = os.path.join(SCODOC_VAR_DIR, "tmp") SCODOC_LOGOS_DIR = os.environ.get("SCODOC_LOGOS_DIR", "") diff --git a/debug.py b/debug.py index 52a31f7a73..4b7d3e45c7 100644 --- a/debug.py +++ b/debug.py @@ -57,18 +57,20 @@ import sco_bulletins_xml # Prend le premier departement comme context -def go(app, n=0): +def go(app, n=0, verbose=True): context = app.ScoDoc.objectValues("Folder")[n].Scolarite - print("context in dept ", context.DeptId()) + if verbose: + print("context in dept ", context.DeptId()) return context -def go_dept(app, dept): +def go_dept(app, dept, verbose=True): objs = app.ScoDoc.objectValues("Folder") for o in objs: context = o.Scolarite if context.DeptId() == dept: - print("context in dept ", context.DeptId()) + if verbose: + print("context in dept ", context.DeptId()) return context raise ValueError("dep %s not found" % dept) diff --git a/notes_table.py b/notes_table.py index 38de3147ed..04be302cd6 100644 --- a/notes_table.py +++ b/notes_table.py @@ -1132,6 +1132,9 @@ class NotesTable: def sem_has_decisions(self): """True si au moins une decision de jury dans ce semestre""" + if [x for x in self.decisions_jury_ues.values() if x]: + return True + return len([x for x in self.decisions_jury_ues.values() if x]) > 0 def etud_has_decision(self, etudid): diff --git a/scotests/scointeractive.sh b/scotests/scointeractive.sh index 2cecaf90c2..4db04850b8 100755 --- a/scotests/scointeractive.sh +++ b/scotests/scointeractive.sh @@ -9,9 +9,10 @@ # le département via l'interface web (Zope) usage() { - echo "Usage: $0 [-r] dept [script...]" + echo "Usage: $0 [-h] [-r] [-x] dept [script...]" echo "Lance un environnement interactif python/ScoDoc" echo " -r: supprime et recrée le département (attention: efface la base !)" + echo " -x: exit après exécution des scripts, donc mode non interactif" exit 1 } @@ -20,24 +21,38 @@ cd /opt/scodoc/Products/ScoDoc || exit 2 source config/config.sh source config/utils.sh -if [ $# -lt 1 ] -then - usage -fi +RECREATE_DEPT=0 +PYTHON_INTERACTIVE="-i" -if [ "$1" = "-r" ] -then +while [ -n "$1" ]; do + PARAM="$1" + [ "${PARAM::1}" != "-" ] && break + case $PARAM in + -h | --help) + usage + exit 0 + ;; + -r) + RECREATE_DEPT=1 + ;; + -x) + PYTHON_INTERACTIVE="" + ;; + *) + echo "ERROR: unknown parameter \"$PARAM\"" + usage + exit 1 + ;; + esac shift - recreate_dept=1 -else - recreate_dept=0 -fi +done + DEPT="$1" shift -if [ "$recreate_dept" = 1 ] +if [ "$RECREATE_DEPT" = 1 ] then cfg_pathname="${SCODOC_VAR_DIR}/config/depts/$DEPT".cfg if [ -e "$cfg_pathname" ] @@ -48,13 +63,17 @@ then # systemctl start scodoc fi -cmd="from __future__ import print_function;from Zope2 import configure;configure('/opt/scodoc/etc/zope.conf');import Zope2; app=Zope2.app();from debug import *;context = go_dept(app, '""$DEPT""');" +cmd="from __future__ import print_function;from Zope2 import configure;configure('/opt/scodoc/etc/zope.conf');import Zope2; app=Zope2.app();from debug import *;context = go_dept(app, '""$DEPT""', verbose=False);" for f in "$@" do cmd="${cmd}exec(open(\"${f}\").read());" done -/opt/zope213/bin/python -i -c "$cmd" - +if [ -z "$PYTHON_INTERACTIVE" ] +then + /opt/zope213/bin/python -c "$cmd" +else + /opt/zope213/bin/python "$PYTHON_INTERACTIVE" -c "$cmd" +fi diff --git a/scotests/test_capitalisation.py b/scotests/test_capitalisation.py index 2bb88fbd61..7b4532f81c 100644 --- a/scotests/test_capitalisation.py +++ b/scotests/test_capitalisation.py @@ -11,7 +11,7 @@ Utiliser comme: """ # La variable context est définie par le script de lancement -# l'affecte ainsi pour évietr les warnins pylint: +# l'affecte ainsi pour éviter les warnings pylint: context = context # pylint: disable=undefined-variable import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error import sco_utils From 61d47e8fad25d8a4485bbe9f166be205b9c75a1d Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 23 Apr 2021 10:30:16 +0200 Subject: [PATCH 31/32] fix comment --- config/postupgrade.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/postupgrade.py b/config/postupgrade.py index b4bc02bb32..f8a7d313f9 100644 --- a/config/postupgrade.py +++ b/config/postupgrade.py @@ -1,4 +1,5 @@ #!/opt/zope213/bin/python +# -*- coding: utf-8 -*- """ ScoDoc post-upgrade script. @@ -64,7 +65,7 @@ for dept in depts: if not os.path.exists(fixed_filename): log("fixing #70 on %s" % dept) os.system("../scotests/scointeractive.sh -x %s config/fix_bug70_db.py" % dept) - # n'essaie qu'une foixs, même en cas d'échec + # n'essaie qu'une fois, même en cas d'échec f = open(fixed_filename, "a") f.close() From bfc35578147e0e1146ac190db56d2c96d8f3c5b0 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 25 Apr 2021 10:10:46 +0200 Subject: [PATCH 32/32] =?UTF-8?q?Param=C3=A9trage=20des=20fichiers=20CSV?= =?UTF-8?q?=20Moodle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gen_tables.py | 11 ++++++++--- sco_groups_view.py | 19 ++++++++++++++----- sco_preferences.py | 24 +++++++++++++++++++++++- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/gen_tables.py b/gen_tables.py index 4e013c6780..3f5a95ef36 100644 --- a/gen_tables.py +++ b/gen_tables.py @@ -123,6 +123,7 @@ class GenTable: pdf_col_widths=None, xml_outer_tag="table", xml_row_tag="row", + text_with_titles=False, # CSV with header line text_fields_separator="\t", preferences=None, ): @@ -173,6 +174,7 @@ class GenTable: self.xml_row_tag = xml_row_tag # TEXT parameters self.text_fields_separator = text_fields_separator + self.text_with_titles = text_with_titles # if preferences: self.preferences = preferences @@ -265,8 +267,7 @@ class GenTable: def get_titles_list(self): "list of titles" - l = [] - return l + [self.titles.get(cid, "") for cid in self.columns_ids] + return [self.titles.get(cid, "") for cid in self.columns_ids] def gen(self, format="html", columns_ids=None): """Build representation of the table in the specified format. @@ -479,10 +480,14 @@ class GenTable: def text(self): "raw text representation of the table" + if self.text_with_titles: + headline = [self.get_titles_list()] + else: + headline = [] return "\n".join( [ self.text_fields_separator.join([x for x in line]) - for line in self.get_data_list() + for line in headline + self.get_data_list() ] ) diff --git a/sco_groups_view.py b/sco_groups_view.py index fa806f089c..6336d9703c 100644 --- a/sco_groups_view.py +++ b/sco_groups_view.py @@ -578,6 +578,7 @@ def groups_table( else: filename = "etudiants_%s" % groups_infos.groups_filename + prefs = context.get_preferences(groups_infos.formsemestre_id) tab = GenTable( rows=groups_infos.members, columns_ids=columns_ids, @@ -591,8 +592,9 @@ def groups_table( html_class="table_leftalign table_listegroupe", xml_outer_tag="group_list", xml_row_tag="etud", - text_fields_separator=",", # pour csvmoodle - preferences=context.get_preferences(groups_infos.formsemestre_id), + text_fields_separator=prefs["moodle_csv_separator"], + text_with_titles=prefs["moodle_csv_with_headerline"], + preferences=prefs, ) # if format == "html": @@ -672,7 +674,10 @@ def groups_table( % (tab.base_url,), '
  • Fichier CSV pour Moodle (groupe sélectionné)
  • ' % (tab.base_url,), - '
  • Fichier CSV pour Moodle (tous les groupes)
  • ' + """
  • + Fichier CSV pour Moodle (tous les groupes) + (voir le paramétrage pour modifier le format des fichiers Moodle exportés) +
  • """ % groups_infos.formsemestre_id, ] ) @@ -959,6 +964,7 @@ def export_groups_as_moodle_csv(context, formsemestre_id=None, REQUEST=None): sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) moodle_sem_name = sem["session_id"] + columns_ids = ("email", "semestre_groupe") T = [] for partition_id in partitions_etud_groups: partition = sco_groups.get_partition(context, partition_id) @@ -973,11 +979,14 @@ def export_groups_as_moodle_csv(context, formsemestre_id=None, REQUEST=None): elts.append(group_name) T.append({"email": etud["email"], "semestre_groupe": "-".join(elts)}) # Make table + prefs = context.get_preferences(formsemestre_id) tab = GenTable( rows=T, columns_ids=("email", "semestre_groupe"), filename=moodle_sem_name + "-moodle", - text_fields_separator=",", - preferences=context.get_preferences(formsemestre_id), + titles={x: x for x in columns_ids}, + text_fields_separator=prefs["moodle_csv_separator"], + text_with_titles=prefs["moodle_csv_with_headerline"], + preferences=prefs, ) return tab.make_page(context, format="csv", REQUEST=REQUEST) diff --git a/sco_preferences.py b/sco_preferences.py index 3b356cf5d9..8886969382 100644 --- a/sco_preferences.py +++ b/sco_preferences.py @@ -175,7 +175,7 @@ PREF_CATEGORIES = ( ), ( "feuilles", - {"title": "Mise en forme des feuilles (Absences, Trombinoscopes, ...)"}, + {"title": "Mise en forme des feuilles (Absences, Trombinoscopes, Moodle, ...)"}, ), ("pe", {"title": "Avis de poursuites d'études"}), ("edt", {"title": "Connexion avec le logiciel d'emplois du temps"}), @@ -1644,6 +1644,28 @@ Année scolaire: %(anneescolaire)s "only_global": True, }, ), + # Exports pour Moodle: + ( + "moodle_csv_with_headerline", + { + "initvalue": 0, + "title": "Inclure une ligne d'en-têtes dans les fichiers CSV pour Moodle", + "input_type": "boolcheckbox", + "labels": ["non", "oui"], + "only_global": True, + "category": "feuilles", + }, + ), + ( + "moodle_csv_separator", + { + "initvalue": ",", + "title": "séparateur de colonnes dans les fichiers CSV pour Moodle", + "size": 2, + "only_global": True, + "category": "feuilles", + }, + ), # Experimental: avis poursuite d'études ( "NomResponsablePE",