1
0
forked from ScoDoc/ScoDoc

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
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`.
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
./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)

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
raise # and re-raise exception
if commit:
log("DBInsertDict: commit (requested)")
# log("DBInsertDict: commit (requested)")
cnx.commit()
return oid

View File

@ -695,7 +695,7 @@ def do_import_etud_admission(
"codelycee": get_opt_str(etud, "lycee"),
"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})
if not al:
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()
@click.argument("dept")
def sco_delete_dept(dept):
def sco_delete_dept(dept): # sco-delete-dept
"Delete existing departement"
if os.getuid() != 0:
sys.stderr.write("sco_delete_dept: must be run by root\n")
@ -146,13 +146,13 @@ def sco_delete_dept(dept):
@app.cli.command()
@click.argument("dept")
def sco_create_dept(dept):
def sco_create_dept(dept): # sco-create-dept
"Create new departement"
if os.getuid() != 0:
sys.stderr.write("sco_create_dept: must be run by root\n")
return 1
if os.system('cd tools && ./create_dept.sh -n "{}"'.format(dept)):
sys.stderr.write("error deleting dept " + dept)
if os.system(f'cd tools && ./create_dept.sh -n "{dept}"'):
sys.stderr.write(f"error creating dept {dept}\n")
return 1
return 0

View File

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

@ -4,11 +4,10 @@
"""Creation environnement pour test.
A utiliser avec debug.py (côté serveur).
La classe ScoFake offre un ensemble de raccourcis permettant d'écrire
La classe ScoFake offre un ensemble de raccourcis permettant d'écrire
facilement des tests ou de reproduire des bugs.
"""
from __future__ import print_function
from functools import wraps
import sys
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_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):
@ -58,15 +59,16 @@ def logging_meth(func):
@wraps(func)
def wrapper_logging_meth(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 wrapper_logging_meth
class ScoFake(object):
def __init__(self, context, verbose=True):
self.context = context
"""Helper for ScoSoc tests"""
def __init__(self, verbose=True):
self.verbose = verbose
def log(self, msg):
@ -127,10 +129,10 @@ class ScoFake(object):
nom = r_nom
if not prenom:
prenom = r_prenom
etud = sco_etud.create_etud(self.context, cnx, args=locals(), REQUEST=REQUEST)
inscription = "2020" # pylint: disable=unused-variable
etud = sco_etud.create_etud(context, cnx, args=locals(), REQUEST=REQUEST)
inscription = "2020" # pylint: disable=possibly-unused-variable
sco_synchro_etuds.do_import_etud_admission(
self.context, cnx, etud["etudid"], locals()
context, cnx, etud["etudid"], locals()
)
return etud
@ -147,10 +149,8 @@ class ScoFake(object):
"""Crée une formation"""
if not acronyme:
acronyme = "TEST" + str(random.randint(100000, 999999))
oid = sco_edit_formation.do_formation_create(
self.context, locals(), REQUEST=REQUEST
)
oids = sco_formations.formation_list(self.context, formation_id=oid)
oid = sco_edit_formation.do_formation_create(context, locals(), REQUEST=REQUEST)
oids = sco_formations.formation_list(context, formation_id=oid)
if not oids:
raise ScoValueError("formation not created !")
return oids[0]
@ -171,17 +171,17 @@ class ScoFake(object):
):
"""Crée une UE"""
if numero is None:
numero = sco_edit_ue.next_ue_numero(self.context, formation_id, 0)
oid = sco_edit_ue.do_ue_create(self.context, locals(), REQUEST)
oids = sco_edit_ue.do_ue_list(self.context, args={"ue_id": oid})
numero = sco_edit_ue.next_ue_numero(context, formation_id, 0)
oid = sco_edit_ue.do_ue_create(context, locals(), REQUEST)
oids = sco_edit_ue.do_ue_list(context, args={"ue_id": oid})
if not oids:
raise ScoValueError("ue not created !")
return oids[0]
@logging_meth
def create_matiere(self, ue_id=None, titre=None, numero=None):
oid = sco_edit_matiere.do_matiere_create(self.context, locals(), REQUEST)
oids = sco_edit_matiere.do_matiere_list(self.context, args={"matiere_id": oid})
oid = sco_edit_matiere.do_matiere_create(context, locals(), REQUEST)
oids = sco_edit_matiere.do_matiere_list(context, args={"matiere_id": oid})
if not oids:
raise ScoValueError("matiere not created !")
return oids[0]
@ -205,8 +205,8 @@ class ScoFake(object):
code_apogee=None,
module_type=None,
):
oid = sco_edit_module.do_module_create(self.context, locals(), REQUEST)
oids = sco_edit_module.do_module_list(self.context, args={"module_id": oid})
oid = sco_edit_module.do_module_create(context, locals(), REQUEST)
oids = sco_edit_module.do_module_list(context, args={"module_id": oid})
if not oids:
raise ScoValueError("module not created ! (oid=%s)" % oid)
return oids[0]
@ -231,12 +231,11 @@ class ScoFake(object):
elt_sem_apo=None,
elt_annee_apo=None,
etapes=None,
responsables=["bach"],
responsables=("bach",),
):
oid = sco_formsemestre.do_formsemestre_create(locals())
# oids = self.context.do_formsemestre_list(args={"formsemestre_id": oid})
oids = sco_formsemestre.do_formsemestre_list(
self.context, args={"formsemestre_id": oid}
context, args={"formsemestre_id": oid}
) # API inconsistency
if not oids:
raise ScoValueError("formsemestre not created !")
@ -249,9 +248,9 @@ class ScoFake(object):
formsemestre_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(
self.context, moduleimpl_id=oid
context, moduleimpl_id=oid
) # API inconsistency
if not oids:
raise ScoValueError("moduleimpl not created !")
@ -260,7 +259,7 @@ class ScoFake(object):
@logging_meth
def inscrit_etudiant(self, sem, etud):
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
self.context,
context,
sem["formsemestre_id"],
etud["etudid"],
etat="I",
@ -287,10 +286,8 @@ class ScoFake(object):
):
args = locals()
del args["self"]
oid = sco_evaluations.do_evaluation_create(self.context, **args)
oids = sco_evaluations.do_evaluation_list(
self.context, args={"evaluation_id": oid}
)
oid = sco_evaluations.do_evaluation_create(**args)
oids = sco_evaluations.do_evaluation_list(args={"evaluation_id": oid})
if not oids:
raise ScoValueError("evaluation not created !")
return oids[0]
@ -305,7 +302,7 @@ class ScoFake(object):
uid="bach",
):
return sco_saisie_notes._notes_add(
self.context,
context,
uid,
evaluation["evaluation_id"],
[(etud["etudid"], note)],
@ -422,7 +419,7 @@ class ScoFake(object):
):
"""Affecte décision de jury"""
sco_formsemestre_validation.formsemestre_validation_etud_manu(
self.context,
context,
formsemestre_id=sem["formsemestre_id"],
etudid=etud["etudid"],
code_etat=code_etat,

View File

@ -17,11 +17,12 @@ from app.scodoc import sco_evaluations
from app.scodoc import sco_formsemestre
DEPT = "RT" # ce département (BD) doit exister
context = None # #context
def test_notes_table(test_client):
"""Test construction et cache de NotesTable"""
sems = sco_formsemestre.do_formsemestre_list(None)
sems = sco_formsemestre.do_formsemestre_list(context)
assert len(sems)
sem = sems[0]
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
# Create database for a ScoDoc instance
# This script must be executed as root
# Create database for a ScoDoc departement
# This script must be executed as postgres super user
#
# $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
echo 'Creating postgresql database'
# ---
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
fi
# --- Ensure postgres user scodoc exists
# --- Ensure postgres user "scodoc" ($POSTGRES_USER) exists
init_postgres_user
# ----------------------- Create database
su -c ./create_database.sh "$POSTGRES_SUPERUSER"
# ----------------------- Create Dept database
su -c ./create_database.sh "$POSTGRES_SUPERUSER"
# ----------------------- Create tables
# ----------------------- Initialize table database
# POSTGRES_USER == regular unix user (scodoc)
if [ "$interactive" = 1 ]
then
@ -91,8 +91,5 @@ then
echo " Departement $DEPT cree"
echo
echo " Attention: la base de donnees n'a pas de copies de sauvegarde"
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

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"