Fix creation/deletion scripts + more unit tests

This commit is contained in:
Emmanuel Viennet 2021-07-30 10:36:30 +03:00
parent 31288efb73
commit b0a77fba66
25 changed files with 280 additions and 759 deletions

View File

@ -95,7 +95,7 @@ Lancer le script:
su postgres su postgres
cd /opt/scodoc/tools cd /opt/scodoc/tools
./create_database.sh ./create_users_database.sh
Ce script crée une base nommée `SCO8USERS`, appartenant à l'utilisateur (role) postgres `scodoc`. Ce script crée une base nommée `SCO8USERS`, appartenant à l'utilisateur (role) postgres `scodoc`.
Cet utilisateur est automatiquement créé si nécessaire. Cet utilisateur est automatiquement créé si nécessaire.
@ -146,6 +146,21 @@ de votre installation ScoDoc 7 pour passer à ScoDoc 8 (*ne pas utiliser en prod
cd /opt/scodoc/tools cd /opt/scodoc/tools
./migrate_from_scodoc7.sh ./migrate_from_scodoc7.sh
## Création d'un département
sudo su
cd /opt/scodoc
source venv/bin/activate
flask sco-create-dept DEPT
`DEPT` est le nom du département (un acronyme en majuscule, comme "RT", "GEA", ...).
### Suppression d'un département
sudo su
cd /opt/scodoc
source venv/bin/activate
flask sco-delete-dept DEPT
## Lancement serveur (développement, sur VM Linux) ## Lancement serveur (développement, sur VM Linux)

View File

@ -120,7 +120,7 @@ def DBInsertDict(cnx, table, vals, commit=0, convert_empty_to_nulls=1):
cnx.commit() # get rid of this transaction cnx.commit() # get rid of this transaction
raise # and re-raise exception raise # and re-raise exception
if commit: if commit:
log("DBInsertDict: commit (requested)") # log("DBInsertDict: commit (requested)")
cnx.commit() cnx.commit()
return oid return oid

View File

@ -695,7 +695,7 @@ def do_import_etud_admission(
"codelycee": get_opt_str(etud, "lycee"), "codelycee": get_opt_str(etud, "lycee"),
"boursier": get_opt_str(etud, "bourse"), "boursier": get_opt_str(etud, "bourse"),
} }
log("do_import_etud_admission: etud=%s" % pprint.pformat(etud)) # log("do_import_etud_admission: etud=%s" % pprint.pformat(etud))
al = sco_etud.admission_list(cnx, args={"etudid": etudid}) al = sco_etud.admission_list(cnx, args={"etudid": etudid})
if not al: if not al:
sco_etud.admission_create(cnx, args) # -> adm_id sco_etud.admission_create(cnx, args) # -> adm_id

View File

@ -133,7 +133,7 @@ def user_password(username, password=None): # user-password
@app.cli.command() @app.cli.command()
@click.argument("dept") @click.argument("dept")
def sco_delete_dept(dept): def sco_delete_dept(dept): # sco-delete-dept
"Delete existing departement" "Delete existing departement"
if os.getuid() != 0: if os.getuid() != 0:
sys.stderr.write("sco_delete_dept: must be run by root\n") sys.stderr.write("sco_delete_dept: must be run by root\n")
@ -146,13 +146,13 @@ def sco_delete_dept(dept):
@app.cli.command() @app.cli.command()
@click.argument("dept") @click.argument("dept")
def sco_create_dept(dept): def sco_create_dept(dept): # sco-create-dept
"Create new departement" "Create new departement"
if os.getuid() != 0: if os.getuid() != 0:
sys.stderr.write("sco_create_dept: must be run by root\n") sys.stderr.write("sco_create_dept: must be run by root\n")
return 1 return 1
if os.system('cd tools && ./create_dept.sh -n "{}"'.format(dept)): if os.system(f'cd tools && ./create_dept.sh -n "{dept}"'):
sys.stderr.write("error deleting dept " + dept) sys.stderr.write(f"error creating dept {dept}\n")
return 1 return 1
return 0 return 0

View File

@ -15,7 +15,7 @@ import sys
import random import random
import psycopg2 import psycopg2
from gen_nomprenoms import nomprenom from .gen_nomprenoms import nomprenom
def usage(): def usage():

View File

@ -1,79 +0,0 @@
#!/bin/bash
# Lancement d'un python scodoc interactif
# dans l'environnement d'un département
# et avec chargement des scripts indiqués
# via from ... import *
#
# Si -r est utilisé, veiller à créer au préalable
# le département via l'interface web (Zope)
usage() {
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
}
set -euo pipefail
cd /opt/scodoc/Products/ScoDoc || exit 2
source config/config.sh
source config/utils.sh
RECREATE_DEPT=0
PYTHON_INTERACTIVE="-i"
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
done
DEPT="$1"
shift
if [ "$RECREATE_DEPT" = 1 ]
then
cfg_pathname="${SCODOC_VAR_DIR}/config/depts/$DEPT".cfg
if [ -e "$cfg_pathname" ]
then
(cd config || terminate "no config directory"; ./delete_dept.sh -n "$DEPT") || terminate "error deleting dept $DEPT"
fi
(cd config || terminate "no config directory"; ./create_dept.sh -n "$DEPT") || terminate "error creating dept $DEPT"
# 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""', verbose=False);"
for f in "$@"
do
cmd="${cmd}exec(open(\"${f}\").read());"
done
if [ -z "$PYTHON_INTERACTIVE" ]
then
/opt/zope213/bin/python -c "$cmd"
else
/opt/zope213/bin/python "$PYTHON_INTERACTIVE" -c "$cmd"
fi

