forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into ScoDoc8
Update to rel. 1997.
This commit is contained in:
commit
e61a9752d3
@ -8,13 +8,14 @@ SCONAME = "ScoDoc"
|
||||
SCONEWS = """
|
||||
<h4>Année 2021</h4>
|
||||
<ul>
|
||||
<li>Évaluations de type "deuxième session"</li>
|
||||
<li>Gestion du genre neutre (pas d'affichage de la civilité)</li>
|
||||
<li>Diverses corrections (PV de jurys, ...)</li>
|
||||
<li>Modernisation du code Python</li>
|
||||
</ul>
|
||||
<h4>Année 2020</h4>
|
||||
<ul>
|
||||
<li>Corrections d'erreurs, améliorations saise absences< et affichage bulletins</li>
|
||||
<li>Corrections d'erreurs, améliorations saisie absences et affichage bulletins</li>
|
||||
<li>Nouveau site <a href="https://scodoc.org">scodoc.org</a> pour la documentation</li>
|
||||
<li>Enregistrement de semestres extérieurs</li>
|
||||
<li>Améliorations PV de Jury</li>
|
||||
|
115
ZAbsences.py
115
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 = ""
|
||||
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):
|
||||
@ -718,7 +742,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}
|
||||
@ -745,20 +774,41 @@ class ZAbsences(
|
||||
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,
|
||||
),
|
||||
"""<table border="0" cellspacing="16"><tr><td>
|
||||
<h2>Saisie des absences %s %s,
|
||||
<span class="fontred">semaine du lundi %s</span></h2>
|
||||
|
||||
<p><a href="index_html">Annuler</a></p>
|
||||
|
||||
<p>
|
||||
<form action="doSignaleAbsenceGrHebdo" method="post" action="%s">
|
||||
<div>
|
||||
<form id="group_selector" method="get">
|
||||
<input type="hidden" name="formsemestre_id" id="formsemestre_id" value="%s"/>
|
||||
<input type="hidden" name="datelundi" id="datelundi" value="%s"/>
|
||||
<input type="hidden" name="destination" id="destination" value="%s"/>
|
||||
<input type="hidden" name="moduleimpl_id" id="moduleimpl_id_o" value="%s"/>
|
||||
Groupes: %s
|
||||
</form>
|
||||
<form id="abs_form">
|
||||
"""
|
||||
% (gr_tit, sem["titre_num"], datelundi, REQUEST.URL0),
|
||||
% (
|
||||
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 = []
|
||||
@ -797,12 +847,12 @@ class ZAbsences(
|
||||
sel = "selected" # aucun module specifie
|
||||
|
||||
H.append(
|
||||
"""
|
||||
Module concerné par ces absences (optionnel): <select id="moduleimpl_id" name="moduleimpl_id" onchange="document.location='%(url)s&moduleimpl_id='+document.getElementById('moduleimpl_id').value">
|
||||
"""Module concerné:
|
||||
<select id="moduleimpl_id" name="moduleimpl_id" onchange="change_moduleimpl('%(url)s')">
|
||||
<option value="" %(sel)s>non spécifié</option>
|
||||
%(menu_module)s
|
||||
</select>
|
||||
</p>"""
|
||||
</div>"""
|
||||
% {"menu_module": menu_module, "url": base_url, "sel": sel}
|
||||
)
|
||||
|
||||
@ -826,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
|
||||
)
|
||||
@ -934,7 +983,7 @@ class ZAbsences(
|
||||
les <span class="fontred">%s</span></h2>
|
||||
<p>
|
||||
<a href="%s">%s</a>
|
||||
<form action="doSignaleAbsenceGrSemestre" method="post">
|
||||
<form id="abs_form" action="doSignaleAbsenceGrSemestre" method="post">
|
||||
"""
|
||||
% (gr_tit, sem["titre_num"], dayname, url_link_semaines, msg),
|
||||
]
|
||||
@ -1006,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 = [
|
||||
@ -1043,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)
|
||||
@ -1071,6 +1122,8 @@ class ZAbsences(
|
||||
'<tr><td><span class="redboldtext">Aucun étudiant inscrit !</span></td></tr>'
|
||||
)
|
||||
i = 1
|
||||
cnx = self.GetDBConnexion()
|
||||
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor)
|
||||
for etud in etuds:
|
||||
i += 1
|
||||
etudid = etud["etudid"]
|
||||
@ -1096,17 +1149,20 @@ class ZAbsences(
|
||||
'<tr class="%s"><td><b class="etudinfo" id="%s"><a class="discretelink" href="ficheEtud?etudid=%s" target="new">%s</a></b>%s</td>'
|
||||
% (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 = ""
|
||||
|
@ -394,6 +394,10 @@ REQUEST.URL0=%s<br/>
|
||||
"""
|
||||
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
|
||||
|
@ -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')),
|
||||
|
@ -9,7 +9,7 @@
|
||||
# E. Viennet, Juin 2008
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
set -eo pipefail
|
||||
|
||||
source config.sh
|
||||
source utils.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"
|
||||
|
64
config/fix_bug70_db.py
Normal file
64
config/fix_bug70_db.py
Normal file
@ -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)
|
@ -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,
|
||||
|
19
config/postupgrade.py
Executable file → Normal file
19
config/postupgrade.py
Executable file → Normal file
@ -1,4 +1,5 @@
|
||||
#!/opt/zope213/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
ScoDoc post-upgrade script.
|
||||
@ -11,15 +12,16 @@ _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
|
||||
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)
|
||||
|
||||
# ---
|
||||
@ -54,6 +56,19 @@ 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...
|
||||
|
||||
# ---
|
||||
|
8
config/scodocutils.py
Normal file → Executable file
8
config/scodocutils.py
Normal file → Executable file
@ -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")
|
||||
|
7
config/upgrade.sh
Executable file → Normal file
7
config/upgrade.sh
Executable file → Normal file
@ -23,6 +23,7 @@ fi
|
||||
|
||||
# Upgrade svn working copy if possible
|
||||
svnver=$(svn --version --quiet)
|
||||
# shellcheck disable=SC2072
|
||||
if [[ ${svnver} > "1.7" ]]
|
||||
then
|
||||
(cd "$SCODOC_DIR"; find . -name .svn -type d -exec dirname {} \; | xargs svn upgrade)
|
||||
@ -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"
|
||||
#'
|
||||
|
6
debug.py
6
debug.py
@ -57,17 +57,19 @@ 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
|
||||
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:
|
||||
if verbose:
|
||||
print("context in dept ", context.DeptId())
|
||||
return context
|
||||
raise ValueError("dep %s not found" % dept)
|
||||
|
@ -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 = []
|
||||
|
@ -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 = [
|
||||
'<a class="scodoc_title" href="about">ScoDoc</a>',
|
||||
'<div id="authuser"><a id="authuserlink" href="%(ScoURL)s/Users/userinfo">%(authuser)s</a><br/><a id="deconnectlink" href="%(ScoURL)s/acl_users/logout">déconnexion</a></div>'
|
||||
@ -50,8 +56,8 @@ def sidebar_common(context, REQUEST=None):
|
||||
context.sidebar_dept(REQUEST),
|
||||
"""<h2 class="insidebar">Scolarité</h2>
|
||||
<a href="%(ScoURL)s" class="sidebar">Semestres</a> <br/>
|
||||
<a href="%(ScoURL)s/Notes" class="sidebar">Programmes</a> <br/>
|
||||
<a href="%(ScoURL)s/Absences" class="sidebar">Absences</a> <br/>
|
||||
<a href="%(NotesURL)s" class="sidebar">Programmes</a> <br/>
|
||||
<a href="%(AbsencesURL)s" class="sidebar">Absences</a> <br/>
|
||||
"""
|
||||
% params,
|
||||
]
|
||||
@ -60,14 +66,7 @@ def sidebar_common(context, REQUEST=None):
|
||||
ScoUsersView, context
|
||||
):
|
||||
H.append(
|
||||
"""<a href="%(ScoURL)s/Users" class="sidebar">Utilisateurs</a> <br/>"""
|
||||
% params
|
||||
)
|
||||
|
||||
if 0: # XXX experimental
|
||||
H.append(
|
||||
"""<a href="%(ScoURL)s/Notes/Services" class="sidebar">Services</a> <br/>"""
|
||||
% params
|
||||
"""<a href="%(UsersURL)s" class="sidebar">Utilisateurs</a> <br/>""" % params
|
||||
)
|
||||
|
||||
if authuser.has_permission(ScoChangePreferences, context):
|
||||
@ -88,8 +87,8 @@ def sidebar(context, REQUEST=None):
|
||||
|
||||
H.append(
|
||||
"""<div class="box-chercheetud">Chercher étudiant:<br/>
|
||||
<form id="form-chercheetud" action="%(ScoURL)s/search_etud_in_dept">
|
||||
<div><input type="text" size="12" id="in-expnom" name="expnom"></input></div>
|
||||
<form method="get" id="form-chercheetud" action="%(ScoURL)s/search_etud_in_dept">
|
||||
<div><input type="text" size="12" id="in-expnom" name="expnom" spellcheck="false"></input></div>
|
||||
</form></div>
|
||||
<div class="etud-insidebar">
|
||||
"""
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
|
@ -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"]
|
||||
+ (
|
||||
|
@ -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)
|
||||
|
@ -546,6 +546,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"):
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 deuxième session ?
|
||||
if eval_rattr:
|
||||
if eval_rattr["notes"].has_key(etudid):
|
||||
note = eval_rattr["notes"][etudid]["value"]
|
||||
@ -353,9 +357,15 @@ def do_moduleimpl_moyennes(context, nt, mod):
|
||||
R[etudid] = note
|
||||
else:
|
||||
note_sur_20 = note * 20.0 / eval_rattr["note_max"]
|
||||
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
|
||||
|
||||
|
19
sco_dept.py
19
sco_dept.py
@ -129,11 +129,12 @@ def index_html(context, REQUEST=None, showcodes=0, showsemtable=0):
|
||||
)
|
||||
|
||||
H.append(
|
||||
"""<p><form action="Notes/view_formsemestre_by_etape">
|
||||
Chercher étape courante: <input name="etape_apo" type="text" size="8"></input>
|
||||
"""<p><form action="%s/view_formsemestre_by_etape">
|
||||
Chercher étape courante: <input name="etape_apo" type="text" size="8" spellcheck="false"></input>
|
||||
</form
|
||||
</p>
|
||||
"""
|
||||
% context.NotesURL()
|
||||
)
|
||||
#
|
||||
authuser = REQUEST.AUTHENTICATED_USER
|
||||
@ -155,9 +156,10 @@ Chercher étape courante: <input name="etape_apo" type="text" size="8"></input>
|
||||
"""<hr>
|
||||
<h3>Exports Apogée</h3>
|
||||
<ul>
|
||||
<li><a class="stdlink" href="Notes/semset_page">Années scolaires / exports Apogée</a></li>
|
||||
<li><a class="stdlink" href="%s/semset_page">Années scolaires / exports Apogée</a></li>
|
||||
</ul>
|
||||
"""
|
||||
% context.NotesURL()
|
||||
)
|
||||
#
|
||||
H.append(
|
||||
@ -175,9 +177,9 @@ Chercher étape courante: <input name="etape_apo" type="text" size="8"></input>
|
||||
def _sem_table(context, sems):
|
||||
"""Affiche liste des semestres, utilisée pour semestres en cours"""
|
||||
tmpl = """<tr class="%(trclass)s">%(tmpcode)s
|
||||
<td class="semicon">%(lockimg)s <a href="Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
|
||||
<td class="semicon">%(lockimg)s <a href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s#groupes">%(groupicon)s</a></td>
|
||||
<td class="datesem">%(mois_debut)s</td><td class="datesem"><a title="%(session_id)s">-</a> %(mois_fin)s</td>
|
||||
<td><a class="stdlink" href="Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
<td><a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
<span class="respsem">(%(responsable_name)s)</span>
|
||||
</td>
|
||||
</tr>
|
||||
@ -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("</table>")
|
||||
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"] = '<a title="%(session_id)s"></a> %(anneescolaire)s' % sem
|
||||
sem["_dash_mois_fin_class"] = "datesem"
|
||||
sem["titre_resp"] = (
|
||||
"""<a class="stdlink" href="Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
"""<a class="stdlink" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre_num)s</a>
|
||||
<span class="respsem">(%(responsable_name)s)</span>"""
|
||||
% sem
|
||||
)
|
||||
|
@ -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,
|
||||
@ -133,11 +135,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
|
||||
@ -232,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
|
||||
@ -245,6 +243,9 @@ def do_evaluation_etat(
|
||||
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}
|
||||
@ -268,11 +269,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 {
|
||||
@ -896,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).
|
||||
</p><p class="help">
|
||||
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: </p>
|
||||
<ul>
|
||||
<li>les notes d'une évaluation de "rattrapage" remplaceront les moyennes du module
|
||||
<em>si elles sont meilleures que celles calculées</em>.</li>
|
||||
<li>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.</li>
|
||||
</ul>
|
||||
<p class="help">
|
||||
Dans ces deux cas, le coefficient est ignoré, et toutes les notes n'ont
|
||||
pas besoin d'être rentrées.
|
||||
</p>
|
||||
<p class="help">
|
||||
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).
|
||||
</p>
|
||||
@ -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)",
|
||||
),
|
||||
},
|
||||
),
|
||||
]
|
||||
|
109
sco_find_etud.py
109
sco_find_etud.py
@ -58,7 +58,7 @@ def form_search_etud(
|
||||
H.append(
|
||||
"""<form action="search_etud_in_dept" method="POST">
|
||||
<b>%s</b>
|
||||
<input type="text" name="expnom" width=12 value="">
|
||||
<input type="text" name="expnom" width="12" spellcheck="false" value="">
|
||||
<input type="submit" value="Chercher">
|
||||
<br/>(entrer une partie du nom)
|
||||
"""
|
||||
@ -96,71 +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
|
||||
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("<h2>%s</h2>" % title)
|
||||
|
||||
dest_url = "ficheEtud"
|
||||
if len(expnom) > 1:
|
||||
etuds = context.getEtudInfo(filled=1, etudid=expnom, REQUEST=REQUEST)
|
||||
if len(etuds) != 1:
|
||||
if scu.is_valid_code_nip(expnom):
|
||||
etuds = search_etuds_infos(context, code_nip=expnom, REQUEST=REQUEST)
|
||||
elif expnom:
|
||||
else:
|
||||
etuds = search_etuds_infos(context, expnom=expnom, REQUEST=REQUEST)
|
||||
else:
|
||||
etuds = []
|
||||
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)
|
||||
|
||||
if len(etuds) > 0:
|
||||
# Choix dans la liste des résultats:
|
||||
H.append(
|
||||
H = [
|
||||
context.sco_header(
|
||||
page_title="Recherche d'un étudiant",
|
||||
no_side_bar=True,
|
||||
init_qtip=True,
|
||||
javascripts=["js/etud_info.js"],
|
||||
REQUEST=REQUEST,
|
||||
),
|
||||
"""<h2>%d résultats pour "%s": choisissez un étudiant:</h2>"""
|
||||
% (len(etuds), expnom)
|
||||
)
|
||||
H.append(
|
||||
% (len(etuds), expnom),
|
||||
form_search_etud(
|
||||
context,
|
||||
dest_url=dest_url,
|
||||
parameters=parameters,
|
||||
parameters_keys=parameters_keys,
|
||||
REQUEST=REQUEST,
|
||||
title="Autre recherche",
|
||||
)
|
||||
)
|
||||
|
||||
),
|
||||
]
|
||||
if len(etuds) > 0:
|
||||
# Choix dans la liste des résultats:
|
||||
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"])
|
||||
@ -185,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('<h2 style="color: red;">Aucun résultat pour "%s".</h2>' % expnom)
|
||||
add_headers = True
|
||||
no_side_bar = False
|
||||
H.append(
|
||||
"""<p class="help">La recherche porte sur tout ou partie du NOM ou du NIP de l'étudiant</p>"""
|
||||
)
|
||||
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 """<form action="table_etud_in_accessible_depts" method="POST">
|
||||
<b>Chercher étudiant:</b>
|
||||
<input type="text" name="expnom" width=12 value="">
|
||||
<input type="text" name="expnom" width="12" spellcheck="false" value="">
|
||||
<input type="submit" value="Chercher">
|
||||
<br/>(entrer une partie du nom ou le code NIP, cherche dans tous les départements autorisés)
|
||||
"""
|
||||
|
@ -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 !")
|
||||
|
@ -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),
|
||||
|
@ -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"
|
||||
@ -1195,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:
|
||||
@ -1261,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:
|
||||
|
@ -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":
|
||||
|
@ -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)
|
||||
)
|
||||
|
||||
|
@ -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,),
|
||||
'<li><a class="stdlink" href="%s&format=moodlecsv">Fichier CSV pour Moodle (groupe sélectionné)</a></li>'
|
||||
% (tab.base_url,),
|
||||
'<li><a class="stdlink" href="export_groups_as_moodle_csv?formsemestre_id=%s">Fichier CSV pour Moodle (tous les groupes)</a></li>'
|
||||
"""<li>
|
||||
<a class="stdlink" href="export_groups_as_moodle_csv?formsemestre_id=%s">Fichier CSV pour Moodle (tous les groupes)</a>
|
||||
<em>(voir le paramétrage pour modifier le format des fichiers Moodle exportés)</em>
|
||||
</li>"""
|
||||
% groups_infos.formsemestre_id,
|
||||
]
|
||||
)
|
||||
@ -784,9 +789,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 +936,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('<form action="Absences/SignaleAbsenceGrHebdo" method="get">')
|
||||
FA.append('<input type="hidden" name="datelundi" value="%s"/>' % datelundi)
|
||||
FA.append('<input type="hidden" name="moduleimpl_id" value="%s"/>' % moduleimpl_id)
|
||||
@ -955,12 +958,13 @@ 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)
|
||||
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)
|
||||
@ -975,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)
|
||||
|
@ -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
|
||||
@ -259,9 +260,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(
|
||||
'<span class="moduleimpl_abs_link"><a class="stdlink" href="Absences/SignaleAbsenceGrHebdo?formsemestre_id=%s&moduleimpl_id=%s&datelundi=%s">Saisie Absences hebdo.</a></span>'
|
||||
% (formsemestre_id, moduleimpl_id, datelundi)
|
||||
'<span class="moduleimpl_abs_link"><a class="stdlink" href="Absences/SignaleAbsenceGrHebdo?formsemestre_id=%s&moduleimpl_id=%s&datelundi=%s&group_ids=%s">Saisie Absences hebdo.</a></span>'
|
||||
% (formsemestre_id, moduleimpl_id, datelundi, group_id)
|
||||
)
|
||||
|
||||
H.append("</td></tr></table>")
|
||||
@ -340,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"
|
||||
@ -360,7 +362,13 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No
|
||||
)
|
||||
H.append(" <em>%(description)s</em>" % eval)
|
||||
if eval["evaluation_type"] == EVALUATION_RATTRAPAGE:
|
||||
H.append("""<span class="mievr_rattr">rattrapage</span>""")
|
||||
H.append(
|
||||
"""<span class="mievr_rattr" title="remplace si meilleure note">rattrapage</span>"""
|
||||
)
|
||||
elif eval["evaluation_type"] == EVALUATION_SESSION2:
|
||||
H.append(
|
||||
"""<span class="mievr_rattr" title="remplace autres notes">session 2</span>"""
|
||||
)
|
||||
if etat["last_modif"]:
|
||||
H.append(
|
||||
"""<span class="mievr_lastmodif">(dernière modif le %s)</span>"""
|
||||
|
@ -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:
|
||||
|
@ -168,7 +168,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"}),
|
||||
@ -1637,6 +1637,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",
|
||||
@ -2040,7 +2062,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)
|
||||
|
@ -824,10 +824,10 @@ def _pvjury_pdf_type(
|
||||
# Signature du directeur
|
||||
objects += sco_pdf.makeParas(
|
||||
"""<para spaceBefore="10mm" align="right">
|
||||
Le %s, %s</para>"""
|
||||
%s, %s</para>"""
|
||||
% (
|
||||
context.get_preference("DirectorTitle", formsemestre_id) or "",
|
||||
context.get_preference("DirectorName", formsemestre_id) or "",
|
||||
context.get_preference("DirectorTitle", formsemestre_id) or "",
|
||||
),
|
||||
style,
|
||||
)
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
@ -758,7 +758,9 @@ def make_formsemestre_recapcomplet(
|
||||
H.append("</table>")
|
||||
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)
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -11,12 +11,21 @@ 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
|
||||
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) # pylint: disable=undefined-variable
|
||||
G = sco_fake_gen.ScoFake(context.Notes)
|
||||
G.verbose = False
|
||||
|
||||
# --- Création d'étudiants
|
||||
@ -61,12 +70,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"]
|
||||
|
||||
@ -91,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"]
|
||||
)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 é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
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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[0];
|
||||
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);
|
||||
}
|
||||
@ -42,3 +41,7 @@ function ajaxFunction(mod, etudid, dat){
|
||||
}
|
||||
}
|
||||
|
||||
// -----
|
||||
function change_moduleimpl(url) {
|
||||
document.location = url + '&moduleimpl_id=' + document.getElementById('moduleimpl_id').value;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ $(function() {
|
||||
position: { collision: 'flip' }, // automatic menu position up/down
|
||||
source: "search_etud_by_name",
|
||||
select: function (event, ui) {
|
||||
$("#in-expnom").val(ui.item.value);
|
||||
$("#form-chercheetud").submit();
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user