forked from ScoDoc/DocScoDoc
maj
This commit is contained in:
commit
aed2d6ce10
29
README.md
29
README.md
@ -106,13 +106,15 @@ Ou avec couverture (`pip install pytest-cov`)
|
|||||||
|
|
||||||
#### Utilisation des tests unitaires pour initialiser la base de dev
|
#### Utilisation des tests unitaires pour initialiser la base de dev
|
||||||
On peut aussi utiliser les tests unitaires pour mettre la base
|
On peut aussi utiliser les tests unitaires pour mettre la base
|
||||||
de données de développement dans un état connu, par exemple pour éviter de recréer à la main étudianst et semestres quand on développe.
|
de données de développement dans un état connu, par exemple pour éviter de
|
||||||
|
recréer à la main étudiants et semestres quand on développe.
|
||||||
|
|
||||||
Il suffit de positionner une variable d'environnement indiquant la BD utilisée par les tests:
|
Il suffit de positionner une variable d'environnement indiquant la BD utilisée par les tests:
|
||||||
|
|
||||||
export SCODOC_TEST_DATABASE_URI=postgresql:///SCODOC_DEV
|
export SCODOC_TEST_DATABASE_URI=postgresql:///SCODOC_DEV
|
||||||
|
|
||||||
puis de les lancer normalement, par exemple:
|
(si elle n'existe pas, voir plus loin pour la créer) puis de les lancer
|
||||||
|
normalement, par exemple:
|
||||||
|
|
||||||
pytest tests/unit/test_sco_basic.py
|
pytest tests/unit/test_sco_basic.py
|
||||||
|
|
||||||
@ -133,7 +135,8 @@ On utilise SQLAlchemy avec Alembic et Flask-Migrate.
|
|||||||
|
|
||||||
Ne pas oublier de commiter les migrations (`git add migrations` ...).
|
Ne pas oublier de commiter les migrations (`git add migrations` ...).
|
||||||
|
|
||||||
Mémo pour développeurs: séquence re-création d'une base:
|
Mémo pour développeurs: séquence re-création d'une base (vérifiez votre `.env`
|
||||||
|
ou variables d'environnement pour interroger la bonne base !).
|
||||||
|
|
||||||
dropdb SCODOC_DEV
|
dropdb SCODOC_DEV
|
||||||
tools/create_database.sh SCODOC_DEV # créé base SQL
|
tools/create_database.sh SCODOC_DEV # créé base SQL
|
||||||
@ -148,7 +151,25 @@ Si la base utilisée pour les dev n'est plus en phase avec les scripts de
|
|||||||
migration, utiliser les commandes `flask db history`et `flask db stamp`pour se
|
migration, utiliser les commandes `flask db history`et `flask db stamp`pour se
|
||||||
positionner à la bonne étape.
|
positionner à la bonne étape.
|
||||||
|
|
||||||
# Paquet debian 11
|
### Profiling
|
||||||
|
|
||||||
|
Sur une machine de DEV, lancer
|
||||||
|
|
||||||
|
flask profile --host 0.0.0.0 --length 32 --profile-dir /opt/scodoc-data
|
||||||
|
|
||||||
|
le fichier `.prof` sera alors écrit dans `/opt/scoidoc-data` (on peut aussi utiliser `/tmp`).
|
||||||
|
|
||||||
|
Pour la visualisation, [snakeviz](https://jiffyclub.github.io/snakeviz/) est bien:
|
||||||
|
|
||||||
|
pip install snakeviz
|
||||||
|
|
||||||
|
puis
|
||||||
|
|
||||||
|
snakeviz -s --hostname 0.0.0.0 -p 5555 /opt/scodoc-data/GET.ScoDoc......prof
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Paquet Debian 11
|
||||||
|
|
||||||
Les scripts associés au paquet Debian (.deb) sont dans `tools/debian`. Le plus
|
Les scripts associés au paquet Debian (.deb) sont dans `tools/debian`. Le plus
|
||||||
important est `postinst`qui se charge de configurer le système (install ou
|
important est `postinst`qui se charge de configurer le système (install ou
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# -*- coding: UTF-8 -*
|
# -*- coding: UTF-8 -*
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
|
|
||||||
|
import datetime
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
@ -24,7 +25,12 @@ from flask_moment import Moment
|
|||||||
from flask_caching import Cache
|
from flask_caching import Cache
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError, APIInvalidParams
|
from app.scodoc.sco_exceptions import (
|
||||||
|
AccessDenied,
|
||||||
|
ScoGenError,
|
||||||
|
ScoValueError,
|
||||||
|
APIInvalidParams,
|
||||||
|
)
|
||||||
from config import DevConfig
|
from config import DevConfig
|
||||||
import sco_version
|
import sco_version
|
||||||
|
|
||||||
@ -50,10 +56,21 @@ def handle_sco_value_error(exc):
|
|||||||
return render_template("sco_value_error.html", exc=exc), 404
|
return render_template("sco_value_error.html", exc=exc), 404
|
||||||
|
|
||||||
|
|
||||||
|
def handle_access_denied(exc):
|
||||||
|
return render_template("error_access_denied.html", exc=exc), 403
|
||||||
|
|
||||||
|
|
||||||
def internal_server_error(e):
|
def internal_server_error(e):
|
||||||
"""Bugs scodoc, erreurs 500"""
|
"""Bugs scodoc, erreurs 500"""
|
||||||
# note that we set the 500 status explicitly
|
# note that we set the 500 status explicitly
|
||||||
return render_template("error_500.html", SCOVERSION=sco_version.SCOVERSION), 500
|
return (
|
||||||
|
render_template(
|
||||||
|
"error_500.html",
|
||||||
|
SCOVERSION=sco_version.SCOVERSION,
|
||||||
|
date=datetime.datetime.now().isoformat(),
|
||||||
|
),
|
||||||
|
500,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def handle_invalid_usage(error):
|
def handle_invalid_usage(error):
|
||||||
@ -93,6 +110,10 @@ class LogRequestFormatter(logging.Formatter):
|
|||||||
record.url = None
|
record.url = None
|
||||||
record.remote_addr = None
|
record.remote_addr = None
|
||||||
record.sco_user = current_user
|
record.sco_user = current_user
|
||||||
|
if has_request_context():
|
||||||
|
record.sco_admin_mail = current_app.config["SCODOC_ADMIN_MAIL"]
|
||||||
|
else:
|
||||||
|
record.sco_admin_mail = "(pas de requête)"
|
||||||
|
|
||||||
return super().format(record)
|
return super().format(record)
|
||||||
|
|
||||||
@ -121,6 +142,10 @@ class LogExceptionFormatter(logging.Formatter):
|
|||||||
record.http_params = None
|
record.http_params = None
|
||||||
record.sco_user = current_user
|
record.sco_user = current_user
|
||||||
|
|
||||||
|
if has_request_context():
|
||||||
|
record.sco_admin_mail = current_app.config["SCODOC_ADMIN_MAIL"]
|
||||||
|
else:
|
||||||
|
record.sco_admin_mail = "(pas de requête)"
|
||||||
return super().format(record)
|
return super().format(record)
|
||||||
|
|
||||||
|
|
||||||
@ -165,6 +190,7 @@ def create_app(config_class=DevConfig):
|
|||||||
|
|
||||||
app.register_error_handler(ScoGenError, handle_sco_value_error)
|
app.register_error_handler(ScoGenError, handle_sco_value_error)
|
||||||
app.register_error_handler(ScoValueError, handle_sco_value_error)
|
app.register_error_handler(ScoValueError, handle_sco_value_error)
|
||||||
|
app.register_error_handler(AccessDenied, handle_access_denied)
|
||||||
app.register_error_handler(500, internal_server_error)
|
app.register_error_handler(500, internal_server_error)
|
||||||
app.register_error_handler(503, postgresql_server_error)
|
app.register_error_handler(503, postgresql_server_error)
|
||||||
app.register_error_handler(APIInvalidParams, handle_invalid_usage)
|
app.register_error_handler(APIInvalidParams, handle_invalid_usage)
|
||||||
@ -197,12 +223,14 @@ def create_app(config_class=DevConfig):
|
|||||||
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
|
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
|
||||||
"%(levelname)s: %(message)s"
|
"%(levelname)s: %(message)s"
|
||||||
)
|
)
|
||||||
|
# les champs additionnels sont définis dans LogRequestFormatter
|
||||||
scodoc_exc_formatter = LogExceptionFormatter(
|
scodoc_exc_formatter = LogExceptionFormatter(
|
||||||
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
|
"[%(asctime)s] %(sco_user)s@%(remote_addr)s requested %(url)s\n"
|
||||||
"%(levelname)s: %(message)s\n"
|
"%(levelname)s: %(message)s\n"
|
||||||
"Referrer: %(http_referrer)s\n"
|
"Referrer: %(http_referrer)s\n"
|
||||||
"Method: %(http_method)s\n"
|
"Method: %(http_method)s\n"
|
||||||
"Params: %(http_params)s\n"
|
"Params: %(http_params)s\n"
|
||||||
|
"Admin mail: %(sco_admin_mail)s\n"
|
||||||
)
|
)
|
||||||
if not app.testing:
|
if not app.testing:
|
||||||
if not app.debug:
|
if not app.debug:
|
||||||
@ -259,15 +287,19 @@ def create_app(config_class=DevConfig):
|
|||||||
)
|
)
|
||||||
# ---- INITIALISATION SPECIFIQUES A SCODOC
|
# ---- INITIALISATION SPECIFIQUES A SCODOC
|
||||||
from app.scodoc import sco_bulletins_generator
|
from app.scodoc import sco_bulletins_generator
|
||||||
from app.scodoc.sco_bulletins_example import BulletinGeneratorExample
|
|
||||||
from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy
|
from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy
|
||||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||||
from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC
|
from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC
|
||||||
|
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorExample)
|
# l'ordre est important, le premeir sera le "défaut" pour les nouveaux départements.
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
|
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
|
||||||
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
|
||||||
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
|
||||||
|
if app.testing or app.debug:
|
||||||
|
from app.scodoc.sco_bulletins_example import BulletinGeneratorExample
|
||||||
|
|
||||||
|
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorExample)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
@ -422,7 +422,7 @@ class UserRole(db.Model):
|
|||||||
|
|
||||||
|
|
||||||
def get_super_admin():
|
def get_super_admin():
|
||||||
"""L'utilisateur admin (où le premier, s'il y en a plusieurs).
|
"""L'utilisateur admin (ou le premier, s'il y en a plusieurs).
|
||||||
Utilisé par les tests unitaires et le script de migration.
|
Utilisé par les tests unitaires et le script de migration.
|
||||||
"""
|
"""
|
||||||
admin_role = Role.query.filter_by(name="SuperAdmin").first()
|
admin_role = Role.query.filter_by(name="SuperAdmin").first()
|
||||||
|
@ -273,6 +273,7 @@ class NotesModuleImplInscription(db.Model):
|
|||||||
"""Inscription à un module (etudiants,moduleimpl)"""
|
"""Inscription à un module (etudiants,moduleimpl)"""
|
||||||
|
|
||||||
__tablename__ = "notes_moduleimpl_inscription"
|
__tablename__ = "notes_moduleimpl_inscription"
|
||||||
|
__table_args__ = (db.UniqueConstraint("moduleimpl_id", "etudid"),)
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
moduleimpl_inscription_id = db.synonym("id")
|
moduleimpl_inscription_id = db.synonym("id")
|
||||||
|
@ -40,7 +40,7 @@ from app.scodoc.sco_permissions import Permission
|
|||||||
def sidebar_common():
|
def sidebar_common():
|
||||||
"partie commune à toutes les sidebar"
|
"partie commune à toutes les sidebar"
|
||||||
H = [
|
H = [
|
||||||
f"""<a class="scodoc_title" href="about">ScoDoc 9</a>
|
f"""<a class="scodoc_title" href="{url_for("scodoc.about", scodoc_dept=g.scodoc_dept)}">ScoDoc 9</a>
|
||||||
<div id="authuser"><a id="authuserlink" href="{
|
<div id="authuser"><a id="authuserlink" href="{
|
||||||
url_for("users.user_info_page",
|
url_for("users.user_info_page",
|
||||||
scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
scodoc_dept=g.scodoc_dept, user_name=current_user.user_name)
|
||||||
|
@ -1265,7 +1265,7 @@ class NotesTable(object):
|
|||||||
),
|
),
|
||||||
self.get_nom_long(etudid),
|
self.get_nom_long(etudid),
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.formsemestre_edit_uecoefs",
|
"notes.formsemestre_edit_uecoefs",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=self.formsemestre_id,
|
formsemestre_id=self.formsemestre_id,
|
||||||
err_ue_id=ue["ue_id"],
|
err_ue_id=ue["ue_id"],
|
||||||
|
@ -136,7 +136,8 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
|
|
||||||
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
prefs = sco_preferences.SemPreferences(formsemestre_id)
|
||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > toutes notes
|
||||||
|
if not nt.get_etud_etat(etudid):
|
||||||
|
raise ScoValueError("Etudiant non inscrit à ce semestre")
|
||||||
I = scu.DictDefault(defaultvalue="")
|
I = scu.DictDefault(defaultvalue="")
|
||||||
I["etudid"] = etudid
|
I["etudid"] = etudid
|
||||||
I["formsemestre_id"] = formsemestre_id
|
I["formsemestre_id"] = formsemestre_id
|
||||||
@ -774,8 +775,8 @@ def formsemestre_bulletinetud(
|
|||||||
except:
|
except:
|
||||||
sco_etud.log_unknown_etud()
|
sco_etud.log_unknown_etud()
|
||||||
raise ScoValueError("étudiant inconnu")
|
raise ScoValueError("étudiant inconnu")
|
||||||
|
# API, donc erreurs admises en ScoValueError
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||||
|
|
||||||
bulletin = do_formsemestre_bulletinetud(
|
bulletin = do_formsemestre_bulletinetud(
|
||||||
formsemestre_id,
|
formsemestre_id,
|
||||||
|
@ -348,7 +348,7 @@ def do_moduleimpl_moyennes(nt, mod):
|
|||||||
if etudid in eval_rattr["notes"]:
|
if etudid in eval_rattr["notes"]:
|
||||||
note = eval_rattr["notes"][etudid]["value"]
|
note = eval_rattr["notes"][etudid]["value"]
|
||||||
if note != None and note != NOTES_NEUTRALISE and note != NOTES_ATTENTE:
|
if note != None and note != NOTES_NEUTRALISE and note != NOTES_ATTENTE:
|
||||||
if isinstance(R[etudid], float):
|
if not isinstance(R[etudid], float):
|
||||||
R[etudid] = note
|
R[etudid] = note
|
||||||
else:
|
else:
|
||||||
note_sur_20 = note * 20.0 / eval_rattr["note_max"]
|
note_sur_20 = note * 20.0 / eval_rattr["note_max"]
|
||||||
|
@ -48,9 +48,19 @@ import sco_version
|
|||||||
|
|
||||||
|
|
||||||
def report_debouche_date(start_year=None, format="html"):
|
def report_debouche_date(start_year=None, format="html"):
|
||||||
"""Rapport (table) pour les débouchés des étudiants sortis à partir de l'année indiquée."""
|
"""Rapport (table) pour les débouchés des étudiants sortis
|
||||||
|
à partir de l'année indiquée.
|
||||||
|
"""
|
||||||
if not start_year:
|
if not start_year:
|
||||||
return report_debouche_ask_date()
|
return report_debouche_ask_date("Année de début de la recherche")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
start_year = int(start_year)
|
||||||
|
except ValueError:
|
||||||
|
return report_debouche_ask_date(
|
||||||
|
"Année invalide. Année de début de la recherche"
|
||||||
|
)
|
||||||
|
|
||||||
if format == "xls":
|
if format == "xls":
|
||||||
keep_numeric = True # pas de conversion des notes en strings
|
keep_numeric = True # pas de conversion des notes en strings
|
||||||
else:
|
else:
|
||||||
@ -96,8 +106,9 @@ def get_etudids_with_debouche(start_year):
|
|||||||
FROM notes_formsemestre_inscription i, notes_formsemestre s, itemsuivi it
|
FROM notes_formsemestre_inscription i, notes_formsemestre s, itemsuivi it
|
||||||
WHERE i.etudid = it.etudid
|
WHERE i.etudid = it.etudid
|
||||||
AND i.formsemestre_id = s.id AND s.date_fin >= %(start_date)s
|
AND i.formsemestre_id = s.id AND s.date_fin >= %(start_date)s
|
||||||
|
AND s.dept_id = %(dept_id)s
|
||||||
""",
|
""",
|
||||||
{"start_date": start_date},
|
{"start_date": start_date, "dept_id": g.scodoc_dept_id},
|
||||||
)
|
)
|
||||||
|
|
||||||
return [x["etudid"] for x in r]
|
return [x["etudid"] for x in r]
|
||||||
@ -193,15 +204,16 @@ def table_debouche_etudids(etudids, keep_numeric=True):
|
|||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
|
||||||
def report_debouche_ask_date():
|
def report_debouche_ask_date(msg: str) -> str:
|
||||||
"""Formulaire demande date départ"""
|
"""Formulaire demande date départ"""
|
||||||
return (
|
return f"""{html_sco_header.sco_header()}
|
||||||
html_sco_header.sco_header()
|
<h2>Table des débouchés des étudiants</h2>
|
||||||
+ """<form method="GET">
|
<form method="GET">
|
||||||
Date de départ de la recherche: <input type="text" name="start_year" value="" size=10/>
|
{msg}
|
||||||
</form>"""
|
<input type="text" name="start_year" value="" size=10/>
|
||||||
+ html_sco_header.sco_footer()
|
</form>
|
||||||
)
|
{html_sco_header.sco_footer()}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
|
@ -48,6 +48,7 @@ from app.scodoc import sco_users
|
|||||||
|
|
||||||
def index_html(showcodes=0, showsemtable=0):
|
def index_html(showcodes=0, showsemtable=0):
|
||||||
"Page accueil département (liste des semestres)"
|
"Page accueil département (liste des semestres)"
|
||||||
|
showcodes = int(showcodes)
|
||||||
showsemtable = int(showsemtable)
|
showsemtable = int(showsemtable)
|
||||||
H = []
|
H = []
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ def index_html(showcodes=0, showsemtable=0):
|
|||||||
# Responsable de formation:
|
# Responsable de formation:
|
||||||
sco_formsemestre.sem_set_responsable_name(sem)
|
sco_formsemestre.sem_set_responsable_name(sem)
|
||||||
|
|
||||||
if showcodes == "1":
|
if showcodes:
|
||||||
sem["tmpcode"] = "<td><tt>%s</tt></td>" % sem["formsemestre_id"]
|
sem["tmpcode"] = "<td><tt>%s</tt></td>" % sem["formsemestre_id"]
|
||||||
else:
|
else:
|
||||||
sem["tmpcode"] = ""
|
sem["tmpcode"] = ""
|
||||||
@ -126,7 +127,7 @@ def index_html(showcodes=0, showsemtable=0):
|
|||||||
"""
|
"""
|
||||||
% sco_preferences.get_preference("DeptName")
|
% sco_preferences.get_preference("DeptName")
|
||||||
)
|
)
|
||||||
H.append(_sem_table_gt(sems).html())
|
H.append(_sem_table_gt(sems, showcodes=showcodes).html())
|
||||||
H.append("</table>")
|
H.append("</table>")
|
||||||
if not showsemtable:
|
if not showsemtable:
|
||||||
H.append(
|
H.append(
|
||||||
|
@ -845,6 +845,7 @@ def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None):
|
|||||||
"""
|
"""
|
||||||
from app.scodoc import sco_formations
|
from app.scodoc import sco_formations
|
||||||
|
|
||||||
|
ue_code = str(ue_code)
|
||||||
if ue_id:
|
if ue_id:
|
||||||
ue = do_ue_list(args={"ue_id": ue_id})[0]
|
ue = do_ue_list(args={"ue_id": ue_id})[0]
|
||||||
if not ue_code:
|
if not ue_code:
|
||||||
|
@ -640,7 +640,7 @@ def view_apo_csv_delete(etape_apo="", semset_id="", dialog_confirmed=False):
|
|||||||
if not semset_id:
|
if not semset_id:
|
||||||
raise ValueError("invalid null semset_id")
|
raise ValueError("invalid null semset_id")
|
||||||
semset = sco_semset.SemSet(semset_id=semset_id)
|
semset = sco_semset.SemSet(semset_id=semset_id)
|
||||||
dest_url = "apo_semset_maq_status?semset_id=" + semset_id
|
dest_url = f"apo_semset_maq_status?semset_id={semset_id}"
|
||||||
if not dialog_confirmed:
|
if not dialog_confirmed:
|
||||||
return scu.confirm_dialog(
|
return scu.confirm_dialog(
|
||||||
"""<h2>Confirmer la suppression du fichier étape <tt>%s</tt>?</h2>
|
"""<h2>Confirmer la suppression du fichier étape <tt>%s</tt>?</h2>
|
||||||
|
@ -655,7 +655,7 @@ def log_unknown_etud():
|
|||||||
|
|
||||||
|
|
||||||
def get_etud_info(etudid=False, code_nip=False, filled=False) -> list:
|
def get_etud_info(etudid=False, code_nip=False, filled=False) -> list:
|
||||||
"""infos sur un etudiant (API). If not foud, returns empty list.
|
"""infos sur un etudiant (API). If not found, returns empty list.
|
||||||
On peut specifier etudid ou code_nip
|
On peut specifier etudid ou code_nip
|
||||||
ou bien cherche dans les argumenst de la requête courante:
|
ou bien cherche dans les argumenst de la requête courante:
|
||||||
etudid, code_nip, code_ine (dans cet ordre).
|
etudid, code_nip, code_ine (dans cet ordre).
|
||||||
@ -671,6 +671,19 @@ def get_etud_info(etudid=False, code_nip=False, filled=False) -> list:
|
|||||||
return etud
|
return etud
|
||||||
|
|
||||||
|
|
||||||
|
# Optim par cache local, utilité non prouvée mais
|
||||||
|
# on s'oriente vers un cahce persistent dans Redis ou bien l'utilisation de NT
|
||||||
|
# def get_etud_info_filled_by_etudid(etudid, cnx=None) -> dict:
|
||||||
|
# """Infos sur un étudiant, avec cache local à la requête"""
|
||||||
|
# if etudid in g.stored_etud_info:
|
||||||
|
# return g.stored_etud_info[etudid]
|
||||||
|
# cnx = cnx or ndb.GetDBConnexion()
|
||||||
|
# etud = etudident_list(cnx, args={"etudid": etudid})
|
||||||
|
# fill_etuds_info(etud)
|
||||||
|
# g.stored_etud_info[etudid] = etud[0]
|
||||||
|
# return etud[0]
|
||||||
|
|
||||||
|
|
||||||
def create_etud(cnx, args={}):
|
def create_etud(cnx, args={}):
|
||||||
"""Creation d'un étudiant. génère aussi évenement et "news".
|
"""Creation d'un étudiant. génère aussi évenement et "news".
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ from enum import Enum
|
|||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
import openpyxl.utils.datetime
|
import openpyxl.utils.datetime
|
||||||
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL
|
from openpyxl.styles.numbers import FORMAT_NUMBER_00, FORMAT_GENERAL, FORMAT_DATE_DDMMYY
|
||||||
from openpyxl.comments import Comment
|
from openpyxl.comments import Comment
|
||||||
from openpyxl import Workbook, load_workbook
|
from openpyxl import Workbook, load_workbook
|
||||||
from openpyxl.cell import WriteOnlyCell
|
from openpyxl.cell import WriteOnlyCell
|
||||||
@ -65,10 +65,16 @@ class COLORS(Enum):
|
|||||||
|
|
||||||
|
|
||||||
def xldate_as_datetime(xldate, datemode=0):
|
def xldate_as_datetime(xldate, datemode=0):
|
||||||
"""Conversion d'une date Excel en date
|
"""Conversion d'une date Excel en datetime python
|
||||||
|
Deux formats de chaîne acceptés:
|
||||||
|
* JJ/MM/YYYY (chaîne naïve)
|
||||||
|
* Date ISO (valeur de type date lue dans la feuille)
|
||||||
Peut lever une ValueError
|
Peut lever une ValueError
|
||||||
"""
|
"""
|
||||||
return openpyxl.utils.datetime.from_ISO8601(xldate)
|
try:
|
||||||
|
return datetime.datetime.strptime(xldate, "%d/%m/%Y")
|
||||||
|
except:
|
||||||
|
return openpyxl.utils.datetime.from_ISO8601(xldate)
|
||||||
|
|
||||||
|
|
||||||
def adjust_sheetname(sheet_name):
|
def adjust_sheetname(sheet_name):
|
||||||
@ -283,10 +289,6 @@ class ScoExcelSheet:
|
|||||||
style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
|
style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié
|
||||||
"""
|
"""
|
||||||
cell = WriteOnlyCell(self.ws, value or "")
|
cell = WriteOnlyCell(self.ws, value or "")
|
||||||
if not (isinstance(value, int) or isinstance(value, float)):
|
|
||||||
cell.data_type = "s"
|
|
||||||
# if style is not None and "fill" in style:
|
|
||||||
# toto()
|
|
||||||
if style is None:
|
if style is None:
|
||||||
style = self.default_style
|
style = self.default_style
|
||||||
if "font" in style:
|
if "font" in style:
|
||||||
@ -308,6 +310,14 @@ class ScoExcelSheet:
|
|||||||
lines = comment.splitlines()
|
lines = comment.splitlines()
|
||||||
cell.comment.width = 7 * max([len(line) for line in lines])
|
cell.comment.width = 7 * max([len(line) for line in lines])
|
||||||
cell.comment.height = 20 * len(lines)
|
cell.comment.height = 20 * len(lines)
|
||||||
|
# test datatype at the end so that datetime format may be overwritten
|
||||||
|
if isinstance(value, datetime.date):
|
||||||
|
cell.data_type = "d"
|
||||||
|
cell.number_format = FORMAT_DATE_DDMMYY
|
||||||
|
elif isinstance(value, int) or isinstance(value, float):
|
||||||
|
cell.data_type = "n"
|
||||||
|
else:
|
||||||
|
cell.data_type = "s"
|
||||||
return cell
|
return cell
|
||||||
|
|
||||||
def make_row(self, values: list, style=None, comments=None):
|
def make_row(self, values: list, style=None, comments=None):
|
||||||
@ -568,10 +578,9 @@ def excel_bytes_to_list(bytes_content):
|
|||||||
return _excel_to_list(filelike)
|
return _excel_to_list(filelike)
|
||||||
except:
|
except:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
|
"""Le fichier xlsx attendu n'est pas lisible !
|
||||||
|
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ..)
|
||||||
"""
|
"""
|
||||||
scolars_import_excel_file: un contenu xlsx semble corrompu!
|
|
||||||
peut-être avez vous fourni un fichier au mauvais format (txt, xls, ..)
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -580,10 +589,9 @@ def excel_file_to_list(filename):
|
|||||||
return _excel_to_list(filename)
|
return _excel_to_list(filename)
|
||||||
except:
|
except:
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
"""scolars_import_excel_file: un contenu xlsx
|
"""Le fichier xlsx attendu n'est pas lisible !
|
||||||
semble corrompu !
|
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...)
|
||||||
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...)
|
"""
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,10 +45,6 @@ class InvalidEtudId(NoteProcessError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AccessDenied(ScoException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidNoteValue(ScoException):
|
class InvalidNoteValue(ScoException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -92,6 +88,10 @@ class ScoGenError(ScoException):
|
|||||||
ScoException.__init__(self, msg)
|
ScoException.__init__(self, msg)
|
||||||
|
|
||||||
|
|
||||||
|
class AccessDenied(ScoGenError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ScoInvalidDateError(ScoValueError):
|
class ScoInvalidDateError(ScoValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -93,16 +93,21 @@ _formsemestreEditor = ndb.EditableTable(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_formsemestre(formsemestre_id):
|
def get_formsemestre(formsemestre_id, raise_soft_exc=False):
|
||||||
"list ONE formsemestre"
|
"list ONE formsemestre"
|
||||||
|
if formsemestre_id in g.stored_get_formsemestre:
|
||||||
|
return g.stored_get_formsemestre[formsemestre_id]
|
||||||
if not isinstance(formsemestre_id, int):
|
if not isinstance(formsemestre_id, int):
|
||||||
raise ValueError("formsemestre_id must be an integer !")
|
raise ValueError("formsemestre_id must be an integer !")
|
||||||
try:
|
sems = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})
|
||||||
sem = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})[0]
|
if not sems:
|
||||||
return sem
|
|
||||||
except:
|
|
||||||
log("get_formsemestre: invalid formsemestre_id (%s)" % formsemestre_id)
|
log("get_formsemestre: invalid formsemestre_id (%s)" % formsemestre_id)
|
||||||
raise
|
if raise_soft_exc:
|
||||||
|
raise ScoValueError(f"semestre {formsemestre_id} inconnu !")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"semestre {formsemestre_id} inconnu !")
|
||||||
|
g.stored_get_formsemestre[formsemestre_id] = sems[0]
|
||||||
|
return sems[0]
|
||||||
|
|
||||||
|
|
||||||
def do_formsemestre_list(*a, **kw):
|
def do_formsemestre_list(*a, **kw):
|
||||||
|
@ -337,7 +337,7 @@ def formsemestre_status_menubar(sem):
|
|||||||
submenu.append(
|
submenu.append(
|
||||||
{
|
{
|
||||||
"title": "%s" % partition["partition_name"],
|
"title": "%s" % partition["partition_name"],
|
||||||
"endpoint": "scolar.affectGroups",
|
"endpoint": "scolar.affect_groups",
|
||||||
"args": {"partition_id": partition["partition_id"]},
|
"args": {"partition_id": partition["partition_id"]},
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
}
|
}
|
||||||
@ -505,15 +505,29 @@ def formsemestre_page_title():
|
|||||||
|
|
||||||
fill_formsemestre(sem)
|
fill_formsemestre(sem)
|
||||||
|
|
||||||
H = [
|
h = f"""<div class="formsemestre_page_title">
|
||||||
"""<div class="formsemestre_page_title">""",
|
<div class="infos">
|
||||||
"""<div class="infos">
|
<span class="semtitle"><a class="stdlink" title="{sem['session_id']}"
|
||||||
<span class="semtitle"><a class="stdlink" title="%(session_id)s" href="%(notes_url)s/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titre)s</a><a title="%(etape_apo_str)s">%(num_sem)s</a>%(modalitestr)s</span><span class="dates"><a title="du %(date_debut)s au %(date_fin)s ">%(mois_debut)s - %(mois_fin)s</a></span><span class="resp"><a title="%(nomcomplet)s">%(resp)s</a></span><span class="nbinscrits"><a class="discretelink" href="%(notes_url)s/formsemestre_lists?formsemestre_id=%(formsemestre_id)s">%(nbinscrits)d inscrits</a></span><span class="lock">%(locklink)s</span><span class="eye">%(eyelink)s</span></div>"""
|
href="{url_for('notes.formsemestre_status',
|
||||||
% sem,
|
scodoc_dept=g.scodoc_dept, formsemestre_id=sem['formsemestre_id'])}"
|
||||||
formsemestre_status_menubar(sem),
|
>{sem['titre']}</a><a
|
||||||
"""</div>""",
|
title="{sem['etape_apo_str']}">{sem['num_sem']}</a>{sem['modalitestr']}</span><span
|
||||||
]
|
class="dates"><a
|
||||||
return "\n".join(H)
|
title="du {sem['date_debut']} au {sem['date_fin']} "
|
||||||
|
>{sem['mois_debut']} - {sem['mois_fin']}</a></span><span
|
||||||
|
class="resp"><a title="{sem['nomcomplet']}">{sem['resp']}</a></span><span
|
||||||
|
class="nbinscrits"><a class="discretelink"
|
||||||
|
href="{url_for("scolar.groups_view",
|
||||||
|
scodoc_dept=g.scodoc_dept, formsemestre_id=sem['formsemestre_id'])}"
|
||||||
|
>{sem['nbinscrits']} inscrits</a></span><span
|
||||||
|
class="lock">{sem['locklink']}</span><span
|
||||||
|
class="eye">{sem['eyelink']}</span>
|
||||||
|
</div>
|
||||||
|
{formsemestre_status_menubar(sem)}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
return h
|
||||||
|
|
||||||
|
|
||||||
def fill_formsemestre(sem):
|
def fill_formsemestre(sem):
|
||||||
@ -843,7 +857,7 @@ def _make_listes_sem(sem, with_absences=True):
|
|||||||
H.append('<p class="help indent">Aucun groupe dans cette partition')
|
H.append('<p class="help indent">Aucun groupe dans cette partition')
|
||||||
if sco_groups.sco_permissions_check.can_change_groups(formsemestre_id):
|
if sco_groups.sco_permissions_check.can_change_groups(formsemestre_id):
|
||||||
H.append(
|
H.append(
|
||||||
f""" (<a href="{url_for("scolar.affectGroups",
|
f""" (<a href="{url_for("scolar.affect_groups",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
partition_id=partition["partition_id"])
|
partition_id=partition["partition_id"])
|
||||||
}" class="stdlink">créer</a>)"""
|
}" class="stdlink">créer</a>)"""
|
||||||
@ -967,7 +981,7 @@ def formsemestre_status(formsemestre_id=None):
|
|||||||
"""Tableau de bord semestre HTML"""
|
"""Tableau de bord semestre HTML"""
|
||||||
# porté du DTML
|
# porté du DTML
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
|
||||||
Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
Mlist = sco_moduleimpl.do_moduleimpl_withmodule_list(
|
||||||
formsemestre_id=formsemestre_id
|
formsemestre_id=formsemestre_id
|
||||||
)
|
)
|
||||||
|
@ -492,6 +492,8 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
|
|||||||
"""
|
"""
|
||||||
from app.scodoc import sco_formsemestre
|
from app.scodoc import sco_formsemestre
|
||||||
|
|
||||||
|
cnx = ndb.GetDBConnexion()
|
||||||
|
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
partition = get_partition(partition_id)
|
partition = get_partition(partition_id)
|
||||||
formsemestre_id = partition["formsemestre_id"]
|
formsemestre_id = partition["formsemestre_id"]
|
||||||
@ -500,6 +502,7 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
|
|||||||
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > inscrdict
|
nt = sco_cache.NotesTableCache.get(formsemestre_id) # > inscrdict
|
||||||
etuds_set = set(nt.inscrdict)
|
etuds_set = set(nt.inscrdict)
|
||||||
# Build XML:
|
# Build XML:
|
||||||
|
t1 = time.time()
|
||||||
doc = Element("ajax-response")
|
doc = Element("ajax-response")
|
||||||
x_response = Element("response", type="object", id="MyUpdater")
|
x_response = Element("response", type="object", id="MyUpdater")
|
||||||
doc.append(x_response)
|
doc.append(x_response)
|
||||||
@ -513,7 +516,8 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
|
|||||||
)
|
)
|
||||||
x_response.append(x_group)
|
x_response.append(x_group)
|
||||||
for e in get_group_members(group["group_id"]):
|
for e in get_group_members(group["group_id"]):
|
||||||
etud = sco_etud.get_etud_info(etudid=e["etudid"], filled=1)[0]
|
etud = sco_etud.get_etud_info(etudid=e["etudid"], filled=True)[0]
|
||||||
|
# etud = sco_etud.get_etud_info_filled_by_etudid(e["etudid"], cnx)
|
||||||
x_group.append(
|
x_group.append(
|
||||||
Element(
|
Element(
|
||||||
"etud",
|
"etud",
|
||||||
@ -540,6 +544,7 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
|
|||||||
doc.append(x_group)
|
doc.append(x_group)
|
||||||
for etudid in etuds_set:
|
for etudid in etuds_set:
|
||||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||||
|
# etud = sco_etud.get_etud_info_filled_by_etudid(etudid, cnx)
|
||||||
x_group.append(
|
x_group.append(
|
||||||
Element(
|
Element(
|
||||||
"etud",
|
"etud",
|
||||||
@ -550,7 +555,8 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
|
|||||||
origin=comp_origin(etud, sem),
|
origin=comp_origin(etud, sem),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
log("XMLgetGroupsInPartition: %s seconds" % (time.time() - t0))
|
t2 = time.time()
|
||||||
|
log(f"XMLgetGroupsInPartition: {t2-t0} seconds ({t1-t0}+{t2-t1})")
|
||||||
# XML response:
|
# XML response:
|
||||||
data = sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
|
data = sco_xml.XML_HEADER + ElementTree.tostring(doc).decode(scu.SCO_ENCODING)
|
||||||
response = make_response(data)
|
response = make_response(data)
|
||||||
@ -911,7 +917,7 @@ def editPartitionForm(formsemestre_id=None):
|
|||||||
H.append(", ".join(lg))
|
H.append(", ".join(lg))
|
||||||
H.append(
|
H.append(
|
||||||
f"""</td><td><a class="stdlink" href="{
|
f"""</td><td><a class="stdlink" href="{
|
||||||
url_for("scolar.affectGroups",
|
url_for("scolar.affect_groups",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
partition_id=p["partition_id"])
|
partition_id=p["partition_id"])
|
||||||
}">répartir</a></td>
|
}">répartir</a></td>
|
||||||
@ -1173,7 +1179,7 @@ def group_set_name(group_id, group_name, redirect=1):
|
|||||||
if redirect:
|
if redirect:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.affectGroups",
|
"scolar.affect_groups",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
partition_id=group["partition_id"],
|
partition_id=group["partition_id"],
|
||||||
)
|
)
|
||||||
@ -1216,7 +1222,7 @@ def group_rename(group_id):
|
|||||||
elif tf[0] == -1:
|
elif tf[0] == -1:
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.affectGroups",
|
"scolar.affect_groups",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
partition_id=group["partition_id"],
|
partition_id=group["partition_id"],
|
||||||
)
|
)
|
||||||
@ -1236,7 +1242,7 @@ def groups_auto_repartition(partition_id=None):
|
|||||||
formsemestre_id = partition["formsemestre_id"]
|
formsemestre_id = partition["formsemestre_id"]
|
||||||
# renvoie sur page édition groupes
|
# renvoie sur page édition groupes
|
||||||
dest_url = url_for(
|
dest_url = url_for(
|
||||||
"scolar.affectGroups", scodoc_dept=g.scodoc_dept, partition_id=partition_id
|
"scolar.affect_groups", scodoc_dept=g.scodoc_dept, partition_id=partition_id
|
||||||
)
|
)
|
||||||
if not sco_permissions_check.can_change_groups(formsemestre_id):
|
if not sco_permissions_check.can_change_groups(formsemestre_id):
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
|
@ -27,70 +27,33 @@
|
|||||||
|
|
||||||
"""Formulaires gestion des groupes
|
"""Formulaires gestion des groupes
|
||||||
"""
|
"""
|
||||||
|
from flask import render_template
|
||||||
|
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc.sco_exceptions import AccessDenied
|
from app.scodoc.sco_exceptions import AccessDenied
|
||||||
|
|
||||||
|
|
||||||
def affectGroups(partition_id):
|
def affect_groups(partition_id):
|
||||||
"""Formulaire affectation des etudiants aux groupes de la partition.
|
"""Formulaire affectation des etudiants aux groupes de la partition.
|
||||||
Permet aussi la creation et la suppression de groupes.
|
Permet aussi la creation et la suppression de groupes.
|
||||||
"""
|
"""
|
||||||
# Ported from DTML and adapted to new group management (nov 2009)
|
# réécrit pour 9.0.47 avec un template
|
||||||
partition = sco_groups.get_partition(partition_id)
|
partition = sco_groups.get_partition(partition_id)
|
||||||
formsemestre_id = partition["formsemestre_id"]
|
formsemestre_id = partition["formsemestre_id"]
|
||||||
if not sco_groups.sco_permissions_check.can_change_groups(formsemestre_id):
|
if not sco_groups.sco_permissions_check.can_change_groups(formsemestre_id):
|
||||||
raise AccessDenied("vous n'avez pas la permission d'effectuer cette opération")
|
raise AccessDenied("vous n'avez pas la permission d'effectuer cette opération")
|
||||||
|
return render_template(
|
||||||
H = [
|
"scolar/affect_groups.html",
|
||||||
html_sco_header.sco_header(
|
sco_header=html_sco_header.sco_header(
|
||||||
page_title="Affectation aux groupes",
|
page_title="Affectation aux groupes",
|
||||||
javascripts=["js/groupmgr.js"],
|
javascripts=["js/groupmgr.js"],
|
||||||
cssstyles=["css/groups.css"],
|
cssstyles=["css/groups.css"],
|
||||||
),
|
),
|
||||||
"""<h2 class="formsemestre">Affectation aux groupes de %s</h2><form id="sp">"""
|
sco_footer=html_sco_header.sco_footer(),
|
||||||
% partition["partition_name"],
|
partition=partition,
|
||||||
]
|
partitions_list=sco_groups.get_partitions_list(
|
||||||
|
formsemestre_id, with_default=False
|
||||||
H += [
|
),
|
||||||
"""</select></form>""",
|
formsemestre_id=formsemestre_id,
|
||||||
"""<p>Faites glisser les étudiants d'un groupe à l'autre. Les modifications ne sont enregistrées que lorsque vous cliquez sur le bouton "<em>Enregistrer ces groupes</em>". Vous pouvez créer de nouveaux groupes. Pour <em>supprimer</em> un groupe, utiliser le lien "suppr." en haut à droite de sa boite. Vous pouvez aussi <a class="stdlink" href="groups_auto_repartition?partition_id=%(partition_id)s">répartir automatiquement les groupes</a>.
|
)
|
||||||
</p>"""
|
|
||||||
% partition,
|
|
||||||
"""<div id="gmsg" class="head_message"></div>""",
|
|
||||||
"""<div id="ginfo"></div>""",
|
|
||||||
"""<div id="savedinfo"></div>""",
|
|
||||||
"""<form name="formGroup" id="formGroup" onSubmit="return false;">""",
|
|
||||||
"""<input type="hidden" name="partition_id" value="%s"/>""" % partition_id,
|
|
||||||
"""<input name="groupName" size="6"/>
|
|
||||||
<input type="button" onClick="createGroup();" value="Créer groupe"/>
|
|
||||||
|
|
||||||
<input type="button" onClick="submitGroups( target='gmsg' );" value="Enregistrer ces groupes" />
|
|
||||||
|
|
||||||
<input type="button" onClick="document.location = 'formsemestre_status?formsemestre_id=%s'" value="Annuler" />
|
|
||||||
Editer groupes de
|
|
||||||
<select name="other_partition_id" onchange="GotoAnother();">"""
|
|
||||||
% formsemestre_id,
|
|
||||||
]
|
|
||||||
for p in sco_groups.get_partitions_list(formsemestre_id, with_default=False):
|
|
||||||
H.append('<option value="%s"' % p["partition_id"])
|
|
||||||
if p["partition_id"] == partition_id:
|
|
||||||
H.append(" selected")
|
|
||||||
H.append(">%s</option>" % p["partition_name"])
|
|
||||||
H += [
|
|
||||||
"""</select>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div id="groups">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="clear: left; margin-top: 15px;">
|
|
||||||
<p class="help"></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
""",
|
|
||||||
html_sco_header.sco_footer(),
|
|
||||||
]
|
|
||||||
return "\n".join(H)
|
|
||||||
|
@ -489,7 +489,7 @@ def groups_table(
|
|||||||
columns_ids += ["etape", "etudid", "code_nip", "code_ine"]
|
columns_ids += ["etape", "etudid", "code_nip", "code_ine"]
|
||||||
if with_paiement:
|
if with_paiement:
|
||||||
columns_ids += ["datefinalisationinscription_str", "paiementinscription_str"]
|
columns_ids += ["datefinalisationinscription_str", "paiementinscription_str"]
|
||||||
if with_paiement or with_codes:
|
if with_paiement: # or with_codes:
|
||||||
sco_portal_apogee.check_paiement_etuds(groups_infos.members)
|
sco_portal_apogee.check_paiement_etuds(groups_infos.members)
|
||||||
if with_archives:
|
if with_archives:
|
||||||
from app.scodoc import sco_archives_etud
|
from app.scodoc import sco_archives_etud
|
||||||
|
@ -25,16 +25,16 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
""" Importation des etudiants à partir de fichiers CSV
|
""" Importation des étudiants à partir de fichiers CSV
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
import flask
|
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
@ -252,7 +252,7 @@ def students_import_excel(
|
|||||||
|
|
||||||
|
|
||||||
def scolars_import_excel_file(
|
def scolars_import_excel_file(
|
||||||
datafile,
|
datafile: io.BytesIO,
|
||||||
formsemestre_id=None,
|
formsemestre_id=None,
|
||||||
check_homonyms=True,
|
check_homonyms=True,
|
||||||
require_ine=False,
|
require_ine=False,
|
||||||
@ -414,16 +414,14 @@ def scolars_import_excel_file(
|
|||||||
if NbHomonyms:
|
if NbHomonyms:
|
||||||
NbImportedHomonyms += 1
|
NbImportedHomonyms += 1
|
||||||
# Insert in DB tables
|
# Insert in DB tables
|
||||||
formsemestre_to_invalidate.add(
|
formsemestre_id_etud = _import_one_student(
|
||||||
_import_one_student(
|
cnx,
|
||||||
cnx,
|
formsemestre_id,
|
||||||
formsemestre_id,
|
values,
|
||||||
values,
|
GroupIdInferers,
|
||||||
GroupIdInferers,
|
annee_courante,
|
||||||
annee_courante,
|
created_etudids,
|
||||||
created_etudids,
|
linenum,
|
||||||
linenum,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verification proportion d'homonymes: si > 10%, abandonne
|
# Verification proportion d'homonymes: si > 10%, abandonne
|
||||||
@ -522,7 +520,7 @@ def _import_one_student(
|
|||||||
annee_courante,
|
annee_courante,
|
||||||
created_etudids,
|
created_etudids,
|
||||||
linenum,
|
linenum,
|
||||||
):
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Import d'un étudiant et inscription dans le semestre.
|
Import d'un étudiant et inscription dans le semestre.
|
||||||
Return: id du semestre dans lequel il a été inscrit.
|
Return: id du semestre dans lequel il a été inscrit.
|
||||||
@ -550,6 +548,12 @@ def _import_one_student(
|
|||||||
else:
|
else:
|
||||||
args["formsemestre_id"] = values["codesemestre"]
|
args["formsemestre_id"] = values["codesemestre"]
|
||||||
formsemestre_id = values["codesemestre"]
|
formsemestre_id = values["codesemestre"]
|
||||||
|
try:
|
||||||
|
formsemestre_id = int(formsemestre_id)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError(
|
||||||
|
f"valeur invalide dans la colonne codesemestre, ligne {linenum+1}"
|
||||||
|
) from exc
|
||||||
# recupere liste des groupes:
|
# recupere liste des groupes:
|
||||||
if formsemestre_id not in GroupIdInferers:
|
if formsemestre_id not in GroupIdInferers:
|
||||||
GroupIdInferers[formsemestre_id] = sco_groups.GroupIdInferer(formsemestre_id)
|
GroupIdInferers[formsemestre_id] = sco_groups.GroupIdInferer(formsemestre_id)
|
||||||
@ -566,7 +570,7 @@ def _import_one_student(
|
|||||||
)
|
)
|
||||||
|
|
||||||
do_formsemestre_inscription_with_modules(
|
do_formsemestre_inscription_with_modules(
|
||||||
args["formsemestre_id"],
|
int(args["formsemestre_id"]),
|
||||||
etudid,
|
etudid,
|
||||||
group_ids,
|
group_ids,
|
||||||
etat="I",
|
etat="I",
|
||||||
|
@ -109,8 +109,11 @@ def import_excel_file(datafile):
|
|||||||
if not exceldata:
|
if not exceldata:
|
||||||
raise ScoValueError("Ficher excel vide ou invalide")
|
raise ScoValueError("Ficher excel vide ou invalide")
|
||||||
_, data = sco_excel.excel_bytes_to_list(exceldata)
|
_, data = sco_excel.excel_bytes_to_list(exceldata)
|
||||||
if not data: # probably a bug
|
if not data:
|
||||||
raise ScoException("import_excel_file: empty file !")
|
raise ScoValueError(
|
||||||
|
"""Le fichier xlsx attendu semble vide !
|
||||||
|
"""
|
||||||
|
)
|
||||||
# 1- --- check title line
|
# 1- --- check title line
|
||||||
fs = [scu.stripquotes(s).lower() for s in data[0]]
|
fs = [scu.stripquotes(s).lower() for s in data[0]]
|
||||||
log("excel: fs='%s'\ndata=%s" % (str(fs), str(data)))
|
log("excel: fs='%s'\ndata=%s" % (str(fs), str(data)))
|
||||||
@ -179,11 +182,13 @@ def import_users(users):
|
|||||||
line = line + 1
|
line = line + 1
|
||||||
user_ok, msg = sco_users.check_modif_user(
|
user_ok, msg = sco_users.check_modif_user(
|
||||||
0,
|
0,
|
||||||
|
ignore_optionals=False,
|
||||||
user_name=u["user_name"],
|
user_name=u["user_name"],
|
||||||
nom=u["nom"],
|
nom=u["nom"],
|
||||||
prenom=u["prenom"],
|
prenom=u["prenom"],
|
||||||
email=u["email"],
|
email=u["email"],
|
||||||
roles=u["roles"].split(","),
|
roles=u["roles"].split(","),
|
||||||
|
dept=u["dept"],
|
||||||
)
|
)
|
||||||
if not user_ok:
|
if not user_ok:
|
||||||
append_msg("identifiant '%s' %s" % (u["user_name"], msg))
|
append_msg("identifiant '%s' %s" % (u["user_name"], msg))
|
||||||
@ -193,39 +198,12 @@ def import_users(users):
|
|||||||
u["passwd"] = generate_password()
|
u["passwd"] = generate_password()
|
||||||
#
|
#
|
||||||
# check identifiant
|
# check identifiant
|
||||||
if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]*$", u["user_name"]):
|
|
||||||
user_ok = False
|
|
||||||
append_msg(
|
|
||||||
"identifiant '%s' invalide (pas d'accents ni de caractères spéciaux)"
|
|
||||||
% u["user_name"]
|
|
||||||
)
|
|
||||||
if len(u["user_name"]) > 64:
|
|
||||||
user_ok = False
|
|
||||||
append_msg(
|
|
||||||
"identifiant '%s' trop long (64 caractères)" % u["user_name"]
|
|
||||||
)
|
|
||||||
if len(u["nom"]) > 64:
|
|
||||||
user_ok = False
|
|
||||||
append_msg("nom '%s' trop long (64 caractères)" % u["nom"])
|
|
||||||
if len(u["prenom"]) > 64:
|
|
||||||
user_ok = False
|
|
||||||
append_msg("prenom '%s' trop long (64 caractères)" % u["prenom"])
|
|
||||||
if len(u["email"]) > 120:
|
|
||||||
user_ok = False
|
|
||||||
append_msg("email '%s' trop long (120 caractères)" % u["email"])
|
|
||||||
# check that tha same user_name has not already been described in this import
|
|
||||||
if u["user_name"] in created.keys():
|
if u["user_name"] in created.keys():
|
||||||
user_ok = False
|
user_ok = False
|
||||||
append_msg(
|
append_msg(
|
||||||
"l'utilisateur '%s' a déjà été décrit ligne %s"
|
"l'utilisateur '%s' a déjà été décrit ligne %s"
|
||||||
% (u["user_name"], created[u["user_name"]]["line"])
|
% (u["user_name"], created[u["user_name"]]["line"])
|
||||||
)
|
)
|
||||||
# check département
|
|
||||||
if u["dept"] != "":
|
|
||||||
dept = Departement.query.filter_by(acronym=u["dept"]).first()
|
|
||||||
if dept is None:
|
|
||||||
user_ok = False
|
|
||||||
append_msg("département '%s' inexistant" % u["dept"])
|
|
||||||
# check roles / ignore whitespaces around roles / build roles_string
|
# check roles / ignore whitespaces around roles / build roles_string
|
||||||
# roles_string (expected by User) appears as column 'roles' in excel file
|
# roles_string (expected by User) appears as column 'roles' in excel file
|
||||||
roles_list = []
|
roles_list = []
|
||||||
|
@ -390,7 +390,7 @@ def formsemestre_inscr_passage(
|
|||||||
): # il y a au moins une vraie partition
|
): # il y a au moins une vraie partition
|
||||||
H.append(
|
H.append(
|
||||||
f"""<li><a class="stdlink" href="{
|
f"""<li><a class="stdlink" href="{
|
||||||
url_for("scolar.affectGroups",
|
url_for("scolar.affect_groups",
|
||||||
scodoc_dept=g.scodoc_dept, partition_id=partition["partition_id"])
|
scodoc_dept=g.scodoc_dept, partition_id=partition["partition_id"])
|
||||||
}">Répartir les groupes de {partition["partition_name"]}</a></li>
|
}">Répartir les groupes de {partition["partition_name"]}</a></li>
|
||||||
"""
|
"""
|
||||||
|
@ -315,7 +315,7 @@ def _make_table_notes(
|
|||||||
|
|
||||||
rows.append(
|
rows.append(
|
||||||
{
|
{
|
||||||
"code": code,
|
"code": str(code), # INE, NIP ou etudid
|
||||||
"_code_td_attrs": 'style="padding-left: 1em; padding-right: 2em;"',
|
"_code_td_attrs": 'style="padding-left: 1em; padding-right: 2em;"',
|
||||||
"etudid": etudid,
|
"etudid": etudid,
|
||||||
"nom": etud["nom"].upper(),
|
"nom": etud["nom"].upper(),
|
||||||
@ -374,9 +374,11 @@ def _make_table_notes(
|
|||||||
columns_ids.append(e["evaluation_id"])
|
columns_ids.append(e["evaluation_id"])
|
||||||
#
|
#
|
||||||
if anonymous_listing:
|
if anonymous_listing:
|
||||||
rows.sort(key=lambda x: x["code"])
|
rows.sort(key=lambda x: x["code"] or "")
|
||||||
else:
|
else:
|
||||||
rows.sort(key=lambda x: (x["nom"], x["prenom"])) # sort by nom, prenom
|
rows.sort(
|
||||||
|
key=lambda x: (x["nom"] or "", x["prenom"] or "")
|
||||||
|
) # sort by nom, prenom
|
||||||
|
|
||||||
# Si module, ajoute moyenne du module:
|
# Si module, ajoute moyenne du module:
|
||||||
if len(evals) > 1:
|
if len(evals) > 1:
|
||||||
|
@ -527,15 +527,15 @@ def do_etud_desinscrit_ue(etudid, formsemestre_id, ue_id):
|
|||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""DELETE FROM notes_moduleimpl_inscription
|
"""DELETE FROM notes_moduleimpl_inscription
|
||||||
WHERE moduleimpl_inscription_id IN (
|
WHERE id IN (
|
||||||
SELECT i.moduleimpl_inscription_id FROM
|
SELECT i.id FROM
|
||||||
notes_moduleimpl mi, notes_modules mod,
|
notes_moduleimpl mi, notes_modules mod,
|
||||||
notes_formsemestre sem, notes_moduleimpl_inscription i
|
notes_formsemestre sem, notes_moduleimpl_inscription i
|
||||||
WHERE sem.formsemestre_id = %(formsemestre_id)s
|
WHERE sem.id = %(formsemestre_id)s
|
||||||
AND mi.formsemestre_id = sem.formsemestre_id
|
AND mi.formsemestre_id = sem.id
|
||||||
AND mod.module_id = mi.module_id
|
AND mod.id = mi.module_id
|
||||||
AND mod.ue_id = %(ue_id)s
|
AND mod.ue_id = %(ue_id)s
|
||||||
AND i.moduleimpl_id = mi.moduleimpl_id
|
AND i.moduleimpl_id = mi.id
|
||||||
AND i.etudid = %(etudid)s
|
AND i.etudid = %(etudid)s
|
||||||
)
|
)
|
||||||
""",
|
""",
|
||||||
|
@ -916,7 +916,7 @@ def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite)
|
|||||||
and ue_status["moy"] >= nt.parcours.NOTES_BARRE_VALID_UE
|
and ue_status["moy"] >= nt.parcours.NOTES_BARRE_VALID_UE
|
||||||
):
|
):
|
||||||
code_ue = ADM
|
code_ue = ADM
|
||||||
elif isinstance(ue_status["moy"], float):
|
elif not isinstance(ue_status["moy"], float):
|
||||||
# aucune note (pas de moyenne) dans l'UE: ne la valide pas
|
# aucune note (pas de moyenne) dans l'UE: ne la valide pas
|
||||||
code_ue = None
|
code_ue = None
|
||||||
elif valid_semestre:
|
elif valid_semestre:
|
||||||
|
@ -66,7 +66,7 @@ from app.scodoc.sco_utils import (
|
|||||||
LOGOS_IMAGES_ALLOWED_TYPES,
|
LOGOS_IMAGES_ALLOWED_TYPES,
|
||||||
)
|
)
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.sco_exceptions import ScoGenError
|
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||||
import sco_version
|
import sco_version
|
||||||
|
|
||||||
PAGE_HEIGHT = defaultPageSize[1]
|
PAGE_HEIGHT = defaultPageSize[1]
|
||||||
@ -121,6 +121,7 @@ def makeParas(txt, style, suppress_empty=False):
|
|||||||
"""Returns a list of Paragraph instances from a text
|
"""Returns a list of Paragraph instances from a text
|
||||||
with one or more <para> ... </para>
|
with one or more <para> ... </para>
|
||||||
"""
|
"""
|
||||||
|
result = []
|
||||||
try:
|
try:
|
||||||
paras = _splitPara(txt)
|
paras = _splitPara(txt)
|
||||||
if suppress_empty:
|
if suppress_empty:
|
||||||
@ -133,21 +134,30 @@ def makeParas(txt, style, suppress_empty=False):
|
|||||||
if m.group(1): # non empty paragraph
|
if m.group(1): # non empty paragraph
|
||||||
r.append(para)
|
r.append(para)
|
||||||
paras = r
|
paras = r
|
||||||
return [Paragraph(SU(s), style) for s in paras]
|
result = [Paragraph(SU(s), style) for s in paras]
|
||||||
|
except OSError as e:
|
||||||
|
msg = str(e)
|
||||||
|
# If a file is missing, try to display the invalid name
|
||||||
|
m = re.match(r".*\sfilename=\'(.*?)\'.*", msg, re.DOTALL)
|
||||||
|
if m:
|
||||||
|
filename = os.path.split(m.group(1))[1]
|
||||||
|
if filename.startswith("logo_"):
|
||||||
|
filename = filename[len("logo_") :]
|
||||||
|
raise ScoValueError(
|
||||||
|
f"Erreur dans le format PDF paramétré: fichier logo <b>{filename}</b> non trouvé"
|
||||||
|
) from e
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
detail = " " + str(e)
|
|
||||||
log(traceback.format_exc())
|
log(traceback.format_exc())
|
||||||
log("Invalid pdf para format: %s" % txt)
|
log("Invalid pdf para format: %s" % txt)
|
||||||
return [
|
result = [
|
||||||
Paragraph(
|
Paragraph(
|
||||||
SU(
|
SU('<font color="red"><i>Erreur: format invalide</i></font>'),
|
||||||
'<font color="red"><i>Erreur: format invalide{}</i></font>'.format(
|
|
||||||
detail
|
|
||||||
)
|
|
||||||
),
|
|
||||||
style,
|
style,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def bold_paras(L, tag="b", close=None):
|
def bold_paras(L, tag="b", close=None):
|
||||||
|
@ -88,10 +88,9 @@ def _get_group_info(evaluation_id):
|
|||||||
groups_tree[partition][group_name] = group_id
|
groups_tree[partition][group_name] = group_id
|
||||||
if partition != TOUS:
|
if partition != TOUS:
|
||||||
has_groups = True
|
has_groups = True
|
||||||
nb_groups = len(groups_tree)
|
|
||||||
else:
|
else:
|
||||||
has_groups = False
|
has_groups = False
|
||||||
nb_groups = 1
|
nb_groups = sum([len(groups_tree[p]) for p in groups_tree])
|
||||||
return groups_tree, has_groups, nb_groups
|
return groups_tree, has_groups, nb_groups
|
||||||
|
|
||||||
|
|
||||||
|
@ -827,7 +827,7 @@ def feuille_saisie_notes(evaluation_id, group_ids=[]):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
filename = "notes_%s_%s.xlsx" % (evalname, gr_title_filename)
|
filename = "notes_%s_%s" % (evalname, gr_title_filename)
|
||||||
xls = sco_excel.excel_feuille_saisie(E, sem["titreannee"], description, lines=L)
|
xls = sco_excel.excel_feuille_saisie(E, sem["titreannee"], description, lines=L)
|
||||||
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
|
return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
|
||||||
# return sco_excel.send_excel_file(xls, filename)
|
# return sco_excel.send_excel_file(xls, filename)
|
||||||
|
@ -271,7 +271,7 @@ def formsemestre_synchro_etuds(
|
|||||||
if partitions: # il y a au moins une vraie partition
|
if partitions: # il y a au moins une vraie partition
|
||||||
H.append(
|
H.append(
|
||||||
f"""<li><a class="stdlink" href="{
|
f"""<li><a class="stdlink" href="{
|
||||||
url_for("scolar.affectGroups",
|
url_for("scolar.affect_groups",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
partition_id=partitions[0]["partition_id"]
|
partition_id=partitions[0]["partition_id"]
|
||||||
)}">Répartir les groupes de {partitions[0]["partition_name"]}</a></li>
|
)}">Répartir les groupes de {partitions[0]["partition_name"]}</a></li>
|
||||||
|
@ -29,13 +29,14 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Anciennement ZScoUsers.py, fonctions de gestion des données réécrite avec flask/SQLAlchemy
|
# Anciennement ZScoUsers.py, fonctions de gestion des données réécrite avec flask/SQLAlchemy
|
||||||
|
import re
|
||||||
|
|
||||||
from flask import url_for, g, request
|
from flask import url_for, g, request
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
import cracklib # pylint: disable=import-error
|
import cracklib # pylint: disable=import-error
|
||||||
|
|
||||||
from app import db
|
from app import db, Departement
|
||||||
|
|
||||||
from app.auth.models import Permission
|
from app.auth.models import Permission
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
@ -171,21 +172,25 @@ def list_users(
|
|||||||
if not can_modify:
|
if not can_modify:
|
||||||
d["date_modif_passwd"] = "(non visible)"
|
d["date_modif_passwd"] = "(non visible)"
|
||||||
|
|
||||||
|
columns_ids = [
|
||||||
|
"user_name",
|
||||||
|
"nom_fmt",
|
||||||
|
"prenom_fmt",
|
||||||
|
"email",
|
||||||
|
"dept",
|
||||||
|
"roles_string",
|
||||||
|
"date_expiration",
|
||||||
|
"date_modif_passwd",
|
||||||
|
"passwd_temp",
|
||||||
|
"status_txt",
|
||||||
|
]
|
||||||
|
# Seul l'admin peut voir les dates de dernière connexion
|
||||||
|
if current_user.is_administrator():
|
||||||
|
columns_ids.append("last_seen")
|
||||||
title = "Utilisateurs définis dans ScoDoc"
|
title = "Utilisateurs définis dans ScoDoc"
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
rows=r,
|
rows=r,
|
||||||
columns_ids=(
|
columns_ids=columns_ids,
|
||||||
"user_name",
|
|
||||||
"nom_fmt",
|
|
||||||
"prenom_fmt",
|
|
||||||
"email",
|
|
||||||
"dept",
|
|
||||||
"roles_string",
|
|
||||||
"date_expiration",
|
|
||||||
"date_modif_passwd",
|
|
||||||
"passwd_temp",
|
|
||||||
"status_txt",
|
|
||||||
),
|
|
||||||
titles={
|
titles={
|
||||||
"user_name": "Login",
|
"user_name": "Login",
|
||||||
"nom_fmt": "Nom",
|
"nom_fmt": "Nom",
|
||||||
@ -195,6 +200,7 @@ def list_users(
|
|||||||
"roles_string": "Rôles",
|
"roles_string": "Rôles",
|
||||||
"date_expiration": "Expiration",
|
"date_expiration": "Expiration",
|
||||||
"date_modif_passwd": "Modif. mot de passe",
|
"date_modif_passwd": "Modif. mot de passe",
|
||||||
|
"last_seen": "Dernière cnx.",
|
||||||
"passwd_temp": "Temp.",
|
"passwd_temp": "Temp.",
|
||||||
"status_txt": "Etat",
|
"status_txt": "Etat",
|
||||||
},
|
},
|
||||||
@ -206,7 +212,7 @@ def list_users(
|
|||||||
html_class="table_leftalign list_users",
|
html_class="table_leftalign list_users",
|
||||||
html_with_td_classes=True,
|
html_with_td_classes=True,
|
||||||
html_sortable=True,
|
html_sortable=True,
|
||||||
base_url="%s?all=%s" % (request.base_url, all),
|
base_url="%s?all_depts=%s" % (request.base_url, 1 if all_depts else 0),
|
||||||
pdf_link=False, # table is too wide to fit in a paper page => disable pdf
|
pdf_link=False, # table is too wide to fit in a paper page => disable pdf
|
||||||
preferences=sco_preferences.SemPreferences(),
|
preferences=sco_preferences.SemPreferences(),
|
||||||
)
|
)
|
||||||
@ -379,7 +385,16 @@ def user_info_page(user_name=None):
|
|||||||
return "\n".join(H) + F
|
return "\n".join(H) + F
|
||||||
|
|
||||||
|
|
||||||
def check_modif_user(edit, user_name="", nom="", prenom="", email="", roles=[]):
|
def check_modif_user(
|
||||||
|
edit,
|
||||||
|
ignore_optionals=False,
|
||||||
|
user_name="",
|
||||||
|
nom="",
|
||||||
|
prenom="",
|
||||||
|
email="",
|
||||||
|
dept="",
|
||||||
|
roles=[],
|
||||||
|
):
|
||||||
"""Vérifie que cet utilisateur peut être créé (edit=0) ou modifié (edit=1)
|
"""Vérifie que cet utilisateur peut être créé (edit=0) ou modifié (edit=1)
|
||||||
Cherche homonymes.
|
Cherche homonymes.
|
||||||
returns (ok, msg)
|
returns (ok, msg)
|
||||||
@ -387,17 +402,44 @@ def check_modif_user(edit, user_name="", nom="", prenom="", email="", roles=[]):
|
|||||||
(si ok est faux, l'utilisateur peut quand même forcer la creation)
|
(si ok est faux, l'utilisateur peut quand même forcer la creation)
|
||||||
- msg: message warning a presenter l'utilisateur
|
- msg: message warning a presenter l'utilisateur
|
||||||
"""
|
"""
|
||||||
if not user_name or not nom or not prenom:
|
MSG_OPT = """Attention: %s (vous pouvez forcer l'opération en cochant "<em>Ignorer les avertissements</em>" en bas de page)"""
|
||||||
return False, "champ requis vide"
|
|
||||||
if not email:
|
|
||||||
return False, "vous devriez indiquer le mail de l'utilisateur créé !"
|
|
||||||
# ce login existe ?
|
# ce login existe ?
|
||||||
user = _user_list(user_name)
|
user = _user_list(user_name)
|
||||||
if edit and not user: # safety net, le user_name ne devrait pas changer
|
if edit and not user: # safety net, le user_name ne devrait pas changer
|
||||||
return False, "identifiant %s inexistant" % user_name
|
return False, "identifiant %s inexistant" % user_name
|
||||||
if not edit and user:
|
if not edit and user:
|
||||||
return False, "identifiant %s déjà utilisé" % user_name
|
return False, "identifiant %s déjà utilisé" % user_name
|
||||||
|
if not user_name or not nom or not prenom:
|
||||||
|
return False, "champ requis vide"
|
||||||
|
if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]*$", user_name):
|
||||||
|
return (
|
||||||
|
False,
|
||||||
|
"identifiant '%s' invalide (pas d'accents ni de caractères spéciaux)"
|
||||||
|
% user_name,
|
||||||
|
)
|
||||||
|
if ignore_optionals and len(user_name) > 64:
|
||||||
|
return False, "identifiant '%s' trop long (64 caractères)" % user_name
|
||||||
|
if ignore_optionals and len(nom) > 64:
|
||||||
|
return False, "nom '%s' trop long (64 caractères)" % nom + MSG_OPT
|
||||||
|
if ignore_optionals and len(prenom) > 64:
|
||||||
|
return False, "prenom '%s' trop long (64 caractères)" % prenom + MSG_OPT
|
||||||
|
# check that tha same user_name has not already been described in this import
|
||||||
|
if not email:
|
||||||
|
return False, "vous devriez indiquer le mail de l'utilisateur créé !"
|
||||||
|
if len(email) > 120:
|
||||||
|
return False, "email '%s' trop long (120 caractères)" % email
|
||||||
|
if not re.fullmatch(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", email):
|
||||||
|
return False, "l'adresse mail semble incorrecte"
|
||||||
|
# check département
|
||||||
|
if (
|
||||||
|
ignore_optionals
|
||||||
|
and dept != ""
|
||||||
|
and Departement.query.filter_by(acronym=dept).first() is None
|
||||||
|
):
|
||||||
|
return False, "département '%s' inexistant" % u["dept"] + MSG_OPT
|
||||||
|
if ignore_optionals and not roles:
|
||||||
|
return False, "aucun rôle sélectionné, êtes vous sûr ?" + MSG_OPT
|
||||||
|
# ok
|
||||||
# Des noms/prénoms semblables existent ?
|
# Des noms/prénoms semblables existent ?
|
||||||
nom = nom.lower().strip()
|
nom = nom.lower().strip()
|
||||||
prenom = prenom.lower().strip()
|
prenom = prenom.lower().strip()
|
||||||
@ -417,12 +459,10 @@ def check_modif_user(edit, user_name="", nom="", prenom="", email="", roles=[]):
|
|||||||
"%s %s (pseudo=%s)" % (x.prenom, x.nom, x.user_name)
|
"%s %s (pseudo=%s)" % (x.prenom, x.nom, x.user_name)
|
||||||
for x in similar_users
|
for x in similar_users
|
||||||
]
|
]
|
||||||
),
|
)
|
||||||
|
+ MSG_OPT,
|
||||||
)
|
)
|
||||||
# Roles ?
|
# Roles ?
|
||||||
if not roles:
|
|
||||||
return False, "aucun rôle sélectionné, êtes vous sûr ?"
|
|
||||||
# ok
|
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
|
@ -402,7 +402,7 @@ function GotoAnother() {
|
|||||||
if (groups_unsaved) {
|
if (groups_unsaved) {
|
||||||
alert("Enregistrez ou annulez vos changement avant !");
|
alert("Enregistrez ou annulez vos changement avant !");
|
||||||
} else
|
} else
|
||||||
document.location = SCO_URL + '/affectGroups?partition_id=' + document.formGroup.other_partition_id.value;
|
document.location = SCO_URL + '/affect_groups?partition_id=' + document.formGroup.other_partition_id.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,8 +4,9 @@
|
|||||||
{% block title %}Une erreur est survenue !{% endblock %}
|
{% block title %}Une erreur est survenue !{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>Une erreur est survenue !</h1>
|
<h1>Une erreur est survenue !</h1>
|
||||||
<p>Oops... <span style="color:red;"><b>ScoDoc version <span style="font-size: 120%;">{{SCOVERSION}}</span></b></span> a
|
<p>Oups...</tt> <span style="color:red;"><b>ScoDoc version <span style="font-size: 120%;">{{SCOVERSION}}</span></b></span> a
|
||||||
un problème, désolé.</p>
|
un problème, désolé.</p>
|
||||||
|
<p><tt style="font-size:60%">{{date}}</tt></p>
|
||||||
|
|
||||||
<p> Si le problème persiste, contacter l'administrateur de votre site,
|
<p> Si le problème persiste, contacter l'administrateur de votre site,
|
||||||
ou écrire la liste "notes" <a href="mailto:notes@listes.univ-paris13.fr">notes@listes.univ-paris13.fr</a> en
|
ou écrire la liste "notes" <a href="mailto:notes@listes.univ-paris13.fr">notes@listes.univ-paris13.fr</a> en
|
||||||
|
19
app/templates/error_access_denied.html
Normal file
19
app/templates/error_access_denied.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
|
||||||
|
<h2>Accès non autorisé</h2>
|
||||||
|
|
||||||
|
{{ exc | safe }}
|
||||||
|
|
||||||
|
<p class="footer">
|
||||||
|
{% if g.scodoc_dept %}
|
||||||
|
<a href="{{ exc.dest_url or url_for('scolar.index_html', scodoc_dept=g.scodoc_dept) }}">retour page d'accueil
|
||||||
|
departement {{ g.scodoc_dept }}</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ exc.dest_url or url_for('scodoc.index') }}">retour page d'accueil</a>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -3,7 +3,7 @@
|
|||||||
{% macro render_field(field) %}
|
{% macro render_field(field) %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="wtf-field">{{ field.label }}</td>
|
<td class="wtf-field">{{ field.label }}</td>
|
||||||
<td class="wtf-field">{{ field()|safe }}
|
<td class="wtf-field">{{ field(**kwargs)|safe }}
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
<ul class=errors>
|
<ul class=errors>
|
||||||
{% for error in field.errors %}
|
{% for error in field.errors %}
|
||||||
@ -27,7 +27,7 @@
|
|||||||
{{ render_field(form.nb_rangs) }}
|
{{ render_field(form.nb_rangs) }}
|
||||||
{{ render_field(form.etiquetage) }}
|
{{ render_field(form.etiquetage) }}
|
||||||
{% if form.has_groups %}
|
{% if form.has_groups %}
|
||||||
{{ render_field(form.groups) }}
|
{{ render_field(form.groups, size=form.nb_groups) }}
|
||||||
<!-- Tentative de recréer le choix des groupes sous forme de cases à cocher // demande à créer des champs wtf dynamiquement
|
<!-- Tentative de recréer le choix des groupes sous forme de cases à cocher // demande à créer des champs wtf dynamiquement
|
||||||
{% for partition in form.groups_tree %}
|
{% for partition in form.groups_tree %}
|
||||||
<tr>
|
<tr>
|
||||||
|
43
app/templates/scolar/affect_groups.html
Normal file
43
app/templates/scolar/affect_groups.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
{{ sco_header|safe }}
|
||||||
|
<h2 class="formsemestre">Affectation aux groupes de {{ partition["partition_name"] }}</h2>
|
||||||
|
|
||||||
|
<p>Faites glisser les étudiants d'un groupe à l'autre. Les modifications ne
|
||||||
|
sont enregistrées que lorsque vous cliquez sur le bouton "<em>Enregistrer ces groupes</em>".
|
||||||
|
Vous pouvez créer de nouveaux groupes. Pour <em>supprimer</em> un groupe, utiliser le lien
|
||||||
|
"suppr." en haut à droite de sa boite.
|
||||||
|
Vous pouvez aussi <a class="stdlink"
|
||||||
|
href="{{ url_for('scolar.groups_auto_repartition', scodoc_dept=g.scodoc_dept, partition_id=partition['partition_id']) }}"
|
||||||
|
>répartir automatiquement les groupes</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id="gmsg" class="head_message"></div>
|
||||||
|
<div id="ginfo"></div>
|
||||||
|
<div id="savedinfo"></div>
|
||||||
|
<form name="formGroup" id="formGroup" onSubmit="return false;">
|
||||||
|
<input type="hidden" name="partition_id" value="{{ partition['partition_id'] }}"/>
|
||||||
|
<input name="groupName" size="6"/>
|
||||||
|
<input type="button" onClick="createGroup();" value="Créer groupe"/>
|
||||||
|
|
||||||
|
<input type="button" onClick="submitGroups( target='gmsg' );" value="Enregistrer ces groupes" />
|
||||||
|
|
||||||
|
<input type="button"
|
||||||
|
onClick="document.location = '{{ url_for( 'notes.formsemestre_status', scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id) }}'"
|
||||||
|
value="Annuler" /> Éditer groupes de
|
||||||
|
<select name="other_partition_id" onchange="GotoAnother();">
|
||||||
|
{% for p in partitions_list %}
|
||||||
|
<option value="{{ p['id'] }}" {{"selected" if p["partition_id"] == partition_id }}>{{p["partition_name"]}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="groups">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="clear: left; margin-top: 15px;">
|
||||||
|
<p class="help"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ sco_footer|safe }}
|
@ -1,8 +1,13 @@
|
|||||||
# -*- coding: UTF-8 -*
|
# -*- coding: UTF-8 -*
|
||||||
"""ScoDoc Flask views
|
"""ScoDoc Flask views
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import g, current_app
|
from flask import g, current_app
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from app import db
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
|
|
||||||
scodoc_bp = Blueprint("scodoc", __name__)
|
scodoc_bp = Blueprint("scodoc", __name__)
|
||||||
@ -20,7 +25,14 @@ from app.views import scodoc, notes, scolar, absences, users
|
|||||||
@scodoc_bp.before_app_request
|
@scodoc_bp.before_app_request
|
||||||
def start_scodoc_request():
|
def start_scodoc_request():
|
||||||
"""Affecte toutes les requêtes, de tous les blueprints"""
|
"""Affecte toutes les requêtes, de tous les blueprints"""
|
||||||
|
# current_app.logger.info(f"start_scodoc_request")
|
||||||
ndb.open_db_connection()
|
ndb.open_db_connection()
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
current_user.last_seen = datetime.datetime.utcnow()
|
||||||
|
db.session.commit()
|
||||||
|
# caches locaux (durée de vie=la requête en cours)
|
||||||
|
g.stored_get_formsemestre = {}
|
||||||
|
# g.stored_etud_info = {} optim en cours, voir si utile
|
||||||
|
|
||||||
|
|
||||||
@scodoc_bp.teardown_app_request
|
@scodoc_bp.teardown_app_request
|
||||||
|
@ -1064,6 +1064,10 @@ def AddBilletAbsence(
|
|||||||
begin et end sont au format ISO (eg "1999-01-08 04:05:06")
|
begin et end sont au format ISO (eg "1999-01-08 04:05:06")
|
||||||
"""
|
"""
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
|
begin = str(begin)
|
||||||
|
end = str(end)
|
||||||
|
code_nip = str(code_nip) if code_nip else None
|
||||||
|
|
||||||
# check etudid
|
# check etudid
|
||||||
etuds = sco_etud.get_etud_info(etudid=etudid, code_nip=code_nip, filled=True)
|
etuds = sco_etud.get_etud_info(etudid=etudid, code_nip=code_nip, filled=True)
|
||||||
if not etuds:
|
if not etuds:
|
||||||
|
@ -274,7 +274,12 @@ def formsemestre_bulletinetud(
|
|||||||
xml_with_decisions=False,
|
xml_with_decisions=False,
|
||||||
force_publishing=False,
|
force_publishing=False,
|
||||||
prefer_mail_perso=False,
|
prefer_mail_perso=False,
|
||||||
|
code_nip=None,
|
||||||
):
|
):
|
||||||
|
if not (etudid or code_nip):
|
||||||
|
raise ScoValueError("Paramètre manquant: spécifier code_nip ou etudid")
|
||||||
|
if not formsemestre_id:
|
||||||
|
raise ScoValueError("Paramètre manquant: formsemestre_id est requis")
|
||||||
return sco_bulletins.formsemestre_bulletinetud(
|
return sco_bulletins.formsemestre_bulletinetud(
|
||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
@ -2268,6 +2273,7 @@ sco_publish(
|
|||||||
"/view_apo_csv_delete",
|
"/view_apo_csv_delete",
|
||||||
sco_etape_apogee_view.view_apo_csv_delete,
|
sco_etape_apogee_view.view_apo_csv_delete,
|
||||||
Permission.ScoEditApo,
|
Permission.ScoEditApo,
|
||||||
|
methods=["GET", "POST"],
|
||||||
)
|
)
|
||||||
sco_publish(
|
sco_publish(
|
||||||
"/view_scodoc_etuds", sco_etape_apogee_view.view_scodoc_etuds, Permission.ScoEditApo
|
"/view_scodoc_etuds", sco_etape_apogee_view.view_scodoc_etuds, Permission.ScoEditApo
|
||||||
|
@ -61,6 +61,7 @@ from app.decorators import (
|
|||||||
scodoc,
|
scodoc,
|
||||||
permission_required_compat_scodoc7,
|
permission_required_compat_scodoc7,
|
||||||
)
|
)
|
||||||
|
from app.scodoc.sco_exceptions import AccessDenied
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.views import scodoc_bp as bp
|
from app.views import scodoc_bp as bp
|
||||||
|
|
||||||
@ -82,6 +83,12 @@ def index():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Renvoie les url /ScoDoc/RT/ vers /ScoDoc/RT/Scolarite
|
||||||
|
@bp.route("/ScoDoc/<scodoc_dept>/")
|
||||||
|
def index_dept(scodoc_dept):
|
||||||
|
return redirect(url_for("scolar.index_html", scodoc_dept=scodoc_dept))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"])
|
@bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def table_etud_in_accessible_depts():
|
def table_etud_in_accessible_depts():
|
||||||
|
@ -651,8 +651,8 @@ def formChangeCoordonnees(etudid):
|
|||||||
|
|
||||||
# --- Gestion des groupes:
|
# --- Gestion des groupes:
|
||||||
sco_publish(
|
sco_publish(
|
||||||
"/affectGroups",
|
"/affect_groups",
|
||||||
sco_groups_edit.affectGroups,
|
sco_groups_edit.affect_groups,
|
||||||
Permission.ScoView,
|
Permission.ScoView,
|
||||||
methods=["GET", "POST"],
|
methods=["GET", "POST"],
|
||||||
)
|
)
|
||||||
@ -1589,6 +1589,7 @@ def etudident_delete(etudid, dialog_confirmed=False):
|
|||||||
"admissions",
|
"admissions",
|
||||||
"adresse",
|
"adresse",
|
||||||
"absences",
|
"absences",
|
||||||
|
"absences_notifications",
|
||||||
"billet_absence",
|
"billet_absence",
|
||||||
]
|
]
|
||||||
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
||||||
@ -1756,6 +1757,7 @@ def check_group_apogee(group_id, etat=None, fix=False, fixmail=False):
|
|||||||
@scodoc7func
|
@scodoc7func
|
||||||
def form_students_import_excel(formsemestre_id=None):
|
def form_students_import_excel(formsemestre_id=None):
|
||||||
"formulaire import xls"
|
"formulaire import xls"
|
||||||
|
formsemestre_id = int(formsemestre_id) if formsemestre_id else None
|
||||||
if formsemestre_id:
|
if formsemestre_id:
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
||||||
dest_url = (
|
dest_url = (
|
||||||
@ -1888,7 +1890,7 @@ Les champs avec un astérisque (*) doivent être présents (nulls non autorisés
|
|||||||
else:
|
else:
|
||||||
return sco_import_etuds.students_import_excel(
|
return sco_import_etuds.students_import_excel(
|
||||||
tf[2]["csvfile"],
|
tf[2]["csvfile"],
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=int(formsemestre_id) if formsemestre_id else None,
|
||||||
check_homonyms=tf[2]["check_homonyms"],
|
check_homonyms=tf[2]["check_homonyms"],
|
||||||
require_ine=tf[2]["require_ine"],
|
require_ine=tf[2]["require_ine"],
|
||||||
)
|
)
|
||||||
|
@ -178,7 +178,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||||||
orig_roles_strings = {r.name + "_" + (dept or "") for (r, dept) in orig_roles}
|
orig_roles_strings = {r.name + "_" + (dept or "") for (r, dept) in orig_roles}
|
||||||
# add existing user roles
|
# add existing user roles
|
||||||
displayed_roles = list(editable_roles_set.union(orig_roles))
|
displayed_roles = list(editable_roles_set.union(orig_roles))
|
||||||
displayed_roles.sort(key=lambda x: (x[1], x[0].name))
|
displayed_roles.sort(key=lambda x: (x[1] or "", x[0].name or ""))
|
||||||
displayed_roles_strings = [
|
displayed_roles_strings = [
|
||||||
r.name + "_" + (dept or "") for (r, dept) in displayed_roles
|
r.name + "_" + (dept or "") for (r, dept) in displayed_roles
|
||||||
]
|
]
|
||||||
@ -381,23 +381,29 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||||||
H.append(tf_error_message("""Erreur: %s""" % err))
|
H.append(tf_error_message("""Erreur: %s""" % err))
|
||||||
return "\n".join(H) + "\n" + tf[1] + F
|
return "\n".join(H) + "\n" + tf[1] + F
|
||||||
|
|
||||||
if not force:
|
ok, msg = sco_users.check_modif_user(
|
||||||
ok, msg = sco_users.check_modif_user(
|
edit,
|
||||||
edit,
|
ignore_optionals=force,
|
||||||
user_name=user_name,
|
user_name=user_name,
|
||||||
nom=vals["nom"],
|
nom=vals["nom"],
|
||||||
prenom=vals["prenom"],
|
prenom=vals["prenom"],
|
||||||
email=vals["email"],
|
email=vals["email"],
|
||||||
roles=vals["roles"],
|
roles=vals["roles"],
|
||||||
)
|
)
|
||||||
if not ok:
|
if not ok:
|
||||||
H.append(
|
H.append(tf_error_message(msg))
|
||||||
tf_error_message(
|
return "\n".join(H) + "\n" + tf[1] + F
|
||||||
"""Attention: %s (vous pouvez forcer l'opération en cochant "<em>Ignorer les avertissements</em>" en bas de page)"""
|
|
||||||
% msg
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if "date_expiration" in vals:
|
||||||
|
try:
|
||||||
|
if vals["date_expiration"]:
|
||||||
|
vals["date_expiration"] = datetime.datetime.strptime(
|
||||||
|
vals["date_expiration"], "%d/%m/%Y"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
vals["date_expiration"] = None
|
||||||
|
except ValueError:
|
||||||
|
H.append(tf_error_message("date expiration invalide"))
|
||||||
return "\n".join(H) + "\n" + tf[1] + F
|
return "\n".join(H) + "\n" + tf[1] + F
|
||||||
|
|
||||||
if edit: # modif utilisateur (mais pas password ni user_name !)
|
if edit: # modif utilisateur (mais pas password ni user_name !)
|
||||||
@ -411,17 +417,6 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||||||
del vals["user_name"]
|
del vals["user_name"]
|
||||||
if (current_user.user_name == user_name) and "status" in vals:
|
if (current_user.user_name == user_name) and "status" in vals:
|
||||||
del vals["status"] # no one can't change its own status
|
del vals["status"] # no one can't change its own status
|
||||||
if "date_expiration" in vals:
|
|
||||||
try:
|
|
||||||
if vals["date_expiration"]:
|
|
||||||
vals["date_expiration"] = datetime.datetime.strptime(
|
|
||||||
vals["date_expiration"], "%d/%m/%Y"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
vals["date_expiration"] = None
|
|
||||||
except ValueError:
|
|
||||||
H.append(tf_error_message("date expiration invalide"))
|
|
||||||
return "\n".join(H) + "\n" + tf[1] + F
|
|
||||||
if "status" in vals:
|
if "status" in vals:
|
||||||
vals["active"] = vals["status"] == ""
|
vals["active"] = vals["status"] == ""
|
||||||
# traitement des roles: ne doit pas affecter les roles
|
# traitement des roles: ne doit pas affecter les roles
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
"""ScoDoc 9.0.51: add unicity constraint on notes_moduleimpl_inscription
|
||||||
|
|
||||||
|
Revision ID: d74b4e16fb3c
|
||||||
|
Revises: f86c013c9fbd
|
||||||
|
Create Date: 2021-10-09 20:08:50.927330
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.orm import sessionmaker # added by ev
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "d74b4e16fb3c"
|
||||||
|
down_revision = "f86c013c9fbd"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
Session = sessionmaker()
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# Added by ev: remove duplicates
|
||||||
|
bind = op.get_bind()
|
||||||
|
session = Session(bind=bind)
|
||||||
|
session.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM notes_moduleimpl_inscription i1
|
||||||
|
USING notes_moduleimpl_inscription i2
|
||||||
|
WHERE i1.id < i2.id
|
||||||
|
AND i1.moduleimpl_id = i2.moduleimpl_id
|
||||||
|
AND i1.etudid = i2.etudid;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_unique_constraint(
|
||||||
|
None, "notes_moduleimpl_inscription", ["moduleimpl_id", "etudid"]
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint(None, "notes_moduleimpl_inscription", type_="unique")
|
||||||
|
# ### end Alembic commands ###
|
41
scodoc.py
41
scodoc.py
@ -274,6 +274,23 @@ def list_depts(depts=""): # list-dept
|
|||||||
print(f"{dept.id}\t{dept.acronym}")
|
print(f"{dept.id}\t{dept.acronym}")
|
||||||
|
|
||||||
|
|
||||||
|
@app.cli.command()
|
||||||
|
@click.option(
|
||||||
|
"-n",
|
||||||
|
"--name",
|
||||||
|
is_flag=True,
|
||||||
|
help="show database name instead of connexion string (required for "
|
||||||
|
"dropdb/createddb commands)",
|
||||||
|
)
|
||||||
|
def scodoc_database(name): # list-dept
|
||||||
|
"""print the database connexion string"""
|
||||||
|
uri = app.config["SQLALCHEMY_DATABASE_URI"]
|
||||||
|
if name:
|
||||||
|
print(uri.split("/")[-1])
|
||||||
|
else:
|
||||||
|
print(uri)
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def import_scodoc7_users(): # import-scodoc7-users
|
def import_scodoc7_users(): # import-scodoc7-users
|
||||||
@ -329,3 +346,27 @@ def recursive_help(cmd, parent=None):
|
|||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
def dumphelp():
|
def dumphelp():
|
||||||
recursive_help(app.cli)
|
recursive_help(app.cli)
|
||||||
|
|
||||||
|
|
||||||
|
@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()
|
||||||
|
@ -22,6 +22,8 @@ def test_client():
|
|||||||
with apptest.test_client() as client:
|
with apptest.test_client() as client:
|
||||||
with apptest.app_context():
|
with apptest.app_context():
|
||||||
with apptest.test_request_context():
|
with apptest.test_request_context():
|
||||||
|
# initialize scodoc "g":
|
||||||
|
g.stored_get_formsemestre = {}
|
||||||
# erase and reset database:
|
# erase and reset database:
|
||||||
initialize_scodoc_database(erase=True, create_all=True)
|
initialize_scodoc_database(erase=True, create_all=True)
|
||||||
# Loge l'utilisateur super-admin
|
# Loge l'utilisateur super-admin
|
||||||
|
100
tests/unit/test_notes_rattrapage.py
Normal file
100
tests/unit/test_notes_rattrapage.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
"""Test calculs rattrapages
|
||||||
|
"""
|
||||||
|
|
||||||
|
from config import TestConfig
|
||||||
|
from tests.unit import sco_fake_gen
|
||||||
|
|
||||||
|
from flask import g
|
||||||
|
|
||||||
|
import app
|
||||||
|
from app.scodoc import sco_bulletins
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
DEPT = TestConfig.DEPT_TEST
|
||||||
|
|
||||||
|
|
||||||
|
def test_notes_rattrapage(test_client):
|
||||||
|
"""Test quelques opérations élémentaires de ScoDoc
|
||||||
|
Création 10 étudiants, formation, semestre, inscription etudiant,
|
||||||
|
creation 1 evaluation, saisie 10 notes.
|
||||||
|
"""
|
||||||
|
app.set_sco_dept(DEPT)
|
||||||
|
|
||||||
|
G = sco_fake_gen.ScoFake(verbose=False)
|
||||||
|
etuds = [G.create_etud(code_nip=None)] # un seul
|
||||||
|
|
||||||
|
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"],
|
||||||
|
formation_id=f["formation_id"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- 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"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- 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,
|
||||||
|
)
|
||||||
|
# --- Création d'une évaluation "de rattrapage"
|
||||||
|
e_rat = G.create_evaluation(
|
||||||
|
moduleimpl_id=mi["moduleimpl_id"],
|
||||||
|
jour="02/01/2020",
|
||||||
|
description="evaluation rattrapage",
|
||||||
|
coefficient=1.0,
|
||||||
|
evaluation_type=scu.EVALUATION_RATTRAPAGE,
|
||||||
|
)
|
||||||
|
etud = etuds[0]
|
||||||
|
_, _, _ = G.create_note(evaluation=e, etud=etud, note=12.0)
|
||||||
|
_, _, _ = G.create_note(evaluation=e_rat, etud=etud, note=11.0)
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
# Vérifie structure du bulletin:
|
||||||
|
assert b["etudid"] == etud["etudid"]
|
||||||
|
assert len(b["ues"][0]["modules"][0]["evaluations"]) == 2
|
||||||
|
assert len(b["ues"][0]["modules"]) == 1
|
||||||
|
# Note moyenne: ici le ratrapage est inférieur à la note:
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(12.0)
|
||||||
|
# rattrapage > moyenne:
|
||||||
|
_, _, _ = G.create_note(evaluation=e_rat, etud=etud, note=18.0)
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(18.0)
|
||||||
|
# rattrapage vs absences
|
||||||
|
_, _, _ = G.create_note(evaluation=e, etud=etud, note=None) # abs
|
||||||
|
_, _, _ = G.create_note(evaluation=e_rat, etud=etud, note=17.0)
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(17.0)
|
||||||
|
# et sans note de rattrapage
|
||||||
|
_, _, _ = G.create_note(evaluation=e, etud=etud, note=10.0) # abs
|
||||||
|
_, _, _ = G.create_note(evaluation=e_rat, etud=etud, note=None)
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(10.0)
|
@ -27,6 +27,7 @@ from app.scodoc import sco_codes_parcours
|
|||||||
from app.scodoc import sco_evaluations
|
from app.scodoc import sco_evaluations
|
||||||
from app.scodoc import sco_formsemestre_validation
|
from app.scodoc import sco_formsemestre_validation
|
||||||
from app.scodoc import sco_parcours_dut
|
from app.scodoc import sco_parcours_dut
|
||||||
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_saisie_notes
|
from app.scodoc import sco_saisie_notes
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
@ -197,3 +198,19 @@ def run_sco_basic(verbose=False):
|
|||||||
assert not sco_parcours_dut.formsemestre_has_decisions(
|
assert not sco_parcours_dut.formsemestre_has_decisions(
|
||||||
sem["formsemestre_id"]
|
sem["formsemestre_id"]
|
||||||
), "décisions non effacées"
|
), "décisions non effacées"
|
||||||
|
|
||||||
|
# --- Décision de jury et validations des ECTS d'UE
|
||||||
|
for etud in etuds[:5]: # les etudiants notés
|
||||||
|
sco_formsemestre_validation.formsemestre_validation_etud_manu(
|
||||||
|
sem["formsemestre_id"],
|
||||||
|
etud["etudid"],
|
||||||
|
code_etat=sco_codes_parcours.ADJ,
|
||||||
|
assidu=True,
|
||||||
|
redirect=False,
|
||||||
|
)
|
||||||
|
# Vérifie que toutes les UE des étudiants notés ont été acquises:
|
||||||
|
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
|
||||||
|
for etud in etuds[:5]:
|
||||||
|
dec_ues = nt.get_etud_decision_ues(etud["etudid"])
|
||||||
|
for ue_id in dec_ues:
|
||||||
|
assert dec_ues[ue_id]["code"] in {"ADM", "CMP"}
|
||||||
|
@ -15,29 +15,47 @@ source "$SCRIPT_DIR/utils.sh"
|
|||||||
# Ce script doit tourner comme "root"
|
# Ce script doit tourner comme "root"
|
||||||
check_uid_root "$0"
|
check_uid_root "$0"
|
||||||
|
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
if [ ! $# -eq 2 ]
|
usage() {
|
||||||
then
|
echo "Usage: $0 [ --keep-env ] archive"
|
||||||
echo "Usage: $0 archive dbname"
|
echo "Exemple: $0 /tmp/mon-scodoc.tgz"
|
||||||
echo "Exemple: $0 /tmp/mon-scodoc.tgz SCODOC"
|
echo "OPTION"
|
||||||
|
echo "--keep_env garde la configuration courante"
|
||||||
exit 1
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($# < 1 || $# > 2))
|
||||||
|
then
|
||||||
|
usage
|
||||||
|
elif [ $# -eq 2 -a $1 != '--keep-env' -a $2 != '--keep-env' ] ; then
|
||||||
|
usage
|
||||||
|
elif [ $# -eq 1 ] ; then
|
||||||
|
echo "restauration des données et de la configuration originale (production)"
|
||||||
|
SRC=$1
|
||||||
|
DB_DEST="SCODOC"
|
||||||
|
else
|
||||||
|
echo "restauration des données dans la configuration actuelle"
|
||||||
|
DB_CURRENT=$(su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask scodoc-database -n)")
|
||||||
|
DB_DEST="$DB_CURRENT"
|
||||||
|
KEEP=1
|
||||||
|
if [ $1 = '--keep-env' ]; then
|
||||||
|
SRC=$2
|
||||||
|
else
|
||||||
|
SRC=$1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
DB_DUMP="${SCODOC_VAR_DIR}"/SCODOC.dump
|
||||||
SRC=$1
|
|
||||||
DBNAME=$2
|
|
||||||
|
|
||||||
|
|
||||||
# Safety check
|
# Safety check
|
||||||
echo "Ce script va remplacer les donnees de votre installation ScoDoc par celles"
|
echo "Ce script va remplacer les donnees de votre installation ScoDoc par celles"
|
||||||
echo "enregistrees dans le fichier fourni."
|
echo "enregistrées dans le fichier fourni."
|
||||||
echo "Ce fichier doit avoir ete cree par le script save_scodoc9_data.sh."
|
echo "Ce fichier doit avoir ete cree par le script save_scodoc9_data.sh."
|
||||||
echo
|
echo
|
||||||
echo "Attention: TOUTES LES DONNEES DE CE SCODOC SERONT REMPLACEES !"
|
echo "Attention: TOUTES LES DONNEES DE CE SCODOC SERONT REMPLACEES !"
|
||||||
echo "Notamment, tous les utilisateurs et departements existants seront effaces !"
|
echo "Notamment, tous les utilisateurs et departements existants seront effaces !"
|
||||||
echo
|
echo
|
||||||
echo "La base SQL $DBNAME sera effacée et remplacée !!!"
|
echo "La base SQL $DB_CURRENT sera effacée et remplacée !!!"
|
||||||
echo
|
echo
|
||||||
echo -n "Voulez vous poursuivre cette operation ? (y/n) [n]"
|
echo -n "Voulez vous poursuivre cette operation ? (y/n) [n]"
|
||||||
read -r ans
|
read -r ans
|
||||||
if [ ! "$(norm_ans "$ans")" = 'Y' ]
|
if [ ! "$(norm_ans "$ans")" = 'Y' ]
|
||||||
@ -47,8 +65,13 @@ then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# -- Stop ScoDoc
|
# -- Stop ScoDoc
|
||||||
echo "Arrêt de scodoc9..."
|
if [ $KEEP -ne 1 ]; then
|
||||||
systemctl stop scodoc9
|
echo "Arrêt de scodoc9..."
|
||||||
|
systemctl stop scodoc9
|
||||||
|
else
|
||||||
|
echo -n "Assurez-vous d'avoir arrété le serveur scodoc (validez pour continuer)"
|
||||||
|
read ans
|
||||||
|
fi
|
||||||
|
|
||||||
# Clear caches
|
# Clear caches
|
||||||
echo "Purge des caches..."
|
echo "Purge des caches..."
|
||||||
@ -70,22 +93,31 @@ echo "Vérification du propriétaire..."
|
|||||||
chown -R "${SCODOC_USER}:${SCODOC_GROUP}" "${SCODOC_VAR_DIR}" || die "Error chowning ${SCODOC_VAR_DIR}"
|
chown -R "${SCODOC_USER}:${SCODOC_GROUP}" "${SCODOC_VAR_DIR}" || die "Error chowning ${SCODOC_VAR_DIR}"
|
||||||
|
|
||||||
# --- La base SQL: nommée $(db_name).dump
|
# --- La base SQL: nommée $(db_name).dump
|
||||||
nb=$(su -c "psql -l" "$SCODOC_USER" | awk '{print $1}' | grep -c -e '^'"$DBNAME"'$')
|
nb=$(su -c "psql -l" "$SCODOC_USER" | awk '{print $1}' | grep -c -x "$DB_DEST")
|
||||||
if [ "$nb" -gt 0 ]
|
if [ "$nb" -gt 0 ]
|
||||||
then
|
then
|
||||||
echo "Suppression de la base $DBNAME..."
|
echo "Suppression de la base $DB_DEST..."
|
||||||
su -c "dropdb $DBNAME" "$SCODOC_USER" || die "Erreur destruction db"
|
su -c "dropdb $DB_DEST" "$SCODOC_USER" || die "Erreur destruction db"
|
||||||
fi
|
fi
|
||||||
su -c "createdb $DBNAME" "$SCODOC_USER" || die "Erreur création db"
|
su -c "createdb $DB_DEST" "$SCODOC_USER" || die "Erreur création db"
|
||||||
|
|
||||||
echo "Chargement de la base SQL..."
|
if [ ! -z $KEEP_ENV ] ; then
|
||||||
su -c "pg_restore -d $DBNAME ${SCODOC_VAR_DIR}/SCODOC.dump" "$SCODOC_USER" || die "Erreur chargement de la base SQL"
|
echo "conservation de la configuration actuelle"
|
||||||
|
cp "$SCODOC_VAR_DIR".old/.env "$SCODOC_VAR_DIR"/.env
|
||||||
|
echo "récupération des données..."
|
||||||
|
su -c "pg_restore -f - $DB_DUMP | psql -q $DB_DEST" "$SCODOC_USER" >/dev/null || die "Erreur chargement/renommage de la base SQL"
|
||||||
|
su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask db upgrade)" "$SCODOC_USER"
|
||||||
|
echo "redémarrez scodoc selon votre configuration"
|
||||||
|
else
|
||||||
|
# -- Apply migrations if needed (only on "production" database, = SCODOC sauf config particulière)
|
||||||
|
echo "restauration environnement de production"
|
||||||
|
echo "Chargement de la base SQL..."
|
||||||
|
su -c "pg_restore -d $DB_DEST $DB_DUMP" "$SCODOC_USER" || die "Erreur chargement de la base SQL"
|
||||||
|
export FLASK_ENV="production" # peut-être pas utile? : .env a été recopié
|
||||||
|
su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask db upgrade)" "$SCODOC_USER"
|
||||||
|
# -- Start ScoDoc
|
||||||
|
systemctl start scodoc9
|
||||||
|
fi
|
||||||
|
|
||||||
# -- Apply migrations if needed (only on "production" databse, = SCODOC sauf config particulière)
|
|
||||||
export FLASK_ENV="production"
|
|
||||||
su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask db upgrade)" "$SCODOC_USER"
|
|
||||||
|
|
||||||
# -- Start ScoDoc
|
|
||||||
systemctl start scodoc9
|
|
||||||
|
|
||||||
echo "Terminé."
|
echo "Terminé."
|
||||||
|
@ -20,6 +20,14 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "vérification de la configuration..."
|
||||||
|
DB_CURRENT=$(cd $SCODOC_DIR && source venv/bin/activate && flask scodoc-database -n)
|
||||||
|
if [ $DB_CURRENT != 'SCODOC' ]; then
|
||||||
|
echo "Ce script ne peut transférer les données que depuis une base nommée SCODOC (c'est normalement le cas pour un serveur en production)"
|
||||||
|
echo "Annulation"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Ce script est utile pour transférer toutes les données d'un serveur ScoDoc 9"
|
echo "Ce script est utile pour transférer toutes les données d'un serveur ScoDoc 9"
|
||||||
echo "à un autre ScoDoc 9."
|
echo "à un autre ScoDoc 9."
|
||||||
echo "Il est vivement recommandé de mettre à jour votre ScoDoc avant."
|
echo "Il est vivement recommandé de mettre à jour votre ScoDoc avant."
|
||||||
@ -44,8 +52,10 @@ DEST=$1
|
|||||||
db_name="$SCODOC_DB_PROD" # SCODOC
|
db_name="$SCODOC_DB_PROD" # SCODOC
|
||||||
|
|
||||||
# dump dans /opt/scodoc-data/SCODOC.dump
|
# dump dans /opt/scodoc-data/SCODOC.dump
|
||||||
|
echo "sauvegarde de la base de données"
|
||||||
pg_dump --format=custom --file="$SCODOC_VAR_DIR/$db_name.dump" "$db_name" || die "Error dumping database"
|
pg_dump --format=custom --file="$SCODOC_VAR_DIR/$db_name.dump" "$db_name" || die "Error dumping database"
|
||||||
|
|
||||||
|
echo "création du fichier d'archivage..."
|
||||||
# tar scodoc-data vers le fichier indiqué ou stdout
|
# tar scodoc-data vers le fichier indiqué ou stdout
|
||||||
(cd $(dirname "$SCODOC_VAR_DIR"); tar cfz "$DEST" $(basename "$SCODOC_VAR_DIR")) || die "Error archiving data"
|
(cd $(dirname "$SCODOC_VAR_DIR"); tar cfz "$DEST" $(basename "$SCODOC_VAR_DIR")) || die "Error archiving data"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user