View File

@ -1,38 +0,0 @@
# Tests avec splinter (expérimental)
<http://splinter.cobrateam.info/docs/tutorial.html>
## Installation de Splinter
```
apt-get install python-dev
apt-get install libxslt-dev
apt-get install libxml2-dev
apt-get install python-lxml python-cssselect
/opt/zope213/bin/easy_install zope.testbrowser
/opt/zope213/bin/easy_install cssselect
/opt/zope213/bin/easy_install splinter
```
J'ai du hacker `_mechanize.py`, ligne 218
```
vi +218 /opt/zope213/lib/python2.7/site-packages/mechanize-0.2.5-py2.7.egg/mechanize/_mechanize.py
url = _rfc3986.urljoin(self._response.geturl()+'/', url)
```
(ajouter le + '/')
### Essais:
/opt/zope213/bin/python common.py
ne doit pas déclencher d'erreur.

View File

@ -1,49 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Partie commune:
se connecte et accede a la page d'accueil du premier departement
"""
from splinter import Browser
import re, sys, time
import urlparse
import pdb
from optparse import OptionParser
from conn_info import *
parser = OptionParser()
parser.add_option("-d", "--dept", dest="dept_index", default=0, help="indice du departement")
options, args = parser.parse_args()
dept_index = int(options.dept_index)
t0 = time.time()
browser = Browser('zope.testbrowser')
browser._browser.mech_browser.set_handle_robots(False) # must ignore ScoDoc robots.txt
browser.visit(SCODOC)
print 'Start: title:', browser.title
print 'URL: ', browser.url
# print browser.html
links = browser.find_link_by_partial_text('Scolarit')
print '%d departements' % len(links)
links[dept_index].click() # va sur le premier departement
# ---- Formulaire authentification
print 'Authentification: ', browser.url
browser.fill('__ac_name', USER)
browser.fill('__ac_password', PASSWD)
button = browser.find_by_id('submit')
button[0].click()
# ---- Page accueil Dept
print browser.url
links = browser.find_link_by_partial_text('DUT')
links[0].click()
print 'Starting test from %s' % browser.url
print browser.title

View File

@ -1,10 +0,0 @@
"""Put here the informations neeede to connect to your development test user
"""
#SCODOC='https://scodoc.example.com/'
SCODOC='https://scodoc.viennet.net/'
USER = 'tester'
#PASSWD = 'XXXXXXXXXXXX'
PASSWD = '67un^:653'

View File

@ -1,51 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Essais de base:
se connecte, accede a un semestre puis a un module,
et modifie les notes existantes dans la premiere evaluation
"""
from common import *
# ici on est sur la page d'accueil du departement !
links = browser.find_link_by_partial_text('DUT informatique en FI')
links[0].click()
# ---- Tableau bord semestre
print browser.url
# va dans module AP2 saisir des notes (dans la p1ere evaluation):
browser.find_link_by_partial_text('AP1').first.click()
browser.find_link_by_partial_text('Saisir notes').first.click()
# ---- Ici c'est complique car le bouton submit est disabled
# on construit l'url a la main:
url = browser.find_by_id('gr')[0]["action"]
evaluation_id = browser.find_by_name('evaluation_id').value
group_id = re.search( r'value="(.*?)".*?tous', browser.html ).group(1)
dest = urlparse.urljoin(url, 'notes_evaluation_formnotes?evaluation_id='+evaluation_id+'&group_ids:list='+group_id+'&note_method=form')
browser.visit(dest)
# ---- Change une note:
# browser.fill('note_EID3835', '15')
etudids = re.findall( r'name="note_(.*?)"', browser.html )[1:]
note_max = float(re.search( r'notes sur ([0-9]+?)</span>\)', browser.html ).group(1))
for etudid in etudids:
# essaie d'ajouter 1 à la note !
old_val = browser.find_by_name('note_%s' % etudid).value
try:
val = min(float(old_val) + 1, note_max)
browser.fill('note_%s'%etudid, str(val))
print etudid, old_val, '->', val
except:
pass
# ... et met la derniere au max (pour tester)
browser.fill('note_%s'%etudids[-1], str(note_max))
print etudids[-1], '->', note_max
# ---- Validation formulaire saisie notes:
browser.find_by_id('tf_submit').click()
browser.find_by_id('tf_submit').click()

View File

