diff --git a/VERSION.py b/VERSION.py index b87455b086..0a182ca310 100644 --- a/VERSION.py +++ b/VERSION.py @@ -8,13 +8,14 @@ SCONAME = "ScoDoc" SCONEWS = """
- Saisie des absences %s %s, - semaine du lundi %s- - - -- Aucun étudiant inscrit ! | %s%s | '
% (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)"'
@@ -1122,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/ZScolar.py b/ZScolar.py
index 8e3e76d9df..5a6a4bc5a5 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/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/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" 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-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/config/postupgrade.py b/config/postupgrade.py old mode 100755 new mode 100644 index 1137357ac8..f8a7d313f9 --- a/config/postupgrade.py +++ b/config/postupgrade.py @@ -1,4 +1,5 @@ #!/opt/zope213/bin/python +# -*- coding: utf-8 -*- """ ScoDoc post-upgrade script. @@ -11,21 +12,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 +35,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 +43,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 +52,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 fois, 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 98a5be9594..5141d00037 --- 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() @@ -24,8 +30,10 @@ 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", "") + def get_dept_cnx_str(dept): "db cnx string for dept" f = os.path.join(SCODOC_VAR_DIR, "config", "depts", dept + ".cfg") diff --git a/config/upgrade.sh b/config/upgrade.sh old mode 100755 new mode 100644 index 578f9d2112..4f824165b1 --- a/config/upgrade.sh +++ b/config/upgrade.sh @@ -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 @@ -132,6 +133,10 @@ then chmod 600 "$LOCAL_CONFIG_FILENAME" fi +# 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" #' 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/gen_tables.py b/gen_tables.py index fbd8309c7d..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() ] ) @@ -534,14 +539,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/html_sidebar.py b/html_sidebar.py index e9e022fcb9..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): @@ -88,8 +87,8 @@ def sidebar(context, REQUEST=None): H.append( """ Chercher étudiant:
- |
- 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:
++ 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).
@@ -1024,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_find_etud.py b/sco_find_etud.py index 0af146e217..7038a16cef 100644 --- a/sco_find_etud.py +++ b/sco_find_etud.py @@ -58,7 +58,7 @@ def form_search_etud( 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() @@ -268,14 +231,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 ] @@ -294,7 +257,7 @@ def form_search_etud_in_accessible_depts(context, REQUEST): return "" return """