From b0a77fba6627bb8e5430037e7374adda7db9744c Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Fri, 30 Jul 2021 10:36:30 +0300 Subject: [PATCH] Fix creation/deletion scripts + more unit tests --- README.md | 17 +- app/scodoc/notesdb.py | 2 +- app/scodoc/sco_synchro_etuds.py | 2 +- scodoc.py | 8 +- scotests/demo/demo_reset_noms.py | 2 +- scotests/scointeractive.sh | 79 --------- scotests/splinter/README.md | 38 ---- scotests/splinter/common.py | 49 ------ scotests/splinter/conn_info.py | 10 -- scotests/splinter/test-0.py | 51 ------ scotests/splinter/test-intensive-changes.py | 66 ------- scotests/splinter/test-jury.py | 44 ----- scotests/test-all-moys.py | 45 ----- scotests/test-apo-csv.py | 64 ------- scotests/test-apo-export-impairs.py | 46 ----- scotests/test_basic.py | 181 -------------------- scotests/testpyexcelerator.py | 19 -- tests/conftest.py | 28 ++- tests/unit/__init__.py | 1 + {scotests => tests/unit}/sco_fake_gen.py | 61 ++++--- tests/unit/test_caches.py | 3 +- tests/unit/test_sco_basic.py | 178 +++++++++++++++++++ tools/create_database.sh | 16 +- tools/create_dept.sh | 11 +- tools/create_users_database.sh | 18 ++ 25 files changed, 280 insertions(+), 759 deletions(-) delete mode 100755 scotests/scointeractive.sh delete mode 100644 scotests/splinter/README.md delete mode 100644 scotests/splinter/common.py delete mode 100644 scotests/splinter/conn_info.py delete mode 100644 scotests/splinter/test-0.py delete mode 100755 scotests/splinter/test-intensive-changes.py delete mode 100644 scotests/splinter/test-jury.py delete mode 100644 scotests/test-all-moys.py delete mode 100644 scotests/test-apo-csv.py delete mode 100644 scotests/test-apo-export-impairs.py delete mode 100644 scotests/test_basic.py delete mode 100644 scotests/testpyexcelerator.py create mode 100644 tests/unit/__init__.py rename {scotests => tests/unit}/sco_fake_gen.py (87%) create mode 100644 tests/unit/test_sco_basic.py create mode 100644 tools/create_users_database.sh diff --git a/README.md b/README.md index 06f0ae40..50146fc7 100644 --- a/README.md +++ b/README.md @@ -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 + +où `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) diff --git a/app/scodoc/notesdb.py b/app/scodoc/notesdb.py index e23fc903..6e70d805 100644 --- a/app/scodoc/notesdb.py +++ b/app/scodoc/notesdb.py @@ -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 diff --git a/app/scodoc/sco_synchro_etuds.py b/app/scodoc/sco_synchro_etuds.py index 8149bfe6..cd39c74d 100644 --- a/app/scodoc/sco_synchro_etuds.py +++ b/app/scodoc/sco_synchro_etuds.py @@ -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 diff --git a/scodoc.py b/scodoc.py index 8760e93b..f53035bc 100755 --- a/scodoc.py +++ b/scodoc.py @@ -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 diff --git a/scotests/demo/demo_reset_noms.py b/scotests/demo/demo_reset_noms.py index d1f00cf3..8211f5bb 100755 --- a/scotests/demo/demo_reset_noms.py +++ b/scotests/demo/demo_reset_noms.py @@ -15,7 +15,7 @@ import sys import random import psycopg2 -from gen_nomprenoms import nomprenom +from .gen_nomprenoms import nomprenom def usage(): diff --git a/scotests/scointeractive.sh b/scotests/scointeractive.sh deleted file mode 100755 index 4db04850..00000000 --- a/scotests/scointeractive.sh +++ /dev/null @@ -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 - diff --git a/scotests/splinter/README.md b/scotests/splinter/README.md deleted file mode 100644 index 56f36ef1..00000000 --- a/scotests/splinter/README.md +++ /dev/null @@ -1,38 +0,0 @@ - -# Tests avec splinter (expérimental) - - - -## 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. - - - - diff --git a/scotests/splinter/common.py b/scotests/splinter/common.py deleted file mode 100644 index 788551cd..00000000 --- a/scotests/splinter/common.py +++ /dev/null @@ -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 diff --git a/scotests/splinter/conn_info.py b/scotests/splinter/conn_info.py deleted file mode 100644 index d827308d..00000000 --- a/scotests/splinter/conn_info.py +++ /dev/null @@ -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' - diff --git a/scotests/splinter/test-0.py b/scotests/splinter/test-0.py deleted file mode 100644 index eb49864d..00000000 --- a/scotests/splinter/test-0.py +++ /dev/null @@ -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+'¬e_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]+?)\)', 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() diff --git a/scotests/splinter/test-intensive-changes.py b/scotests/splinter/test-intensive-changes.py deleted file mode 100755 index 812eca5f..00000000 --- a/scotests/splinter/test-intensive-changes.py +++ /dev/null @@ -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+'¬e_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]+?)\)', 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) diff --git a/scotests/splinter/test-jury.py b/scotests/splinter/test-jury.py deleted file mode 100644 index 63c8f9d7..00000000 --- a/scotests/splinter/test-jury.py +++ /dev/null @@ -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) - diff --git a/scotests/test-all-moys.py b/scotests/test-all-moys.py deleted file mode 100644 index cc8ca5eb..00000000 --- a/scotests/test-all-moys.py +++ /dev/null @@ -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() diff --git a/scotests/test-apo-csv.py b/scotests/test-apo-csv.py deleted file mode 100644 index 1743d2d0..00000000 --- a/scotests/test-apo-csv.py +++ /dev/null @@ -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) - - - - - - - diff --git a/scotests/test-apo-export-impairs.py b/scotests/test-apo-export-impairs.py deleted file mode 100644 index 9025045f..00000000 --- a/scotests/test-apo-export-impairs.py +++ /dev/null @@ -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] != '' }) - - - - - - diff --git a/scotests/test_basic.py b/scotests/test_basic.py deleted file mode 100644 index 8cf0018b..00000000 --- a/scotests/test_basic.py +++ /dev/null @@ -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" diff --git a/scotests/testpyexcelerator.py b/scotests/testpyexcelerator.py deleted file mode 100644 index 543829f7..00000000 --- a/scotests/testpyexcelerator.py +++ /dev/null @@ -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") - diff --git a/tests/conftest.py b/tests/conftest.py index 036c3112..b61a1623 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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() diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..a0291f06 --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ +# Unit tests diff --git a/scotests/sco_fake_gen.py b/tests/unit/sco_fake_gen.py similarity index 87% rename from scotests/sco_fake_gen.py rename to tests/unit/sco_fake_gen.py index e36bd08d..36ebe613 100644 --- a/scotests/sco_fake_gen.py +++ b/tests/unit/sco_fake_gen.py @@ -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, diff --git a/tests/unit/test_caches.py b/tests/unit/test_caches.py index 32475e50..86568711 100644 --- a/tests/unit/test_caches.py +++ b/tests/unit/test_caches.py @@ -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"] diff --git a/tests/unit/test_sco_basic.py b/tests/unit/test_sco_basic.py new file mode 100644 index 00000000..d42fdd61 --- /dev/null +++ b/tests/unit/test_sco_basic.py @@ -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" diff --git a/tools/create_database.sh b/tools/create_database.sh index 8e443399..ee90346b 100755 --- a/tools/create_database.sh +++ b/tools/create_database.sh @@ -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 diff --git a/tools/create_dept.sh b/tools/create_dept.sh index 1b564960..8386b04d 100755 --- a/tools/create_dept.sh +++ b/tools/create_dept.sh @@ -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 diff --git a/tools/create_users_database.sh b/tools/create_users_database.sh new file mode 100644 index 00000000..cc8b501c --- /dev/null +++ b/tools/create_users_database.sh @@ -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"