@ -1,66 +0,0 @@
#!/opt/zope213/bin/python
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Essais de changements intensifs des notes
(pour faire des tests en parallele)
se connecte, accede a un semestre puis a un module,
et modifie les notes existantes dans la premiere evaluation
ajoute puis soustrait 1 aux notes valides, N fois
"""
import time
from common import *
# -> ici on est sur la page d'accueil du departement !
links = browser.find_link_by_partial_text('DUT')
links[0].click() # va sur le 1er semestre de DUT trouve
# ---- Tableau bord semestre
print browser.url
# va dans module M1101 saisir des notes (dans la p1ere evaluation):
browser.find_link_by_partial_text('M1101').first.click()
browser.find_link_by_partial_text('Saisir notes').first.click()
# ---- Ici c'est complique car le bouton submit est disabled
# on construit l'url a la main:
url = browser.find_by_id('gr')[0]["action"]
evaluation_id = browser.find_by_name('evaluation_id').value
group_id = re.search( r'value="(.*?)".*?tous', browser.html ).group(1)
url_form = urlparse.urljoin(url, 'notes_evaluation_formnotes?evaluation_id='+evaluation_id+'&group_ids:list='+group_id+'&note_method=form')
# ---- Ajoute une constante aux notes valides:
# le browser doit etre sur le formulaire saisie note
def add_to_notes(increment):
etudids = re.findall( r'name="note_(.*?)"', browser.html )[1:]
note_max = float(re.search( r'notes sur ([0-9]+?)</span>\)', browser.html ).group(1))
print 'add_to_notes: %d etudiants' % len(etudids)
for etudid in etudids:
# essaie d'ajouter 1 a la note !
old_val = browser.find_by_name('note_%s' % etudid).value
try:
val = max(0,min(float(old_val) + increment, note_max))
browser.fill('note_%s'%etudid, str(val))
print etudid, old_val, '->', val
except:
pass
# ---- Validation formulaire saisie notes:
browser.find_by_id('tf_submit').click()
browser.find_by_id('tf_submit').click()
for i in range(10):
browser.visit(url_form) # va sur form saisie notes
add_to_notes(1)
#time.sleep(1)
browser.visit(url_form) # va sur form saisie notes
add_to_notes(-1)
#time.sleep(1)
t1 = time.time()
print 'done in %gs' % (t1-t0)

View File

@ -1,44 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Modification decision de jury
"""
from common import *
# -> ici on est sur la page d'accueil du departement !
DeptURL = browser.url
# Cherche un formsemestre_id:
links = browser.find_link_by_partial_text('DUT')
u = links[0]['href']
formsemestre_id = re.search( r'formsemestre_id=(SEM[0-9]*)', u ).group(1)
# Cherche les etudids
browser.visit( urlparse.urljoin(DeptURL, 'formsemestre_recapcomplet?modejury=1&hidemodules=1&formsemestre_id=' + formsemestre_id) )
#u = browser.find_link_by_partial_href('formsemestre_bulletinetud')[0]['href']
#etudid = re.search( r'etudid=([A-Za-z0-9]*)', u ).group(1)
L = browser.find_link_by_partial_href('formsemestre_bulletinetud')
etudids = [ re.search(r'etudid=([A-Za-z0-9_]*)', x['href']).group(1) for x in L ]
def suppress_then_set( etudid, formsemestre_id, code='ADM' ):
"""Supprime decision de jury pour cet étudiant dans ce semestre
puis saisie de la decision (manuelle) indiquée par code
"""
# Suppression décision existante
browser.visit( urlparse.urljoin(DeptURL, 'formsemestre_validation_suppress_etud?etudid=%s&formsemestre_id=%s&dialog_confirmed=1' % (etudid, formsemestre_id)))
# Saisie décision
browser.visit( urlparse.urljoin(DeptURL, 'formsemestre_validation_etud_form?etudid=%s&formsemestre_id=%s' % (etudid, formsemestre_id)))
browser.fill('code_etat', [code])
browser.find_by_name('formvalidmanu_submit').first.click()
# pas de verification de la page résultat
# Change decisions de jury de tous les étudiants:
for etudid in etudids:
print 'decision pour %s' % etudid
suppress_then_set( etudid, formsemestre_id, code='ADM')
t1 = time.time()
print '%d etudiants traites en %gs' % (len(etudids),t1-t0)

View File

