2021-12-18 09:47:45 +01:00
|
|
|
# -*- coding: UTF-8 -*-
|
2021-05-29 18:22:51 +02:00
|
|
|
|
|
|
|
|
2024-06-20 15:48:11 +02:00
|
|
|
"""Application Flask: ScoDoc"""
|
2021-05-29 18:22:51 +02:00
|
|
|
|
2023-08-22 17:02:00 +02:00
|
|
|
import datetime
|
2021-05-29 18:22:51 +02:00
|
|
|
from pprint import pprint as pp
|
2021-09-13 23:06:42 +02:00
|
|
|
import re
|
2021-05-31 09:57:23 +02:00
|
|
|
import sys
|
2021-05-29 18:22:51 +02:00
|
|
|
|
|
|
|
import click
|
|
|
|
import flask
|
2021-06-24 10:59:03 +02:00
|
|
|
from flask.cli import with_appcontext
|
2021-10-20 16:47:41 +02:00
|
|
|
from flask.templating import render_template
|
2022-03-09 18:03:18 +01:00
|
|
|
from flask_login import login_user, logout_user, current_user
|
2022-02-13 15:19:39 +01:00
|
|
|
import psycopg2
|
2024-10-18 23:12:32 +02:00
|
|
|
import sqlalchemy as sa
|
|
|
|
|
2021-09-13 16:11:33 +02:00
|
|
|
|
2023-03-20 11:17:38 +01:00
|
|
|
import app as mapp
|
2021-08-10 12:57:38 +02:00
|
|
|
from app import create_app, cli, db
|
|
|
|
from app import initialize_scodoc_database
|
|
|
|
from app import clear_scodoc_cache
|
2021-09-13 16:11:33 +02:00
|
|
|
from app import models
|
2021-06-24 10:59:03 +02:00
|
|
|
|
2021-05-29 18:22:51 +02:00
|
|
|
from app.auth.models import User, Role, UserRole
|
2023-01-13 23:23:18 +01:00
|
|
|
from app.entreprises.models import entreprises_reset_database
|
2023-01-24 12:12:24 +01:00
|
|
|
from app.models import Departement, departements
|
2022-01-27 11:44:58 +01:00
|
|
|
from app.models import Formation, UniteEns, Matiere, Module
|
2021-12-20 20:38:21 +01:00
|
|
|
from app.models import FormSemestre, FormSemestreInscription
|
2022-08-02 16:21:41 +02:00
|
|
|
from app.models import GroupDescr
|
2021-11-18 23:52:06 +01:00
|
|
|
from app.models import Identite
|
2022-08-02 16:21:41 +02:00
|
|
|
from app.models import ModuleImpl, ModuleImplInscription
|
|
|
|
from app.models import Partition
|
2023-01-26 14:49:04 +01:00
|
|
|
from app.models import ScolarAutorisationInscription, ScolarFormSemestreValidation
|
2023-01-17 01:05:48 +01:00
|
|
|
from app.models.but_refcomp import (
|
|
|
|
ApcCompetence,
|
|
|
|
ApcNiveau,
|
|
|
|
ApcParcours,
|
|
|
|
ApcReferentielCompetences,
|
|
|
|
)
|
|
|
|
from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE
|
2021-11-18 23:52:06 +01:00
|
|
|
from app.models.evaluations import Evaluation
|
2024-10-18 23:12:32 +02:00
|
|
|
from app.models.formsemestre import notes_formsemestre_responsables
|
|
|
|
from app.models.moduleimpls import notes_modules_enseignants
|
2023-02-09 11:56:20 +01:00
|
|
|
from app.scodoc import sco_dump_db
|
2023-01-13 23:23:18 +01:00
|
|
|
from app.scodoc.sco_logos import make_logo_local
|
2021-09-13 16:11:33 +02:00
|
|
|
from app.scodoc.sco_permissions import Permission
|
2024-04-06 12:16:53 +02:00
|
|
|
from app.views import notes, scolar, ScoData
|
2023-04-14 15:03:41 +02:00
|
|
|
import app.scodoc.sco_utils as scu
|
2021-08-14 18:54:32 +02:00
|
|
|
import tools
|
2022-03-09 16:05:44 +01:00
|
|
|
from tools.fakedatabase import create_test_api_database
|
2021-05-29 18:22:51 +02:00
|
|
|
|
2021-08-19 23:12:42 +02:00
|
|
|
from config import RunningConfig
|
2021-05-29 18:22:51 +02:00
|
|
|
|
2021-08-19 23:12:42 +02:00
|
|
|
app = create_app(RunningConfig)
|
2021-05-29 18:22:51 +02:00
|
|
|
cli.register(app)
|
|
|
|
|
|
|
|
|
2023-04-14 15:03:41 +02:00
|
|
|
@app.context_processor
|
|
|
|
def inject_sco_utils():
|
2024-07-10 00:53:09 +02:00
|
|
|
"Make Permission, sco and scu available in all Jinja templates"
|
2024-02-22 18:32:51 +01:00
|
|
|
# if modified, put the same in conftest.py#27
|
2024-07-21 07:49:58 +02:00
|
|
|
return {
|
|
|
|
"DEBUG": flask.current_app.config["DEBUG"],
|
|
|
|
"Permission": Permission,
|
|
|
|
"scu": scu,
|
|
|
|
"sco": ScoData(),
|
|
|
|
}
|
2023-04-14 15:03:41 +02:00
|
|
|
|
|
|
|
|
2021-05-29 18:22:51 +02:00
|
|
|
@app.shell_context_processor
|
|
|
|
def make_shell_context():
|
2021-12-13 19:05:51 +01:00
|
|
|
import numpy as np
|
|
|
|
import pandas as pd
|
2022-07-23 08:46:00 +02:00
|
|
|
import app as mapp # le package app
|
|
|
|
from app.scodoc import notesdb as ndb
|
|
|
|
from app.comp import res_sem
|
2023-01-13 23:23:18 +01:00
|
|
|
from app.comp.res_but import ResultatsSemestreBUT
|
2022-07-23 08:46:00 +02:00
|
|
|
from app.scodoc import sco_utils as scu
|
2021-08-01 09:52:28 +02:00
|
|
|
|
2021-05-29 18:22:51 +02:00
|
|
|
return {
|
2023-01-17 01:05:48 +01:00
|
|
|
"ApcCompetence": ApcCompetence,
|
|
|
|
"ApcNiveau": ApcNiveau,
|
|
|
|
"ApcParcours": ApcParcours,
|
|
|
|
"ApcReferentielCompetences": ApcReferentielCompetences,
|
|
|
|
"ApcValidationRCUE": ApcValidationRCUE,
|
|
|
|
"ApcValidationAnnee": ApcValidationAnnee,
|
2021-12-13 19:05:51 +01:00
|
|
|
"ctx": app.test_request_context(),
|
2021-05-29 18:22:51 +02:00
|
|
|
"current_app": flask.current_app,
|
2021-08-01 09:52:28 +02:00
|
|
|
"current_user": current_user,
|
2023-08-22 17:02:00 +02:00
|
|
|
"datetime": datetime,
|
2023-01-24 12:12:24 +01:00
|
|
|
"Departement": Departement,
|
2021-12-13 19:05:51 +01:00
|
|
|
"db": db,
|
2021-11-18 23:52:06 +01:00
|
|
|
"Evaluation": Evaluation,
|
2021-12-13 19:05:51 +01:00
|
|
|
"flask": flask,
|
2021-11-18 23:52:06 +01:00
|
|
|
"Formation": Formation,
|
|
|
|
"FormSemestre": FormSemestre,
|
2021-12-20 20:38:21 +01:00
|
|
|
"FormSemestreInscription": FormSemestreInscription,
|
2022-08-02 16:21:41 +02:00
|
|
|
"GroupDescr": GroupDescr,
|
2021-11-18 23:52:06 +01:00
|
|
|
"Identite": Identite,
|
2021-12-13 19:05:51 +01:00
|
|
|
"login_user": login_user,
|
|
|
|
"logout_user": logout_user,
|
|
|
|
"mapp": mapp,
|
2022-01-27 11:44:58 +01:00
|
|
|
"Matiere": Matiere,
|
2023-01-17 01:05:48 +01:00
|
|
|
"models": models,
|
2021-11-18 23:52:06 +01:00
|
|
|
"Module": Module,
|
|
|
|
"ModuleImpl": ModuleImpl,
|
|
|
|
"ModuleImplInscription": ModuleImplInscription,
|
2021-12-13 19:05:51 +01:00
|
|
|
"ndb": ndb,
|
|
|
|
"notes": notes,
|
|
|
|
"np": np,
|
2023-01-17 01:05:48 +01:00
|
|
|
"Partition": Partition,
|
2021-12-13 19:05:51 +01:00
|
|
|
"pd": pd,
|
|
|
|
"Permission": Permission,
|
|
|
|
"pp": pp,
|
2022-07-23 08:46:00 +02:00
|
|
|
"res_sem": res_sem,
|
2023-01-13 23:23:18 +01:00
|
|
|
"ResultatsSemestreBUT": ResultatsSemestreBUT,
|
2023-01-17 01:05:48 +01:00
|
|
|
"Role": Role,
|
2023-02-26 23:27:40 +01:00
|
|
|
"ScoDocSiteConfig": models.ScoDocSiteConfig,
|
2021-12-13 19:05:51 +01:00
|
|
|
"scolar": scolar,
|
2023-01-26 14:49:04 +01:00
|
|
|
"ScolarAutorisationInscription": ScolarAutorisationInscription,
|
2023-01-13 23:23:18 +01:00
|
|
|
"ScolarFormSemestreValidation": ScolarFormSemestreValidation,
|
2022-04-12 17:12:51 +02:00
|
|
|
"ScolarNews": models.ScolarNews,
|
2021-12-13 19:05:51 +01:00
|
|
|
"scu": scu,
|
2021-11-18 23:52:06 +01:00
|
|
|
"UniteEns": UniteEns,
|
2021-12-13 19:05:51 +01:00
|
|
|
"User": User,
|
|
|
|
"UserRole": UserRole,
|
2021-05-29 18:22:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-01 09:52:28 +02:00
|
|
|
# ctx.push()
|
2022-03-09 16:05:44 +01:00
|
|
|
# admin = User.query.filter_by(user_name="admin").first()
|
2021-08-01 09:52:28 +02:00
|
|
|
# login_user(admin)
|
|
|
|
|
|
|
|
|
2021-05-29 18:22:51 +02:00
|
|
|
@app.cli.command()
|
2021-11-22 22:39:05 +01:00
|
|
|
@click.option("--erase/--no-erase", default=False)
|
|
|
|
def sco_db_init(erase=False): # sco-db-init
|
2021-08-09 23:23:11 +02:00
|
|
|
"""Initialize the database.
|
|
|
|
Starts from an existing database and create all
|
|
|
|
the necessary SQL tables and functions.
|
|
|
|
"""
|
2021-08-19 23:43:14 +02:00
|
|
|
if not app.config.get("SCODOC_ADMIN_MAIL"):
|
|
|
|
sys.stderr.write(
|
|
|
|
"""La variable SCODOC_ADMIN_MAIL n'est pas positionnée: vérifier votre .env"""
|
|
|
|
)
|
|
|
|
return 100
|
2021-11-22 22:39:05 +01:00
|
|
|
initialize_scodoc_database(erase=erase)
|
2021-05-29 18:22:51 +02:00
|
|
|
|
|
|
|
|
2023-02-09 11:56:20 +01:00
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("database")
|
|
|
|
def anonymize_db(database): # anonymize-db
|
|
|
|
"""Anonymise la base de nom indiqué (et non pas la base courante!)"""
|
|
|
|
click.confirm(
|
|
|
|
f"L'anonymisation va affecter la base {database} et PERDRE beaucoup de données.\nContinuer ?",
|
|
|
|
abort=True,
|
|
|
|
)
|
|
|
|
sco_dump_db.anonymize_db(database)
|
|
|
|
click.echo(f"Base {database} pseudonymisée")
|
|
|
|
|
|
|
|
|
2021-05-29 18:22:51 +02:00
|
|
|
@app.cli.command()
|
2021-05-31 09:57:23 +02:00
|
|
|
def user_db_clear():
|
2021-08-09 23:23:11 +02:00
|
|
|
"""Erase all users and roles from the database !"""
|
2021-05-31 09:57:23 +02:00
|
|
|
click.echo("Erasing the users database !")
|
|
|
|
_clear_users_db()
|
2021-05-29 18:22:51 +02:00
|
|
|
|
|
|
|
|
2021-05-31 09:57:23 +02:00
|
|
|
def _clear_users_db():
|
2021-05-29 18:22:51 +02:00
|
|
|
"""Erase (drop) all tables of users database !"""
|
2021-08-09 23:23:11 +02:00
|
|
|
click.confirm(
|
|
|
|
"This will erase all users and roles.\nAre you sure you want to continue?",
|
|
|
|
abort=True,
|
|
|
|
)
|
2021-05-29 18:22:51 +02:00
|
|
|
db.reflect()
|
2021-08-09 23:23:11 +02:00
|
|
|
try:
|
|
|
|
db.session.query(UserRole).delete()
|
|
|
|
db.session.query(User).delete()
|
|
|
|
db.session.commit()
|
|
|
|
except:
|
|
|
|
db.session.rollback()
|
|
|
|
raise
|
2021-05-31 09:57:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("username")
|
|
|
|
@click.argument("role")
|
|
|
|
@click.argument("dept")
|
2021-08-08 09:50:10 +02:00
|
|
|
@click.option("-n", "--nom", "nom")
|
|
|
|
@click.option("-p", "--prenom", "prenom")
|
|
|
|
def user_create(username, role, dept, nom=None, prenom=None): # user-create
|
2021-05-31 09:57:23 +02:00
|
|
|
"Create a new user"
|
|
|
|
r = Role.get_named_role(role)
|
|
|
|
if not r:
|
2022-02-13 15:19:39 +01:00
|
|
|
sys.stderr.write(f"user_create: role {role} does not exist\n")
|
2021-05-31 09:57:23 +02:00
|
|
|
return 1
|
2021-06-26 21:57:54 +02:00
|
|
|
u = User.query.filter_by(user_name=username).first()
|
2021-05-31 09:57:23 +02:00
|
|
|
if u:
|
2022-02-13 15:19:39 +01:00
|
|
|
sys.stderr.write(f"user_create: user {u} already exists\n")
|
2021-05-31 09:57:23 +02:00
|
|
|
return 2
|
2021-07-05 21:56:28 +02:00
|
|
|
if dept == "@all":
|
|
|
|
dept = None
|
2021-08-08 09:50:10 +02:00
|
|
|
u = User(user_name=username, dept=dept, nom=nom, prenom=prenom)
|
2021-05-31 09:57:23 +02:00
|
|
|
u.add_role(r, dept)
|
|
|
|
db.session.add(u)
|
|
|
|
db.session.commit()
|
2022-02-13 15:19:39 +01:00
|
|
|
click.echo(f"created user, login: {u.user_name}, with role {r} in dept. {dept}")
|
|
|
|
|
|
|
|
|
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("username")
|
|
|
|
def user_delete(username): # user-delete
|
|
|
|
"Try to delete this user. Fails if it's associated to some scodoc objects."
|
|
|
|
u = User.query.filter_by(user_name=username).first()
|
|
|
|
if not u:
|
|
|
|
sys.stderr.write(f"user_delete: user {username} not found\n")
|
|
|
|
return 2
|
2024-10-18 23:12:32 +02:00
|
|
|
# Vérifie que l'utilisateur n'est pas... utilisé
|
|
|
|
# - Resp. FormSemestre (table assoc)
|
|
|
|
query = sa.select(sa.func.count()).where(
|
|
|
|
notes_formsemestre_responsables.c.responsable_id == u.id
|
|
|
|
)
|
|
|
|
nb = db.session.execute(query).scalar()
|
|
|
|
if nb > 0:
|
|
|
|
sys.stderr.write(
|
|
|
|
"\nuser_delete: erreur: utilisateur déclaré comme resp. de semestre\n"
|
|
|
|
)
|
|
|
|
return 1
|
|
|
|
# - Resp. Module
|
|
|
|
if ModuleImpl.query.filter_by(responsable_id=217).count() > 0:
|
|
|
|
sys.stderr.write(
|
|
|
|
"\nuser_delete: erreur: utilisateur déclaré comme enseignant\n"
|
|
|
|
)
|
|
|
|
return 1
|
|
|
|
# - Enseignant d'un module (table assoc)
|
|
|
|
query = sa.select(sa.func.count()).where(notes_modules_enseignants.c.ens_id == u.id)
|
|
|
|
nb = db.session.execute(query).scalar()
|
|
|
|
if nb > 0:
|
|
|
|
sys.stderr.write(
|
|
|
|
"\nuser_delete: erreur: utilisateur déclaré comme enseignant\n"
|
|
|
|
)
|
|
|
|
return 1
|
|
|
|
#
|
2022-02-13 15:19:39 +01:00
|
|
|
db.session.delete(u)
|
|
|
|
try:
|
|
|
|
db.session.commit()
|
2024-10-18 23:12:32 +02:00
|
|
|
except (sa.exc.IntegrityError, psycopg2.errors.ForeignKeyViolation):
|
2022-02-13 15:19:39 +01:00
|
|
|
sys.stderr.write(
|
|
|
|
f"""\nuser_delete: ne peux pas supprimer l'utilisateur {username}\ncar il est associé à des objets dans ScoDoc (modules, notes, ...).\n"""
|
2021-05-31 09:57:23 +02:00
|
|
|
)
|
2022-02-13 15:19:39 +01:00
|
|
|
return 1
|
|
|
|
click.echo(f"deleted user, login: {username}")
|
2021-05-31 09:57:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("username")
|
|
|
|
@click.password_option()
|
2021-07-05 21:56:28 +02:00
|
|
|
def user_password(username, password=None): # user-password
|
2021-05-31 09:57:23 +02:00
|
|
|
"Set (or change) user's password"
|
|
|
|
if not password:
|
|
|
|
sys.stderr.write("user_password: missing password")
|
|
|
|
return 1
|
2021-06-26 21:57:54 +02:00
|
|
|
u = User.query.filter_by(user_name=username).first()
|
2021-05-31 09:57:23 +02:00
|
|
|
if not u:
|
2021-09-13 16:11:33 +02:00
|
|
|
sys.stderr.write(f"user_password: user {username} does not exists\n")
|
2021-05-31 09:57:23 +02:00
|
|
|
return 1
|
|
|
|
|
|
|
|
u.set_password(password)
|
|
|
|
db.session.add(u)
|
|
|
|
db.session.commit()
|
2021-09-13 16:11:33 +02:00
|
|
|
click.echo(f"changed password for user {u}")
|
|
|
|
|
|
|
|
|
2021-09-13 23:06:42 +02:00
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("rolename")
|
|
|
|
@click.argument("permissions", nargs=-1)
|
|
|
|
def create_role(rolename, permissions): # create-role
|
|
|
|
"""Create a new role"""
|
|
|
|
# Check rolename
|
|
|
|
if not re.match(r"^[a-zA-Z0-9]+$", rolename):
|
|
|
|
sys.stderr.write(f"create_role: invalid rolename {rolename}\n")
|
|
|
|
return 1
|
|
|
|
# Check permissions
|
|
|
|
permission_list = []
|
|
|
|
for permission_name in permissions:
|
|
|
|
perm = Permission.get_by_name(permission_name)
|
|
|
|
if not perm:
|
|
|
|
sys.stderr.write(f"create_role: invalid permission name {perm}\n")
|
|
|
|
sys.stderr.write(
|
|
|
|
f"\tavailable permissions: {', '.join([ name for name in Permission.permission_by_name])}.\n"
|
|
|
|
)
|
|
|
|
return 1
|
|
|
|
permission_list.append(perm)
|
|
|
|
|
|
|
|
role = Role.query.filter_by(name=rolename).first()
|
|
|
|
if role:
|
|
|
|
sys.stderr.write(f"create_role: role {rolename} already exists\n")
|
|
|
|
return 1
|
|
|
|
|
|
|
|
role = Role(name=rolename)
|
|
|
|
for perm in permission_list:
|
|
|
|
role.add_permission(perm)
|
|
|
|
db.session.add(role)
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
2022-07-24 07:14:31 +02:00
|
|
|
@app.cli.command()
|
|
|
|
def list_roles(): # list-roles
|
|
|
|
"""List all defined roles"""
|
|
|
|
for role in Role.query:
|
|
|
|
print(role)
|
|
|
|
|
|
|
|
|
2021-09-13 16:11:33 +02:00
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("rolename")
|
|
|
|
@click.option("-a", "--add", "addpermissionname")
|
|
|
|
@click.option("-r", "--remove", "removepermissionname")
|
|
|
|
def edit_role(rolename, addpermissionname=None, removepermissionname=None): # edit-role
|
|
|
|
"""Add [-a] and/or remove [-r] a permission to/from a role.
|
|
|
|
In ScoDoc, permissions are not associated to users but to roles.
|
|
|
|
Each user has a set of roles in each departement.
|
|
|
|
|
2023-09-29 21:17:31 +02:00
|
|
|
Example: `flask edit-role -a EditApogee Ens`
|
2021-09-13 16:11:33 +02:00
|
|
|
"""
|
|
|
|
if addpermissionname:
|
2021-09-13 23:06:42 +02:00
|
|
|
perm_to_add = Permission.get_by_name(addpermissionname)
|
|
|
|
if not perm_to_add:
|
2021-09-13 16:11:33 +02:00
|
|
|
sys.stderr.write(
|
|
|
|
f"edit_role: permission {addpermissionname} does not exists\n"
|
|
|
|
)
|
|
|
|
return 1
|
|
|
|
else:
|
|
|
|
perm_to_add = None
|
|
|
|
if removepermissionname:
|
2021-09-13 23:06:42 +02:00
|
|
|
perm_to_remove = Permission.get_by_name(removepermissionname)
|
|
|
|
if not perm_to_remove:
|
2021-09-13 16:11:33 +02:00
|
|
|
sys.stderr.write(
|
|
|
|
f"edit_role: permission {removepermissionname} does not exists\n"
|
|
|
|
)
|
|
|
|
return 1
|
|
|
|
else:
|
|
|
|
perm_to_remove = None
|
|
|
|
role = Role.query.filter_by(name=rolename).first()
|
|
|
|
if not role:
|
|
|
|
sys.stderr.write(f"edit_role: role {rolename} does not exists\n")
|
|
|
|
return 1
|
|
|
|
if perm_to_add:
|
|
|
|
role.add_permission(perm_to_add)
|
|
|
|
click.echo(f"adding permission {addpermissionname} to role {rolename}")
|
|
|
|
if perm_to_remove:
|
|
|
|
role.remove_permission(perm_to_remove)
|
|
|
|
click.echo(f"removing permission {removepermissionname} from role {rolename}")
|
|
|
|
if perm_to_add or perm_to_remove:
|
|
|
|
db.session.add(role)
|
|
|
|
db.session.commit()
|
2022-07-24 15:51:13 +02:00
|
|
|
print(role)
|
2021-06-24 10:59:03 +02:00
|
|
|
|
2022-01-03 12:31:20 +01:00
|
|
|
|
2021-12-24 09:50:25 +01:00
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("rolename")
|
|
|
|
def delete_role(rolename):
|
|
|
|
"""Delete a role"""
|
|
|
|
role = Role.query.filter_by(name=rolename).first()
|
|
|
|
if role is None:
|
|
|
|
sys.stderr.write(f"delete_role: role {rolename} does not exists\n")
|
|
|
|
return 1
|
|
|
|
db.session.delete(role)
|
|
|
|
db.session.commit()
|
2021-06-24 10:59:03 +02:00
|
|
|
|
2022-01-03 12:31:20 +01:00
|
|
|
|
2021-12-21 00:04:42 +01:00
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("username")
|
|
|
|
@click.option("-d", "--dept", "dept_acronym")
|
|
|
|
@click.option("-a", "--add", "add_role_name")
|
|
|
|
@click.option("-r", "--remove", "remove_role_name")
|
|
|
|
def user_role(username, dept_acronym=None, add_role_name=None, remove_role_name=None):
|
|
|
|
"""Add or remove a role to the given user in the given dept"""
|
2022-07-24 07:14:31 +02:00
|
|
|
user: User = User.query.filter_by(user_name=username).first()
|
2021-12-21 00:04:42 +01:00
|
|
|
if not user:
|
|
|
|
sys.stderr.write(f"user_role: user {username} does not exists\n")
|
|
|
|
return 1
|
2022-07-24 15:51:13 +02:00
|
|
|
# Sans argument, affiche les rôles de l'utilisateur
|
|
|
|
if dept_acronym is None and add_role_name is None and remove_role_name is None:
|
|
|
|
print(f"Roles for user {user.user_name}")
|
|
|
|
for user_role in sorted(
|
|
|
|
user.user_roles, key=lambda ur: (ur.dept or "", ur.role.name)
|
|
|
|
):
|
|
|
|
print(f"""{user_role.dept or "tous"}:\t{user_role.role.name}""")
|
|
|
|
|
2021-12-21 00:04:42 +01:00
|
|
|
if dept_acronym:
|
|
|
|
dept = models.Departement.query.filter_by(acronym=dept_acronym).first()
|
|
|
|
if dept is None:
|
2022-07-24 15:51:13 +02:00
|
|
|
sys.stderr.write(f"Erreur: le departement {dept_acronym} n'existe pas !\n")
|
2021-12-21 00:04:42 +01:00
|
|
|
return 2
|
|
|
|
|
|
|
|
if add_role_name:
|
|
|
|
role = Role.query.filter_by(name=add_role_name).first()
|
2022-07-24 07:14:31 +02:00
|
|
|
if role is None:
|
2022-07-24 15:51:13 +02:00
|
|
|
sys.stderr.write(
|
|
|
|
f"""user_role: role {add_role_name} does not exists
|
|
|
|
(use list-roles to display existing roles)\n"""
|
|
|
|
)
|
2022-07-24 07:14:31 +02:00
|
|
|
return 2
|
2021-12-21 00:04:42 +01:00
|
|
|
user.add_role(role, dept_acronym)
|
|
|
|
if remove_role_name:
|
|
|
|
role = Role.query.filter_by(name=remove_role_name).first()
|
2022-07-24 07:14:31 +02:00
|
|
|
if role is None:
|
|
|
|
sys.stderr.write(f"user_role: role {remove_role_name} does not exists\n")
|
|
|
|
return 2
|
2021-12-21 00:04:42 +01:00
|
|
|
user_role = UserRole.query.filter(
|
|
|
|
UserRole.role == role, UserRole.user == user, UserRole.dept == dept_acronym
|
|
|
|
).first()
|
2022-08-06 22:31:41 +02:00
|
|
|
if user_role:
|
|
|
|
db.session.delete(user_role)
|
2021-12-21 00:04:42 +01:00
|
|
|
db.session.commit()
|
2024-07-29 21:43:34 +02:00
|
|
|
return 0
|
2021-12-21 00:04:42 +01:00
|
|
|
|
|
|
|
|
2024-06-21 16:34:23 +02:00
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("user_name")
|
|
|
|
@click.argument("new_user_name")
|
|
|
|
def user_change_login(user_name, new_user_name):
|
|
|
|
"""Change user's login (user_name)"""
|
|
|
|
user: User = User.query.filter_by(user_name=user_name).first()
|
|
|
|
if not user:
|
|
|
|
sys.stderr.write(f"user_change_login: user {user_name} does not exists\n")
|
|
|
|
return 1
|
|
|
|
user.change_user_name(new_user_name)
|
|
|
|
|
|
|
|
|
2022-01-18 21:38:00 +01:00
|
|
|
def abort_if_false(ctx, param, value):
|
|
|
|
if not value:
|
|
|
|
ctx.abort()
|
|
|
|
|
|
|
|
|
2021-06-24 10:59:03 +02:00
|
|
|
@app.cli.command()
|
2022-01-18 21:38:00 +01:00
|
|
|
@click.option(
|
2022-11-22 13:13:16 +01:00
|
|
|
"-y",
|
2022-01-18 21:38:00 +01:00
|
|
|
"--yes",
|
|
|
|
is_flag=True,
|
|
|
|
callback=abort_if_false,
|
|
|
|
expose_value=False,
|
2022-08-06 22:31:41 +02:00
|
|
|
prompt="""Attention: Cela va effacer toutes les données du département
|
|
|
|
(étudiants, notes, formations, etc).
|
2022-01-18 21:38:00 +01:00
|
|
|
Voulez-vous vraiment continuer ?
|
|
|
|
""",
|
|
|
|
)
|
2022-11-20 11:36:06 +01:00
|
|
|
@click.option(
|
|
|
|
"-f",
|
|
|
|
"--force",
|
|
|
|
is_flag=True,
|
|
|
|
help="ignore non-existing departement",
|
|
|
|
)
|
2021-06-24 10:59:03 +02:00
|
|
|
@click.argument("dept")
|
2022-11-20 11:36:06 +01:00
|
|
|
def delete_dept(dept, force=False): # delete-dept
|
2021-08-13 00:34:58 +02:00
|
|
|
"""Delete existing departement"""
|
2021-08-19 21:30:22 +02:00
|
|
|
from app.scodoc import notesdb as ndb
|
|
|
|
from app.scodoc import sco_dept
|
|
|
|
|
2023-08-10 19:51:42 +02:00
|
|
|
msg = ""
|
2021-08-13 00:34:58 +02:00
|
|
|
db.reflect()
|
2021-08-19 21:30:22 +02:00
|
|
|
ndb.open_db_connection()
|
2021-08-13 00:34:58 +02:00
|
|
|
d = models.Departement.query.filter_by(acronym=dept).first()
|
2022-11-20 11:36:06 +01:00
|
|
|
if d is None and not force:
|
2021-09-04 11:37:46 +02:00
|
|
|
sys.stderr.write(f"Erreur: le departement {dept} n'existe pas !\n")
|
2021-08-13 00:34:58 +02:00
|
|
|
return 2
|
2022-11-20 11:36:06 +01:00
|
|
|
elif d:
|
2023-02-09 11:56:20 +01:00
|
|
|
msg = sco_dept.delete_dept(d.id)
|
2022-11-20 11:36:06 +01:00
|
|
|
db.session.commit()
|
2023-02-09 11:56:20 +01:00
|
|
|
if msg:
|
|
|
|
print(f"Erreur:\n {msg}")
|
|
|
|
return 0 if not msg else 1
|
2021-06-24 10:59:03 +02:00
|
|
|
|
|
|
|
|
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("dept")
|
2021-08-13 09:31:49 +02:00
|
|
|
def create_dept(dept): # create-dept
|
2021-06-24 10:59:03 +02:00
|
|
|
"Create new departement"
|
2021-12-23 16:03:30 +01:00
|
|
|
_ = departements.create_dept(dept)
|
2021-06-24 10:59:03 +02:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2021-09-17 09:15:12 +02:00
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("depts", nargs=-1)
|
2022-07-24 07:14:31 +02:00
|
|
|
def list_depts(depts=""): # list-depts
|
2021-09-17 09:15:12 +02:00
|
|
|
"""If dept exists, print it, else nothing.
|
|
|
|
Called without arguments, list all depts along with their ids.
|
|
|
|
"""
|
|
|
|
for dept in models.Departement.query.order_by(models.Departement.id):
|
|
|
|
if not depts or dept.acronym in depts:
|
|
|
|
print(f"{dept.id}\t{dept.acronym}")
|
|
|
|
|
|
|
|
|
2021-10-06 13:53:09 +02:00
|
|
|
@app.cli.command()
|
|
|
|
@click.option(
|
|
|
|
"-n",
|
|
|
|
"--name",
|
|
|
|
is_flag=True,
|
|
|
|
help="show database name instead of connexion string (required for "
|
2021-11-17 10:28:51 +01:00
|
|
|
"dropdb/createdb commands)",
|
2021-10-06 13:53:09 +02:00
|
|
|
)
|
2022-11-20 11:36:06 +01:00
|
|
|
def scodoc_database(name): # scodoc-database
|
2021-10-06 13:53:09 +02:00
|
|
|
"""print the database connexion string"""
|
|
|
|
uri = app.config["SQLALCHEMY_DATABASE_URI"]
|
|
|
|
if name:
|
|
|
|
print(uri.split("/")[-1])
|
|
|
|
else:
|
|
|
|
print(uri)
|
|
|
|
|
|
|
|
|
2021-07-05 00:07:17 +02:00
|
|
|
@app.cli.command()
|
|
|
|
@with_appcontext
|
2021-08-14 18:54:32 +02:00
|
|
|
def import_scodoc7_users(): # import-scodoc7-users
|
2021-09-13 23:06:42 +02:00
|
|
|
"""Import users defined in ScoDoc7 postgresql database into ScoDoc 9
|
2021-07-05 00:07:17 +02:00
|
|
|
The old database SCOUSERS must be alive and readable by the current user.
|
2021-07-27 16:07:03 +02:00
|
|
|
This script is typically run as unix user "scodoc".
|
|
|
|
The original SCOUSERS database is left unmodified.
|
2021-07-05 00:07:17 +02:00
|
|
|
"""
|
2021-08-14 18:54:32 +02:00
|
|
|
messages = tools.import_scodoc7_user_db()
|
|
|
|
click.echo("----")
|
|
|
|
click.echo(f"import terminé: {len(messages)} warnings\n")
|
|
|
|
click.echo("\n".join(messages) + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("dept")
|
2021-08-20 09:57:38 +02:00
|
|
|
@click.argument("dept_db_name")
|
2021-08-14 18:54:32 +02:00
|
|
|
@with_appcontext
|
2021-09-16 21:42:45 +02:00
|
|
|
def import_scodoc7_dept(dept: str, dept_db_name: str = ""): # import-scodoc7-dept
|
2021-08-20 09:57:38 +02:00
|
|
|
"""Import département ScoDoc 7: dept: InfoComm, dept_db_name: SCOINFOCOMM"""
|
|
|
|
dept_db_uri = f"postgresql:///{dept_db_name}"
|
|
|
|
tools.import_scodoc7_dept(dept, dept_db_uri)
|
2021-07-29 10:30:13 +02:00
|
|
|
|
|
|
|
|
2021-09-16 21:42:45 +02:00
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("dept", default="")
|
|
|
|
@with_appcontext
|
2021-11-01 15:16:51 +01:00
|
|
|
def migrate_scodoc7_dept_archives(dept: str): # migrate-scodoc7-dept-archives
|
2021-09-16 21:42:45 +02:00
|
|
|
"""Post-migration: renomme les archives en fonction des id de ScoDoc 9"""
|
2021-11-01 15:16:51 +01:00
|
|
|
tools.migrate_scodoc7_dept_archives(dept)
|
2021-09-16 21:42:45 +02:00
|
|
|
|
|
|
|
|
2021-11-14 10:33:37 +01:00
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("dept", default="")
|
|
|
|
@with_appcontext
|
|
|
|
def migrate_scodoc7_dept_logos(dept: str = ""): # migrate-scodoc7-dept-logos
|
|
|
|
"""Post-migration: renomme les logos en fonction des id / dept de ScoDoc 9"""
|
|
|
|
tools.migrate_scodoc7_dept_logos(dept)
|
|
|
|
|
|
|
|
|
2021-11-14 10:43:55 +01:00
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("logo", default=None)
|
|
|
|
@click.argument("dept", default=None)
|
|
|
|
@with_appcontext
|
|
|
|
def localize_logo(logo: str = None, dept: str = None): # migrate-scodoc7-dept-logos
|
|
|
|
"""Make local to a dept a global logo (both logo and dept names are mandatory)"""
|
|
|
|
if logo in ["header", "footer"]:
|
|
|
|
print(
|
|
|
|
f"Can't make logo '{logo}' local: add a local version throught configuration form instead"
|
|
|
|
)
|
|
|
|
return
|
|
|
|
make_logo_local(logoname=logo, dept_name=dept)
|
|
|
|
|
|
|
|
|
2021-10-20 16:47:41 +02:00
|
|
|
@app.cli.command()
|
|
|
|
@click.argument("formsemestre_id", type=click.INT)
|
|
|
|
@click.argument("xlsfile", type=click.File("rb"))
|
|
|
|
@click.argument("zipfile", type=click.File("rb"))
|
|
|
|
def photos_import_files(formsemestre_id: int, xlsfile: str, zipfile: str):
|
2022-02-13 15:50:16 +01:00
|
|
|
"""Import des photos d'étudiants à partir d'une liste excel et d'un zip avec les images."""
|
2021-10-20 16:47:41 +02:00
|
|
|
from app.scodoc import sco_trombino, sco_photos
|
|
|
|
from app.auth.models import get_super_admin
|
|
|
|
|
2023-07-11 06:57:38 +02:00
|
|
|
formsemestre = db.session.get(FormSemestre, formsemestre_id)
|
2023-03-20 11:17:38 +01:00
|
|
|
if not formsemestre:
|
|
|
|
sys.stderr.write("photos-import-files: formsemestre_id invalide\n")
|
2021-10-20 16:47:41 +02:00
|
|
|
return 2
|
|
|
|
|
|
|
|
with app.test_request_context():
|
2023-03-20 11:17:38 +01:00
|
|
|
mapp.set_sco_dept(formsemestre.departement.acronym)
|
2021-10-20 16:47:41 +02:00
|
|
|
admin_user = get_super_admin()
|
|
|
|
login_user(admin_user)
|
|
|
|
|
2023-08-11 23:15:17 +02:00
|
|
|
def callback(etud: Identite, data, filename):
|
2022-09-02 20:56:55 +02:00
|
|
|
return sco_photos.store_photo(etud, data, filename)
|
2021-10-20 16:47:41 +02:00
|
|
|
|
|
|
|
(
|
|
|
|
ignored_zipfiles,
|
|
|
|
unmatched_files,
|
|
|
|
stored_etud_filename,
|
|
|
|
) = sco_trombino.zip_excel_import_files(
|
|
|
|
xlsfile=xlsfile,
|
|
|
|
zipfile=zipfile,
|
|
|
|
callback=callback,
|
|
|
|
filename_title="fichier_photo",
|
|
|
|
)
|
|
|
|
print(
|
|
|
|
render_template(
|
|
|
|
"scolar/photos_import_files.txt",
|
|
|
|
ignored_zipfiles=ignored_zipfiles,
|
|
|
|
unmatched_files=unmatched_files,
|
|
|
|
stored_etud_filename=stored_etud_filename,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-07-29 10:30:13 +02:00
|
|
|
@app.cli.command()
|
2022-01-08 17:38:38 +01:00
|
|
|
@click.option("--sanitize/--no-sanitize", default=False)
|
2021-07-29 10:30:13 +02:00
|
|
|
@with_appcontext
|
2022-01-08 17:38:38 +01:00
|
|
|
def clear_cache(sanitize): # clear-cache
|
2021-07-29 10:30:13 +02:00
|
|
|
"""Clear ScoDoc cache
|
|
|
|
This cache (currently Redis) is persistent between invocation
|
2022-01-08 17:38:38 +01:00
|
|
|
and it may be necessary to clear it during upgrades,
|
|
|
|
development or tests.
|
2021-07-29 10:30:13 +02:00
|
|
|
"""
|
2022-01-08 17:38:38 +01:00
|
|
|
click.echo("Flushing Redis cache...")
|
2021-08-10 12:57:38 +02:00
|
|
|
clear_scodoc_cache()
|
2022-01-08 17:38:38 +01:00
|
|
|
if sanitize:
|
|
|
|
# sanitizes all formations:
|
|
|
|
click.echo("Checking formations...")
|
|
|
|
for formation in Formation.query:
|
|
|
|
formation.sanitize_old_formation()
|
2021-09-13 23:06:42 +02:00
|
|
|
|
|
|
|
|
2022-03-09 16:05:44 +01:00
|
|
|
@app.cli.command()
|
2022-05-04 23:11:20 +02:00
|
|
|
def init_test_database(): # init-test-database
|
2022-03-09 16:05:44 +01:00
|
|
|
"""Initialise les objets en base pour les tests API
|
|
|
|
(à appliquer sur SCODOC_TEST ou SCODOC_DEV)
|
|
|
|
"""
|
|
|
|
click.echo("Initialisation base de test API...")
|
|
|
|
|
|
|
|
ctx = app.test_request_context()
|
|
|
|
ctx.push()
|
2022-03-09 18:03:18 +01:00
|
|
|
admin = User.query.filter_by(user_name="admin").first()
|
|
|
|
login_user(admin)
|
2022-03-09 16:05:44 +01:00
|
|
|
create_test_api_database.init_test_database()
|
|
|
|
|
|
|
|
|
2021-09-13 23:06:42 +02:00
|
|
|
def recursive_help(cmd, parent=None):
|
|
|
|
ctx = click.core.Context(cmd, info_name=cmd.name, parent=parent)
|
|
|
|
print(cmd.get_help(ctx))
|
|
|
|
print()
|
|
|
|
commands = getattr(cmd, "commands", {})
|
|
|
|
for sub in commands.values():
|
|
|
|
recursive_help(sub, ctx)
|
|
|
|
|
|
|
|
|
2022-07-13 12:14:00 +02:00
|
|
|
@app.cli.command()
|
|
|
|
def entreprises_reset_db():
|
|
|
|
"""Remet a zéro les tables du module relations entreprises"""
|
|
|
|
click.confirm(
|
2022-07-13 16:53:54 +02:00
|
|
|
"This will erase all data from the blueprint 'entreprises'.\nAre you sure you want to continue?",
|
2022-07-13 12:14:00 +02:00
|
|
|
abort=True,
|
|
|
|
)
|
|
|
|
db.reflect()
|
|
|
|
try:
|
|
|
|
entreprises_reset_database()
|
|
|
|
except:
|
|
|
|
db.session.rollback()
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
2021-09-13 23:06:42 +02:00
|
|
|
@app.cli.command()
|
|
|
|
def dumphelp():
|
2022-02-13 15:19:39 +01:00
|
|
|
"""Génère la page d'aide complète pour la doc."""
|
2021-09-13 23:06:42 +02:00
|
|
|
recursive_help(app.cli)
|
2021-10-04 21:57:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
@app.cli.command()
|
|
|
|
@click.option("-h", "--host", default="127.0.0.1", help="The interface to bind to.")
|
|
|
|
@click.option("-p", "--port", default=5000, help="The port to bind to.")
|
|
|
|
@click.option(
|
|
|
|
"--length",
|
|
|
|
default=25,
|
|
|
|
help="Number of functions to include in the profiler report.",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"--profile-dir", default=None, help="Directory where profiler data files are saved."
|
|
|
|
)
|
|
|
|
def profile(host, port, length, profile_dir):
|
|
|
|
"""Start the application under the code profiler."""
|
|
|
|
from werkzeug.middleware.profiler import ProfilerMiddleware
|
|
|
|
from werkzeug.serving import run_simple
|
|
|
|
|
|
|
|
app.wsgi_app = ProfilerMiddleware(
|
|
|
|
app.wsgi_app, restrictions=[length], profile_dir=profile_dir
|
|
|
|
)
|
|
|
|
run_simple(
|
|
|
|
host, port, app, use_debugger=False
|
|
|
|
) # use run_simple instead of app.run()
|
2023-04-17 15:43:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
# <== Gestion de l'assiduité ==>
|
|
|
|
|
|
|
|
|
|
|
|
@app.cli.command()
|
|
|
|
@click.option(
|
|
|
|
"-d", "--dept", help="Restreint la migration au dept sélectionné (ACRONYME)"
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"-m",
|
|
|
|
"--morning",
|
|
|
|
help="Spécifie l'heure de début des cours format `hh:mm`",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"-n",
|
|
|
|
"--noon",
|
2023-08-22 16:18:58 +02:00
|
|
|
help="Spécifie l'heure de fin du matin format `hh:mm`",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"-a",
|
|
|
|
"--afternoon",
|
|
|
|
help="Spécifie l'heure de début de l'après-midi format `hh:mm` valeur identique à --noon si non spécifié",
|
2023-04-17 15:43:58 +02:00
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"-e",
|
|
|
|
"--evening",
|
|
|
|
help="Spécifie l'heure de fin des cours format `hh:mm`",
|
|
|
|
)
|
|
|
|
@with_appcontext
|
|
|
|
def migrate_abs_to_assiduites(
|
2023-08-22 16:18:58 +02:00
|
|
|
dept: str = None,
|
|
|
|
morning: str = None,
|
|
|
|
noon: str = None,
|
|
|
|
afternoon: str = None,
|
|
|
|
evening: str = None,
|
2023-04-17 15:43:58 +02:00
|
|
|
): # migrate-abs-to-assiduites
|
|
|
|
"""Permet de migrer les absences vers le nouveau module d'assiduités"""
|
2023-08-22 16:18:58 +02:00
|
|
|
tools.migrate_abs_to_assiduites(dept, morning, noon, afternoon, evening)
|
2023-07-18 08:35:18 +02:00
|
|
|
# import cProfile
|
|
|
|
# cProfile.runctx(
|
|
|
|
# f"tools.migrate_abs_to_assiduites({dept})",
|
|
|
|
# {"tools": tools},
|
|
|
|
# {},
|
|
|
|
# "migration-nimes",
|
|
|
|
# )
|
2023-04-17 15:43:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
@app.cli.command()
|
|
|
|
@click.option(
|
|
|
|
"-d", "--dept", help="Restreint la suppression au dept sélectionné (ACRONYME)"
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"-a",
|
|
|
|
"--assiduites",
|
|
|
|
is_flag=True,
|
|
|
|
help="Supprime les assiduités de scodoc",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"-j",
|
|
|
|
"--justificatifs",
|
|
|
|
is_flag=True,
|
|
|
|
help="Supprime les justificatifs de scodoc",
|
|
|
|
)
|
|
|
|
@with_appcontext
|
|
|
|
def downgrade_assiduites_module(
|
|
|
|
dept: str = None, assiduites: bool = False, justificatifs: bool = False
|
|
|
|
):
|
|
|
|
"""Supprime les assiduites et/ou les justificatifs de tous les départements ou du département sélectionné"""
|
|
|
|
tools.downgrade_module(dept, assiduites, justificatifs)
|
2024-01-02 23:05:08 +01:00
|
|
|
|
|
|
|
|
|
|
|
@app.cli.command()
|
|
|
|
def generate_ens_calendars(): # generate-ens-calendars
|
|
|
|
"""Génère les calendrier enseignants à partir des ics semestres"""
|
|
|
|
from tools.edt import edt_ens
|
|
|
|
|
|
|
|
edt_ens.generate_ens_calendars()
|
2024-06-20 15:48:11 +02:00
|
|
|
|
|
|
|
|
2024-07-23 16:49:11 +02:00
|
|
|
@app.cli.command()
|
|
|
|
@click.option(
|
|
|
|
"-e",
|
|
|
|
"--endpoint",
|
|
|
|
default="api.",
|
|
|
|
help="Endpoint à partir duquel générer la documentation des routes",
|
|
|
|
)
|
|
|
|
@with_appcontext
|
|
|
|
def gen_api_doc(endpoint): # gen-api-map
|
|
|
|
"""Génère la documentation des routes de l'API."""
|
|
|
|
tools.gen_api_doc(app, endpoint_start=endpoint)
|