diff --git a/.gitignore b/.gitignore index 6d49cf2c..deaa1705 100644 --- a/.gitignore +++ b/.gitignore @@ -169,4 +169,5 @@ Thumbs.db .vscode/ *.code-workspace - +# PyCharm projects +.idea/ diff --git a/README.md b/README.md index 8ae84c1f..10bf02c1 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ de votre installation ScoDoc 7 pour passer à ScoDoc 8 (*ne pas utiliser en prod ## Création d'un département - sudo su + su scodoc # si besoin cd /opt/scodoc source venv/bin/activate flask sco-create-dept DEPT @@ -177,7 +177,7 @@ où `DEPT` est le nom du département (un acronyme en majuscule, comme "RT", "GE ### Suppression d'un département - sudo su + su scodoc # si besoin cd /opt/scodoc source venv/bin/activate flask sco-delete-dept DEPT diff --git a/app/scodoc/TrivialFormulator.py b/app/scodoc/TrivialFormulator.py index d14d5e6a..69220cf1 100644 --- a/app/scodoc/TrivialFormulator.py +++ b/app/scodoc/TrivialFormulator.py @@ -757,6 +757,6 @@ def tf_error_message(msg): if isinstance(msg, str): msg = [msg] return ( - '' - % '
  • '.join(msg) + '' + % '
  • '.join(msg) ) diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py index e723139b..0bd3bfbb 100644 --- a/app/scodoc/sco_dept.py +++ b/app/scodoc/sco_dept.py @@ -111,7 +111,7 @@ def index_html(context, REQUEST=None, showcodes=0, showsemtable=0): # aucun semestre courant: affiche aide H.append( """

    Aucune session en cours !

    -

    Pour ajouter une session, aller dans Programmes, +

    Pour ajouter une session, aller dans Programmes, choisissez une formation, puis suivez le lien "UE, modules, semestres".

    Là, en bas de page, suivez le lien diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py index 62c17505..23880f68 100644 --- a/app/scodoc/sco_formations.py +++ b/app/scodoc/sco_formations.py @@ -259,6 +259,7 @@ def formation_list_table(context, formation_id=None, args={}, REQUEST=None): f["parcours_name"] = "" f["_titre_target"] = "ue_list?formation_id=%(formation_id)s" % f f["_titre_link_class"] = "stdlink" + f["_titre_id"] = "titre-%s" % f["acronyme"].lower().replace(" ", "-") # Ajoute les semestres associés à chaque formation: f["sems"] = sco_formsemestre.do_formsemestre_list( context, args={"formation_id": f["formation_id"]} @@ -266,13 +267,14 @@ def formation_list_table(context, formation_id=None, args={}, REQUEST=None): f["sems_list_txt"] = ", ".join([s["session_id"] for s in f["sems"]]) f["_sems_list_txt_html"] = ", ".join( [ - '%(session_id)s' - % s + '%(' + "session_id)s " % s for s in f["sems"] ] + [ - 'ajouter' - % f + 'ajouter ' + % (f["acronyme"].lower().replace(" ", "-"), f["formation_id"]) ] ) if f["sems"]: @@ -288,16 +290,17 @@ def formation_list_table(context, formation_id=None, args={}, REQUEST=None): else: but_locked = '' if editable and not locked: - but_suppr = ( - '%s' - % (f["formation_id"], suppricon) + but_suppr = '%s' % ( + f["formation_id"], + f["acronyme"].lower().replace(" ", "-"), + suppricon, ) else: but_suppr = '' if editable: but_edit = ( - '%s' - % (f["formation_id"], editicon) + '%s' + % (f["formation_id"], f["acronyme"].lower().replace(" ", "-"), editicon) ) else: but_edit = '' @@ -365,4 +368,4 @@ def formation_create_new_version(context, formation_id, redirect=True, REQUEST=N "ue_list?formation_id=" + new_id + "&msg=Nouvelle version !" ) else: - return new_id, modules_old2new, ues_old2new \ No newline at end of file + return new_id, modules_old2new, ues_old2new diff --git a/app/views/notes.py b/app/views/notes.py index d29e9e97..8f42c6ba 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -391,7 +391,7 @@ def index_html(context, REQUEST=None): if editable: H.append( - """

    Créer une formation

    + """

    Créer une formation

    Importer une formation (xml)

    Une "formation" est un programme pédagogique structuré en UE, matières et modules. Chaque semestre se réfère à une formation. La modification d'une formation affecte tous les semestres qui s'y réfèrent.

    """ @@ -1968,7 +1968,7 @@ def formsemestre_validation_etud_manu( def formsemestre_validate_previous_ue( context, formsemestre_id, etudid=None, REQUEST=None ): - "Form. saisie UE validée hors ScoDoc " + "Form. saisie UE validée hors ScoDoc" if not sco_permissions_check.can_validate_sem(formsemestre_id): return scu.confirm_dialog( message="

    Opération non autorisée pour %s" diff --git a/scodoc.py b/scodoc.py index 9f6a6102..6c459ca7 100755 --- a/scodoc.py +++ b/scodoc.py @@ -6,7 +6,6 @@ """ - from __future__ import print_function import os @@ -19,6 +18,7 @@ from flask.cli import with_appcontext from app import create_app, cli, db from app.auth.models import User, Role, UserRole +from app.scodoc.notesdb import set_sco_dept from app.views import notes, scolar, absences import app.utils as utils @@ -152,10 +152,7 @@ def user_password(username, password=None): # user-password @click.argument("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") - return 1 - if os.system('cd tools && ./delete_dept.sh -n "{}"'.format(dept)): + if os.system('tools/delete_dept.sh -n "{}"'.format(dept)): sys.stderr.write("error deleting dept " + dept) return 1 return 0 @@ -165,10 +162,7 @@ def sco_delete_dept(dept): # sco-delete-dept @click.argument("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(f'cd tools && ./create_dept.sh -n "{dept}"'): + if os.system(f'tools/create_dept.sh -n "{dept}"'): sys.stderr.write(f"error creating dept {dept}\n") return 1 return 0 @@ -216,3 +210,22 @@ def clear_cache(): # clear-cache r = redis.Redis() r.flushall() click.echo("Redis caches flushed.") + + +@app.cli.command() +@click.argument("xml_file") +@click.argument("dept") +def import_xml(xml_file, dept): + import flask_login + from flask_login import login_user, logout_user, current_user + from app.scodoc.notesdb import close_dept_connection, open_dept_connection + from app.scodoc.sco_formations import formation_import_xml + + with app.test_request_context(): + u = User.query.first() + flask_login.login_user(u) + click.echo("Importing {}".format(xml_file)) + set_sco_dept(dept) + doc = open(xml_file).read() + formation_import_xml(None, doc) + click.echo("Done") diff --git a/scotests/test_scenario1_formation.py b/scotests/test_scenario1_formation.py deleted file mode 100644 index 564b3e1a..00000000 --- a/scotests/test_scenario1_formation.py +++ /dev/null @@ -1,73 +0,0 @@ -import sco_formations -import random - -# La variable context est définie par le script de lancement -# l'affecte ainsi pour évietr les warnins pylint: -context = context # pylint: disable=undefined-variable -REQUEST = REQUEST # pylint: disable=undefined-variable -import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error -import sco_moduleimpl - -G = sco_fake_gen.ScoFake(context.Notes) -G.verbose = False - -file = open("scotests/export_formation1.xml") -doc = file.read() -file.close() - - -# --- Création de la formation - -f = sco_formations.formation_import_xml(doc=doc, context=context.Notes) - -# --- Création des semestres - -sem1 = G.create_formsemestre( - formation_id=f[0], - semestre_id=1, - date_debut="01/09/2020", - date_fin="01/02/2021", -) - -sem3 = G.create_formsemestre( - formation_id=f[0], - semestre_id=3, - date_debut="01/09/2020", - date_fin="01/02/2021", -) - -sem2 = G.create_formsemestre( - formation_id=f[0], - semestre_id=2, - date_debut="02/02/2021", - date_fin="01/06/2021", -) - -sem4 = G.create_formsemestre( - formation_id=f[0], - semestre_id=4, - date_debut="02/02/2021", - date_fin="01/06/2021", -) - - -# --- Implémentation des modules - -li_module = context.Notes.do_module_list() -mods_imp = [] -for mod in li_module: - if mod["semestre_id"] == 1: - formsemestre_id = sem1["formsemestre_id"] - elif mod["semestre_id"] == 2: - formsemestre_id = sem2["formsemestre_id"] - elif mod["semestre_id"] == 3: - formsemestre_id = sem3["formsemestre_id"] - else: - formsemestre_id = sem4["formsemestre_id"] - - mi = G.create_moduleimpl( - module_id=mod["module_id"], - formsemestre_id=formsemestre_id, - responsable_id="bach", - ) - mods_imp.append(mi) \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 3e7888f0..758250f4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,11 +30,18 @@ def test_client(): db.create_all() Role.insert_roles() u = User(user_name="admin") - admin_role = Role.query.filter_by(name="SuperAdmin").first() - u.add_role(admin_role, "TEST00") + super_admin_role = Role.query.filter_by(name="SuperAdmin").first() + u.add_role(super_admin_role, "TEST00") # u.set_password("admin") login_user(u) - # db.session.add(u) + # Vérifie que l'utilisateur bach existe + u = User.query.filter_by(user_name="bach").first() + if u is None: + u = User(user_name="bach") + if not "Admin" in {r.name for r in u.roles}: + admin_role = Role.query.filter_by(name="Admin").first() + u.add_role(admin_role, "TEST00") + db.session.add(u) ndb.set_sco_dept("TEST00") # set db connection truncate_database() # erase tables yield client diff --git a/tests/scenarios/export_formation1.xml b/tests/scenarios/export_formation1.xml new file mode 100755 index 00000000..8baf7a6b --- /dev/null +++ b/tests/scenarios/export_formation1.xml @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/scenarios/test_scenario1_formation.py b/tests/scenarios/test_scenario1_formation.py new file mode 100644 index 00000000..8027be21 --- /dev/null +++ b/tests/scenarios/test_scenario1_formation.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +""" +Scenario: préparation base de données pour tests Selenium + +S'utilise comme un test avec pytest, mais n'est pas un test ! +Modifie la base de données du département TEST00 + +Usage: pytest tests/scenarios/test_scenario1_formation.py +""" +# code écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en août 2021 + +import random + +from tests.unit import sco_fake_gen +from app.scodoc import sco_edit_module +from app.scodoc import sco_formations +from app.scodoc import sco_moduleimpl + +context = None # #context + + +def test_scenario1(test_client): + """Applique "scenario 1""" + G = sco_fake_gen.ScoFake(verbose=False) + + # Lecture fichier XML local: + with open("tests/unit/formation-exemple-1.xml") as f: + doc = f.read() + + # --- Création de la formation + f = sco_formations.formation_import_xml(doc=doc, context=context) + + # --- Création des semestres + formation_id = f[0] + # --- Mise en place de 4 semestres + sems = [ + G.create_formsemestre( + formation_id=formation_id, + semestre_id=x[0], + date_debut=x[1], + date_fin=x[2], + ) + for x in ( + (1, "01/09/2020", "01/02/2021"), + (2, "02/02/2021", "01/06/2021"), + (3, "01/09/2020", "01/02/2021"), + (4, "02/02/2021", "01/06/2021"), + ) + ] + + # --- Implémentation des modules + modules = sco_edit_module.do_module_list(context, {"formation_id": formation_id}) + mods_imp = [] + for mod in modules: + mi = G.create_moduleimpl( + module_id=mod["module_id"], + formsemestre_id=sems[mod["semestre_id"] - 1]["formsemestre_id"], + responsable_id="bach", + ) + mods_imp.append(mi) diff --git a/tests/unit/sco_fake_gen.py b/tests/unit/sco_fake_gen.py index 9303ef38..42c58452 100644 --- a/tests/unit/sco_fake_gen.py +++ b/tests/unit/sco_fake_gen.py @@ -66,7 +66,7 @@ def logging_meth(func): class ScoFake(object): - """Helper for ScoSoc tests""" + """Helper for ScoDoc tests""" def __init__(self, verbose=True): self.verbose = verbose diff --git a/tools/create_dept.sh b/tools/create_dept.sh index 8eb86036..4d81ce44 100755 --- a/tools/create_dept.sh +++ b/tools/create_dept.sh @@ -17,7 +17,7 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "$SCRIPT_DIR/config.sh" source "$SCRIPT_DIR/utils.sh" -check_uid_root "$0" +[ "$USER" = "$SCODOC_USER" ] || die "$0 must run as user $SCODOC_USER" cd "$SCRIPT_DIR" @@ -61,19 +61,17 @@ then exit 1 fi -# --- Ensure postgres user "scodoc" ($POSTGRES_USER) exists -init_postgres_user - # ----------------------- Create Dept database -su -c ./create_database.sh "$POSTGRES_SUPERUSER" +# (créée en tant qu'utilisateur scodoc) +./create_database.sh # ----------------------- Initialize table database # POSTGRES_USER == regular unix user (scodoc) if [ "$interactive" = 1 ] then - su -c ./initialize_database.sh "$POSTGRES_USER" + ./initialize_database.sh else - su -c ./initialize_database.sh "$POSTGRES_USER" > /dev/null 2>&1 + ./initialize_database.sh > /dev/null 2>&1 fi @@ -84,17 +82,10 @@ echo "dbname=${db_name}" > "$cfg_pathname" if [ "$interactive" = 1 ] then - # ----------------------- Force mise à jour - echo -n "Voulez vous mettre a jour ScoDoc (tres recommande) ? (y/n) [y] " - read -r ans - if [ "$(norm_ans "$ans")" != 'N' ] - then - (cd "$SCODOC_DIR/tools" || terminate "no config directory"; ./upgrade.sh) - fi # ----------------------- echo echo " Departement $DEPT cree" echo echo " Attention: la base de donnees n'a pas de copies de sauvegarde" - echo + echo "(XXX section à revoir en ScoDoc 8.1)" # #sco8 fi diff --git a/tools/delete_dept.sh b/tools/delete_dept.sh index 6ebaff94..a77c0f86 100755 --- a/tools/delete_dept.sh +++ b/tools/delete_dept.sh @@ -19,7 +19,9 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "$SCRIPT_DIR/config.sh" source "$SCRIPT_DIR/utils.sh" -check_uid_root "$0" +# scodoc may now manage departments TODO To be fixed +# check_uid_root "$0" + usage() { echo "$0 [-n DEPT]" echo "(default to interactive mode)" @@ -57,15 +59,15 @@ cfg_pathname="${SCODOC_VAR_DIR}/config/depts/$DEPT".cfg if [ -e "$cfg_pathname" ] then - # arret de ScoDoc - scodocctl stop + # arret de ScoDoc (need root permissions so disabled for the moment TODO to be fixed) + # scodocctl stop # suppression de la base postgres db_name=$(sed '/^dbname=*/!d; s///;q' < "$cfg_pathname") - if su -c "psql -lt" "$POSTGRES_SUPERUSER" | cut -d \| -f 1 | grep -wq "$db_name" + if psql -lt | cut -d \| -f 1 | grep -wq "$db_name" then echo "Suppression de la base postgres $db_name ..." - su -c "dropdb $db_name" "$POSTGRES_SUPERUSER" || terminate "ne peux supprimer base de donnees $db_name" + dropdb $db_name || terminate "ne peux supprimer base de donnees $db_name" else echo "la base postgres $db_name n'existe pas." fi diff --git a/tools/install_debian10.sh b/tools/install_debian10.sh index df2b9272..cc50925a 100755 --- a/tools/install_debian10.sh +++ b/tools/install_debian10.sh @@ -105,6 +105,11 @@ then yes | ufw enable fi +# --- POSTGRESQL +# --- Ensure postgres user "scodoc" ($POSTGRES_USER) exists +init_postgres_user + + # --- XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX --- echo echo "WARNING: version ScoDoc8 expérimentale"