@ -1,45 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""
Enregistre les moyennes générales de tous les étudiants de tous les
semestres.
A utiliser avec debug.py (côté serveur).
"""
from __future__ import print_function
from debug import go_dept
import time
from app.scodoc import sco_cache
DeptName = "CJ"
context = go_dept(app, DeptName)
sems = context.Notes.formsemestre_list()
print("%d semestres" % len(sems))
L = []
n = 0
for sem in sems:
formsemestre_id = sem["formsemestre_id"]
nt = sco_cache.NotesTableCache.get(formsemestre_id)
etudids = nt.get_etudids()
use_ue_coef = sco_preferences.get_preference("use_ue_coefs", formsemestre_id)
n += 1
print("%d %s (%d) use_ue_coef=%s" % (n, formsemestre_id, len(etudids), use_ue_coef))
for etudid in etudids:
mg = nt.get_etud_moy_gen(etudid)
L.append((formsemestre_id, str(use_ue_coef), etudid, str(mg)))
print("Done: %s moys computed" % len(L))
filename = "/opt/tests/%s-%s" % (DeptName, time.strftime("%Y-%m-%dT%H:%M:%S"))
print("Writing file '%s'..." % filename)
f = open(filename, "w")
for l in L:
f.write("\t".join(l) + "\n")
f.close()

View File

@ -1,64 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Petits essais sur les fichiers CSV Apogée
Utiliser avec
/opt/scodoc/bin/zopectl debug
"""
from __future__ import print_function
from debug import *
import sco_apogee_csv
import sco_apogee_compare
#data = open('/opt/scodoc/var/scodoc/archives/apo_csv/RT/2018-2/2019-09-23-15-46-40/V2RT2!116.csv', 'r').read()
#data = open('/opt/scodoc/var/scodoc/archives/apo_csv/RT/2018-1/2019-02-20-11-53-05/V2RT!116.csv', 'r').read()
data = open('/tmp/V2RT116.csv', 'r').read()
A = sco_apogee_csv.ApoData(data)
data = open('/tmp/V2RT116-modif.csv', 'r').read()
B = sco_apogee_csv.ApoData(data)
sco_apogee_compare.compare_etuds_res(A, B)
A.col_ids
# -> ['apoL_a01_code', 'apoL_a02_nom', 'apoL_a03_prenom', 'apoL_a04_naissance', 'apoL_c0001', 'apoL_c0002', 'apoL_c0003', 'apoL_c0004']
e = A.etuds[0]
pp(e.cols)
# {'apoL_a01_code': '11809768',
# 'apoL_a02_nom': 'AKYOL',
# 'apoL_a03_prenom': 'OLIVIER',
# 'apoL_a04_naissance': ' 31/01/1999',
# 'apoL_c0001': '',
# 'apoL_c0002': '',
# ... }
A.apo_elts.keys()
# ['VRTW4', 'VRTW3', 'VRTU42', 'VRTU41', 'VRTU32', ... ]
elt = A.apo_elts['VRT3101']
elt.code # 'VRT3102'
B = sco_apogee_csv.ApoData( open('/opt/tests/V2RT-modif.csv').read() )
# les colonnes de l'élément
col_ids = [ ec['apoL_a01_code'] for ec in elt.cols ]
e.cols['apoL_c0033']
common_nips = set([e["nip"] for e in A.etuds])
A.etud_by_nip.keys()
B_etud_by_nip = { e["nip"] : e for e in B.etuds }
d = build_etud_res(B.etuds[0], B)

View File

@ -1,46 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Essai export des semestres impairs avec VET seulement pour les diplômés
Utiliser avec
/opt/scodoc/bin/zopectl debug
"""
from __future__ import print_function
from debug import *
import sco_apogee_csv
import sco_parcours_dut
context = go_dept(app, 'RT').Notes
etudid='EID33751'
formsemestre_id='SEM37099'
etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
Se = sco_parcours_dut.SituationEtudParcours(context, etud, formsemestre_id)
print(Se.all_other_validated())
data = open('/opt/scodoc/var/scodoc/archives/apo_csv/RT/2019-1/2020-07-07-17-24-18/V2RT!117.csv', 'r').read()
apo_data = sco_apogee_csv.ApoData(data, periode=1, export_res_etape=False)
apo_data.setup(context)
ix = [ x['nom'] for x in apo_data.etuds ].index('HAMILA')
e = apo_data.etuds[ix]
e.lookup_scodoc(context, apo_data.etape_formsemestre_ids)
e.associate_sco(context, apo_data)
pp({ k : e.new_cols[k] for k in e.new_cols if e.new_cols[k] != '' })

View File

@ -1,181 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Test de base de ScoDoc
Création 10 étudiants, formation, semestre, inscription etudiant, creation 1 evaluation, saisie 10 notes.
Utiliser comme:
flask test-interactive scotests/test_basic.py
"""
import random
from flask import g
from app import decorators
import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error
from app.scodoc import sco_utils as scu
from app.scodoc import sco_abs
from app.scodoc import sco_abs_views
from app.scodoc import sco_bulletins
from app.scodoc import sco_evaluations
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_parcours_dut
from app.scodoc import sco_formsemestre_validation
context = sco_fake_gen.Sco8Context()
g.scodoc_dept = "TEST00"
G = sco_fake_gen.ScoFake(context)
G.verbose = False
# --- Création d'étudiants
etuds = [G.create_etud(code_nip=None) for _ in range(10)]
# --- Création d'une formation
f = G.create_formation(acronyme="")
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
mat = G.create_matiere(ue_id=ue["ue_id"], titre="matière test")
mod = G.create_module(
matiere_id=mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
# --- Mise place d'un semestre
sem = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=1,
date_debut="01/01/2020",
date_fin="30/06/2020",
)
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem["formsemestre_id"],
responsable_id="bach",
)
# --- Inscription des étudiants
for etud in etuds:
G.inscrit_etudiant(sem, etud)
# --- Creation évaluation
e = G.create_evaluation(
moduleimpl_id=mi["moduleimpl_id"],
jour="01/01/2020",
description="evaluation test",
coefficient=1.0,
)
# --- 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, sem["formsemestre_id"], etud["etudid"], REQUEST=REQUEST
)
# Toute les notes sont saisies, donc eval complète
etat = sco_evaluations.do_evaluation_etat(context, 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, e2["evaluation_id"])
assert etat["evalcomplete"] == False
# la première éval est toujours complète:
etat = sco_evaluations.do_evaluation_etat(context, e["evaluation_id"])
assert etat["evalcomplete"]
# Modifie l'évaluation 2 pour "prise en compte immédiate"
e2["publish_incomplete"] = "1"
sco_evaluations.do_evaluation_edit(context, REQUEST, e2)
etat = sco_evaluations.do_evaluation_etat(context, 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, 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"]
_ = sco_abs_views.doSignaleAbsence(
context,
"15/01/2020",
"18/01/2020",
demijournee=2,
etudid=etudid,
REQUEST=REQUEST,
)
_ = sco_abs_views.doJustifAbsence(
context,
"17/01/2020",
"18/01/2020",
demijournee=2,
etudid=etudid,
REQUEST=REQUEST,
)
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
assert nbabs == 6, "incorrect nbabs (%d)" % nbabs
assert nbabsjust == 2, "incorrect nbabsjust (%s)" % nbabsjust
# --- 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,
sem["formsemestre_id"],
etudid,
sco_codes_parcours.AJ,
False,
REQUEST=REQUEST,
)
assert sco_parcours_dut.formsemestre_has_decisions(
context, sem["formsemestre_id"]
), "décisions manquantes"
# Suppression de la décision
sco_formsemestre_validation.formsemestre_validation_suppress_etud(
context, sem["formsemestre_id"], etudid
)
assert not sco_parcours_dut.formsemestre_has_decisions(
context, sem["formsemestre_id"]
), "décisions non effacées"

View File

@ -1,19 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
import sys
from pyExcelerator import *
#UnicodeUtils.DEFAULT_ENCODING = 'utf-8'
wb = Workbook()
title = "Essai"
ws = wb.add_sheet(u"çelé")
ws.write(1,1, "çeci où".decode('utf-8'))
ws.write(2,2, "Hélène".decode('utf-8'))
wb.save("toto.xls")

View File

@ -1,6 +1,7 @@
import pytest import pytest
from flask import g from flask import g
from flask_login import login_user, logout_user, current_user
import app as myapp import app as myapp
from app import db, create_app from app import db, create_app
@ -14,16 +15,25 @@ def test_client():
# Setup # Setup
myapp.Config.TESTING = True myapp.Config.TESTING = True
myapp.Config.SQLALCHEMY_DATABASE_URI = "sqlite://" myapp.Config.SQLALCHEMY_DATABASE_URI = "sqlite://"
myapp.Config.SERVER_NAME = "test.gr"
apptest = create_app() apptest = create_app()
# Run tests: # Run tests:
with apptest.test_client() as client: with apptest.test_client() as client:
with apptest.app_context(): with apptest.app_context():
db.create_all() with apptest.test_request_context():
Role.insert_roles() db.create_all()
g.scodoc_dept = "RT" Role.insert_roles()
g.db_conn = ndb.open_dept_connection() u = User(user_name="admin")
yield client admin_role = Role.query.filter_by(name="Admin").first()
ndb.close_dept_connection() u.add_role(admin_role, "TEST00")
# Teardown: # u.set_password("admin")
db.session.remove() login_user(u)
db.drop_all() # db.session.add(u)
g.scodoc_dept = "RT"
g.db_conn = ndb.open_dept_connection()
yield client
# ndb.close_dept_connection()
# Teardown:
db.session.remove()
db.drop_all()

1
tests/unit/__init__.py Normal file
View File

@ -0,0 +1 @@
# Unit tests

View File

@ -8,7 +8,6 @@ La classe ScoFake offre un ensemble de raccourcis permettant d'écrire
facilement des tests ou de reproduire des bugs. facilement des tests ou de reproduire des bugs.
""" """
from __future__ import print_function
from functools import wraps from functools import wraps
import sys import sys
import string import string
@ -47,7 +46,9 @@ PRENOMS_H = [x.strip() for x in open(DEMO_DIR + "/prenoms-h.txt").readlines()]
PRENOMS_F = [x.strip() for x in open(DEMO_DIR + "/prenoms-f.txt").readlines()] PRENOMS_F = [x.strip() for x in open(DEMO_DIR + "/prenoms-f.txt").readlines()]
PRENOMS_X = [x.strip() for x in open(DEMO_DIR + "/prenoms-x.txt").readlines()] PRENOMS_X = [x.strip() for x in open(DEMO_DIR + "/prenoms-x.txt").readlines()]
# nb: en python2, les chaines ci-dessus sont en utf8 # En ScoDoc 8 le "context" ne sert plus (remplacé par les contextes de Flask)
# Laisse cette globale pendant la transition du code #sco8 #context
context = None
def id_generator(size=6, chars=string.ascii_uppercase + string.digits): def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
@ -58,15 +59,16 @@ def logging_meth(func):
@wraps(func) @wraps(func)
def wrapper_logging_meth(self, *args, **kwargs): def wrapper_logging_meth(self, *args, **kwargs):
r = func(self, *args, **kwargs) r = func(self, *args, **kwargs)
self.log("%s(%s) -> \n%s" % (func.__name__, kwargs, pprint.pformat(r))) # self.log("%s(%s) -> \n%s" % (func.__name__, kwargs, pprint.pformat(r)))
return r return r
return wrapper_logging_meth return wrapper_logging_meth
class ScoFake(object): class ScoFake(object):
def __init__(self, context, verbose=True): """Helper for ScoSoc tests"""
self.context = context
def __init__(self, verbose=True):
self.verbose = verbose self.verbose = verbose
def log(self, msg): def log(self, msg):
@ -127,10 +129,10 @@ class ScoFake(object):
nom = r_nom nom = r_nom
if not prenom: if not prenom:
prenom = r_prenom prenom = r_prenom
etud = sco_etud.create_etud(self.context, cnx, args=locals(), REQUEST=REQUEST) etud = sco_etud.create_etud(context, cnx, args=locals(), REQUEST=REQUEST)
inscription = "2020" # pylint: disable=unused-variable inscription = "2020" # pylint: disable=possibly-unused-variable
sco_synchro_etuds.do_import_etud_admission( sco_synchro_etuds.do_import_etud_admission(
self.context, cnx, etud["etudid"], locals() context, cnx, etud["etudid"], locals()
) )
return etud return etud
@ -147,10 +149,8 @@ class ScoFake(object):
"""Crée une formation""" """Crée une formation"""
if not acronyme: if not acronyme:
acronyme = "TEST" + str(random.randint(100000, 999999)) acronyme = "TEST" + str(random.randint(100000, 999999))
oid = sco_edit_formation.do_formation_create( oid = sco_edit_formation.do_formation_create(context, locals(), REQUEST=REQUEST)
self.context, locals(), REQUEST=REQUEST oids = sco_formations.formation_list(context, formation_id=oid)
)
oids = sco_formations.formation_list(self.context, formation_id=oid)
if not oids: if not oids:
raise ScoValueError("formation not created !") raise ScoValueError("formation not created !")
return oids[0] return oids[0]
@ -171,17 +171,17 @@ class ScoFake(object):
): ):
"""Crée une UE""" """Crée une UE"""
if numero is None: if numero is None:
numero = sco_edit_ue.next_ue_numero(self.context, formation_id, 0) numero = sco_edit_ue.next_ue_numero(context, formation_id, 0)
oid = sco_edit_ue.do_ue_create(self.context, locals(), REQUEST) oid = sco_edit_ue.do_ue_create(context, locals(), REQUEST)
oids = sco_edit_ue.do_ue_list(self.context, args={"ue_id": oid}) oids = sco_edit_ue.do_ue_list(context, args={"ue_id": oid})
if not oids: if not oids:
raise ScoValueError("ue not created !") raise ScoValueError("ue not created !")
return oids[0] return oids[0]
@logging_meth @logging_meth
def create_matiere(self, ue_id=None, titre=None, numero=None): def create_matiere(self, ue_id=None, titre=None, numero=None):
oid = sco_edit_matiere.do_matiere_create(self.context, locals(), REQUEST) oid = sco_edit_matiere.do_matiere_create(context, locals(), REQUEST)
oids = sco_edit_matiere.do_matiere_list(self.context, args={"matiere_id": oid}) oids = sco_edit_matiere.do_matiere_list(context, args={"matiere_id": oid})
if not oids: if not oids:
raise ScoValueError("matiere not created !") raise ScoValueError("matiere not created !")
return oids[0] return oids[0]
@ -205,8 +205,8 @@ class ScoFake(object):
code_apogee=None, code_apogee=None,
module_type=None, module_type=None,
): ):
oid = sco_edit_module.do_module_create(self.context, locals(), REQUEST) oid = sco_edit_module.do_module_create(context, locals(), REQUEST)
oids = sco_edit_module.do_module_list(self.context, args={"module_id": oid}) oids = sco_edit_module.do_module_list(context, args={"module_id": oid})
if not oids: if not oids:
raise ScoValueError("module not created ! (oid=%s)" % oid) raise ScoValueError("module not created ! (oid=%s)" % oid)
return oids[0] return oids[0]
@ -231,12 +231,11 @@ class ScoFake(object):
elt_sem_apo=None, elt_sem_apo=None,
elt_annee_apo=None, elt_annee_apo=None,
etapes=None, etapes=None,
responsables=["bach"], responsables=("bach",),
): ):
oid = sco_formsemestre.do_formsemestre_create(locals()) oid = sco_formsemestre.do_formsemestre_create(locals())
# oids = self.context.do_formsemestre_list(args={"formsemestre_id": oid})
oids = sco_formsemestre.do_formsemestre_list( oids = sco_formsemestre.do_formsemestre_list(
self.context, args={"formsemestre_id": oid} context, args={"formsemestre_id": oid}
) # API inconsistency ) # API inconsistency
if not oids: if not oids:
raise ScoValueError("formsemestre not created !") raise ScoValueError("formsemestre not created !")
@ -249,9 +248,9 @@ class ScoFake(object):
formsemestre_id=None, formsemestre_id=None,
responsable_id=None, responsable_id=None,
): ):
oid = sco_moduleimpl.do_moduleimpl_create(self.context, locals()) oid = sco_moduleimpl.do_moduleimpl_create(context, locals())
oids = sco_moduleimpl.do_moduleimpl_list( oids = sco_moduleimpl.do_moduleimpl_list(
self.context, moduleimpl_id=oid context, moduleimpl_id=oid
) # API inconsistency ) # API inconsistency
if not oids: if not oids:
raise ScoValueError("moduleimpl not created !") raise ScoValueError("moduleimpl not created !")
@ -260,7 +259,7 @@ class ScoFake(object):
@logging_meth @logging_meth
def inscrit_etudiant(self, sem, etud): def inscrit_etudiant(self, sem, etud):
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules( sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
self.context, context,
sem["formsemestre_id"], sem["formsemestre_id"],
etud["etudid"], etud["etudid"],
etat="I", etat="I",
@ -287,10 +286,8 @@ class ScoFake(object):
): ):
args = locals() args = locals()
del args["self"] del args["self"]
oid = sco_evaluations.do_evaluation_create(self.context, **args) oid = sco_evaluations.do_evaluation_create(**args)
oids = sco_evaluations.do_evaluation_list( oids = sco_evaluations.do_evaluation_list(args={"evaluation_id": oid})
self.context, args={"evaluation_id": oid}
)
if not oids: if not oids:
raise ScoValueError("evaluation not created !") raise ScoValueError("evaluation not created !")
return oids[0] return oids[0]
@ -305,7 +302,7 @@ class ScoFake(object):
uid="bach", uid="bach",
): ):
return sco_saisie_notes._notes_add( return sco_saisie_notes._notes_add(
self.context, context,
uid, uid,
evaluation["evaluation_id"], evaluation["evaluation_id"],
[(etud["etudid"], note)], [(etud["etudid"], note)],
@ -422,7 +419,7 @@ class ScoFake(object):
): ):
"""Affecte décision de jury""" """Affecte décision de jury"""
sco_formsemestre_validation.formsemestre_validation_etud_manu( sco_formsemestre_validation.formsemestre_validation_etud_manu(
self.context, context,
formsemestre_id=sem["formsemestre_id"], formsemestre_id=sem["formsemestre_id"],
etudid=etud["etudid"], etudid=etud["etudid"],
code_etat=code_etat, code_etat=code_etat,

View File

@ -17,11 +17,12 @@ from app.scodoc import sco_evaluations
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
DEPT = "RT" # ce département (BD) doit exister DEPT = "RT" # ce département (BD) doit exister
context = None # #context
def test_notes_table(test_client): def test_notes_table(test_client):
"""Test construction et cache de NotesTable""" """Test construction et cache de NotesTable"""
sems = sco_formsemestre.do_formsemestre_list(None) sems = sco_formsemestre.do_formsemestre_list(context)
assert len(sems) assert len(sems)
sem = sems[0] sem = sems[0]
formsemestre_id = sem["formsemestre_id"] formsemestre_id = sem["formsemestre_id"]

View File

@ -0,0 +1,178 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Test de base de ScoDoc
Utiliser comme:
pytest tests/unit/test_sco_basic.py
"""
import random
from flask import g
from tests.unit import sco_fake_gen
from app import decorators
from app.scodoc import notesdb as ndb
from app.scodoc import sco_utils as scu
from app.scodoc import sco_abs
from app.scodoc import sco_abs_views
from app.scodoc import sco_bulletins
from app.scodoc import sco_evaluations
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_parcours_dut
from app.scodoc import sco_formsemestre_validation
context = None # #context
def test_sco_basic(test_client):
"""Test quelques opérations élémentaires de ScoDoc
Création 10 étudiants, formation, semestre, inscription etudiant,
creation 1 evaluation, saisie 10 notes.
"""
ndb.set_sco_dept("TEST00") # ce département doit exister !
G = sco_fake_gen.ScoFake(verbose=False)
G.verbose = True
# --- Création d'étudiants
etuds = [G.create_etud(code_nip=None) for _ in range(10)]
# --- Création d'une formation
f = G.create_formation(acronyme="")
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
mat = G.create_matiere(ue_id=ue["ue_id"], titre="matière test")
mod = G.create_module(
matiere_id=mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
# --- Mise place d'un semestre
sem = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=1,
date_debut="01/01/2020",
date_fin="30/06/2020",
)
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem["formsemestre_id"],
responsable_id="bach",
)
# --- Inscription des étudiants
for etud in etuds:
G.inscrit_etudiant(sem, etud)
# --- Creation évaluation
e = G.create_evaluation(
moduleimpl_id=mi["moduleimpl_id"],
jour="01/01/2020",
description="evaluation test",
coefficient=1.0,
)
# --- 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, sem["formsemestre_id"], etud["etudid"]
)
# Toute les notes sont saisies, donc eval complète
etat = sco_evaluations.do_evaluation_etat(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(e2["evaluation_id"])
assert etat["evalcomplete"] == False
# la première éval est toujours complète:
etat = sco_evaluations.do_evaluation_etat(e["evaluation_id"])
assert etat["evalcomplete"]
# Modifie l'évaluation 2 pour "prise en compte immédiate"
e2["publish_incomplete"] = "1"
sco_evaluations.do_evaluation_edit(e2)
etat = sco_evaluations.do_evaluation_etat(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(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"]
_ = sco_abs_views.doSignaleAbsence(
context, "15/01/2020", "18/01/2020", demijournee=2, etudid=etudid
)
_ = sco_abs_views.doJustifAbsence(
context,
"17/01/2020",
"18/01/2020",
demijournee=2,
etudid=etudid,
)
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem)
assert nbabs == 6, "incorrect nbabs (%d)" % nbabs
assert nbabsjust == 2, "incorrect nbabsjust (%s)" % nbabsjust
# --- 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, sem["formsemestre_id"], etudid, sco_codes_parcours.AJ, False
)
assert sco_parcours_dut.formsemestre_has_decisions(
context, sem["formsemestre_id"]
), "décisions manquantes"
# Suppression de la décision
sco_formsemestre_validation.formsemestre_validation_suppress_etud(
context, sem["formsemestre_id"], etudid
)
assert not sco_parcours_dut.formsemestre_has_decisions(
context, sem["formsemestre_id"]
), "décisions non effacées"

View File

@ -1,20 +1,16 @@
#!/bin/bash #!/bin/bash
# Create database for a ScoDoc instance # Create database for a ScoDoc departement
# This script must be executed as root # This script must be executed as postgres super user
# #
# $db_name is passed as an environment variable # $db_name is passed as an environment variable
source config.sh source config.sh
source utils.sh source utils.sh
check_uid_root "$0" echo 'Creating postgresql database'
# 1--- CREATION UTILISATEUR POSTGRESQL
init_postgres_user
# 2--- CREATION BASE UTILISATEURS
echo 'Creating postgresql database for users:' "$SCODOC_USER_DB"
su -c "createdb -E UTF-8 -p $POSTGRES_PORT -O $SCODOC_USER $SCODOC_USER_DB" $POSTGRES_SUPERUSER
# ---
echo 'Creating postgresql database ' $db_name
createdb -E UTF-8 -p $POSTGRES_PORT -O $POSTGRES_USER $db_name

View File

@ -56,13 +56,13 @@ then
exit 1 exit 1
fi fi
# --- Ensure postgres user scodoc exists # --- Ensure postgres user "scodoc" ($POSTGRES_USER) exists
init_postgres_user init_postgres_user
# ----------------------- Create database # ----------------------- Create Dept database
su -c ./create_database.sh "$POSTGRES_SUPERUSER" su -c ./create_database.sh "$POSTGRES_SUPERUSER"
# ----------------------- Create tables # ----------------------- Initialize table database
# POSTGRES_USER == regular unix user (scodoc) # POSTGRES_USER == regular unix user (scodoc)
if [ "$interactive" = 1 ] if [ "$interactive" = 1 ]
then then
@ -92,7 +92,4 @@ then
echo echo
echo " Attention: la base de donnees n'a pas de copies de sauvegarde" echo " Attention: la base de donnees n'a pas de copies de sauvegarde"
echo echo
echo " Maintenant, vous pouvez ajouter le departement via l'application web"
echo " en suivant le lien \"Administration de ScoDoc\" sur la page d'accueil."
echo
fi fi

View File

@ -0,0 +1,18 @@
#!/bin/bash
# Create USERS database for ScoDoc 8
# This script must be executed as root
#
# $db_name is passed as an environment variable
source config.sh
source utils.sh
check_uid_root "$0"
# 1--- CREATION UTILISATEUR POSTGRESQL
init_postgres_user
# 2--- CREATION BASE UTILISATEURS
echo 'Creating postgresql database for users:' "$SCODOC_USER_DB"
su -c "createdb -E UTF-8 -p $POSTGRES_PORT -O $SCODOC_USER $SCODOC_USER_DB" "$POSTGRES_SUPERUSER"