forked from ScoDoc/ScoDoc
update
This commit is contained in:
commit
39a9f353d2
@ -6,3 +6,4 @@ from flask import Blueprint
|
|||||||
bp = Blueprint("api", __name__)
|
bp = Blueprint("api", __name__)
|
||||||
|
|
||||||
from app.api import sco_api
|
from app.api import sco_api
|
||||||
|
from app.api import tokens
|
||||||
|
@ -33,6 +33,7 @@ token_auth = HTTPTokenAuth()
|
|||||||
|
|
||||||
@basic_auth.verify_password
|
@basic_auth.verify_password
|
||||||
def verify_password(username, password):
|
def verify_password(username, password):
|
||||||
|
# breakpoint()
|
||||||
user = User.query.filter_by(user_name=username).first()
|
user = User.query.filter_by(user_name=username).first()
|
||||||
if user and user.check_password(password):
|
if user and user.check_password(password):
|
||||||
return user
|
return user
|
||||||
@ -51,3 +52,17 @@ def verify_token(token):
|
|||||||
@token_auth.error_handler
|
@token_auth.error_handler
|
||||||
def token_auth_error(status):
|
def token_auth_error(status):
|
||||||
return error_response(status)
|
return error_response(status)
|
||||||
|
|
||||||
|
|
||||||
|
def token_permission_required(permission):
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
scodoc_dept = getattr(g, "scodoc_dept", None)
|
||||||
|
if not current_user.has_permission(permission, scodoc_dept):
|
||||||
|
abort(403)
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
return login_required(decorated_function)
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
@ -48,9 +48,9 @@ from app.api.errors import bad_request
|
|||||||
from app import models
|
from app import models
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ScoDoc/api/list_depts", methods=["GET"])
|
@bp.route("list_depts", methods=["GET"])
|
||||||
@token_auth.login_required
|
@token_auth.login_required
|
||||||
def list_depts():
|
def list_depts():
|
||||||
depts = models.Departement.query.filter_by(visible=True).all()
|
depts = models.Departement.query.filter_by(visible=True).all()
|
||||||
data = {"items": [d.to_dict() for d in depts]}
|
data = [d.to_dict() for d in depts]
|
||||||
return jsonify(data)
|
return jsonify(data)
|
||||||
|
@ -213,6 +213,9 @@ class User(UserMixin, db.Model):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_token(token):
|
def check_token(token):
|
||||||
|
"""Retreive user for given token, chek token's validity
|
||||||
|
and returns the user object.
|
||||||
|
"""
|
||||||
user = User.query.filter_by(token=token).first()
|
user = User.query.filter_by(token=token).first()
|
||||||
if user is None or user.token_expiration < datetime.utcnow():
|
if user is None or user.token_expiration < datetime.utcnow():
|
||||||
return None
|
return None
|
||||||
|
@ -50,9 +50,19 @@ def scodoc(func):
|
|||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def scodoc_function(*args, **kwargs):
|
def scodoc_function(*args, **kwargs):
|
||||||
|
# print("@scodoc")
|
||||||
# interdit les POST si pas loggué
|
# interdit les POST si pas loggué
|
||||||
if request.method == "POST" and not current_user.is_authenticated:
|
if (
|
||||||
current_app.logger.info("POST by non authenticated user")
|
request.method == "POST"
|
||||||
|
and not current_user.is_authenticated
|
||||||
|
and not request.form.get(
|
||||||
|
"__ac_password"
|
||||||
|
) # exception pour compat API ScoDoc7
|
||||||
|
):
|
||||||
|
current_app.logger.info(
|
||||||
|
"POST by non authenticated user (request.form=%s)",
|
||||||
|
str(request.form)[:2048],
|
||||||
|
)
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"auth.login",
|
"auth.login",
|
||||||
@ -68,6 +78,7 @@ def scodoc(func):
|
|||||||
# current_app.logger.info("setting dept to None")
|
# current_app.logger.info("setting dept to None")
|
||||||
g.scodoc_dept = None
|
g.scodoc_dept = None
|
||||||
g.scodoc_dept_id = -1 # invalide
|
g.scodoc_dept_id = -1 # invalide
|
||||||
|
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
return scodoc_function
|
return scodoc_function
|
||||||
@ -97,8 +108,8 @@ def permission_required_compat_scodoc7(permission):
|
|||||||
def decorator(f):
|
def decorator(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
# current_app.logger.warning("PERMISSION; kwargs=%s" % str(kwargs))
|
|
||||||
# cherche les paramètre d'auth:
|
# cherche les paramètre d'auth:
|
||||||
|
# print("@permission_required_compat_scodoc7")
|
||||||
auth_ok = False
|
auth_ok = False
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
user_name = request.args.get("__ac_name")
|
user_name = request.args.get("__ac_name")
|
||||||
@ -113,7 +124,6 @@ def permission_required_compat_scodoc7(permission):
|
|||||||
if u and u.check_password(user_password):
|
if u and u.check_password(user_password):
|
||||||
auth_ok = True
|
auth_ok = True
|
||||||
flask_login.login_user(u)
|
flask_login.login_user(u)
|
||||||
|
|
||||||
# reprend le chemin classique:
|
# reprend le chemin classique:
|
||||||
scodoc_dept = getattr(g, "scodoc_dept", None)
|
scodoc_dept = getattr(g, "scodoc_dept", None)
|
||||||
|
|
||||||
@ -150,6 +160,7 @@ def scodoc7func(func):
|
|||||||
2. or be called directly from Python.
|
2. or be called directly from Python.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# print("@scodoc7func")
|
||||||
# Détermine si on est appelé via une route ("toplevel")
|
# Détermine si on est appelé via une route ("toplevel")
|
||||||
# ou par un appel de fonction python normal.
|
# ou par un appel de fonction python normal.
|
||||||
top_level = not hasattr(g, "scodoc7_decorated")
|
top_level = not hasattr(g, "scodoc7_decorated")
|
||||||
|
@ -41,7 +41,7 @@ class Identite(db.Model):
|
|||||||
code_nip = db.Column(db.Text())
|
code_nip = db.Column(db.Text())
|
||||||
code_ine = db.Column(db.Text())
|
code_ine = db.Column(db.Text())
|
||||||
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
||||||
# ne pas utiliser après migrate_scodoc7_dept_archive
|
# ne pas utiliser après migrate_scodoc7_dept_archives
|
||||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||||
#
|
#
|
||||||
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
|
billets = db.relationship("BilletAbsence", backref="etudiant", lazy="dynamic")
|
||||||
|
@ -137,6 +137,12 @@ class NotesTag(db.Model):
|
|||||||
# Association tag <-> module
|
# Association tag <-> module
|
||||||
notes_modules_tags = db.Table(
|
notes_modules_tags = db.Table(
|
||||||
"notes_modules_tags",
|
"notes_modules_tags",
|
||||||
db.Column("tag_id", db.Integer, db.ForeignKey("notes_tags.id")),
|
db.Column(
|
||||||
db.Column("module_id", db.Integer, db.ForeignKey("notes_modules.id")),
|
"tag_id",
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_tags.id", ondelete="CASCADE"),
|
||||||
|
),
|
||||||
|
db.Column(
|
||||||
|
"module_id", db.Integer, db.ForeignKey("notes_modules.id", ondelete="CASCADE")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
@ -79,7 +79,7 @@ class FormSemestre(db.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
# Ancien id ScoDoc7 pour les migrations de bases anciennes
|
||||||
# ne pas utiliser après migrate_scodoc7_dept_archive
|
# ne pas utiliser après migrate_scodoc7_dept_archives
|
||||||
scodoc7_id = db.Column(db.Text(), nullable=True)
|
scodoc7_id = db.Column(db.Text(), nullable=True)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
@ -987,7 +987,7 @@ def mail_bulletin(formsemestre_id, I, pdfdata, filename, recipient_addr):
|
|||||||
bcc = copy_addr.strip()
|
bcc = copy_addr.strip()
|
||||||
else:
|
else:
|
||||||
bcc = ""
|
bcc = ""
|
||||||
msg = Message(subject, sender=sender, recipients=recipients, bcc=bcc)
|
msg = Message(subject, sender=sender, recipients=recipients, bcc=[bcc])
|
||||||
msg.body = hea
|
msg.body = hea
|
||||||
|
|
||||||
# Attach pdf
|
# Attach pdf
|
||||||
|
@ -1949,6 +1949,15 @@ class BasePreferences(object):
|
|||||||
"name": name,
|
"name": name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
if len(pdb) > 1:
|
||||||
|
# suppress buggy duplicates (may come from corrupted database for ice ages)
|
||||||
|
log(
|
||||||
|
f"**oups** detected duplicated preference !\n({self.dept_id}, {formsemestre_id}, {name}, {value})"
|
||||||
|
)
|
||||||
|
for obj in pdb[1:]:
|
||||||
|
self._editor.delete(cnx, obj["id"])
|
||||||
|
pdb = [pdb[0]]
|
||||||
|
|
||||||
if not pdb:
|
if not pdb:
|
||||||
# crée préférence
|
# crée préférence
|
||||||
log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
|
log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
|
||||||
@ -1962,10 +1971,8 @@ class BasePreferences(object):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
modif = True
|
modif = True
|
||||||
log("create pref sem=%s %s=%s" % (formsemestre_id, name, value))
|
|
||||||
else:
|
else:
|
||||||
# edit existing value
|
# edit existing value
|
||||||
|
|
||||||
existing_value = pdb[0]["value"] # old stored value
|
existing_value = pdb[0]["value"] # old stored value
|
||||||
if (
|
if (
|
||||||
(existing_value != value)
|
(existing_value != value)
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
{% macro render_field(field) %}
|
{% macro render_field(field, auth_name=None) %}
|
||||||
<tr style="">
|
<tr style="">
|
||||||
<td class="wtf-field">{{ field.label }}</td>
|
{% if auth_name %}
|
||||||
|
<td class="wtf-field"> {{ field.label }}<span style="font-weight:700;"> ({{ auth_name }}):</span></td>
|
||||||
|
{% else %}
|
||||||
|
<td class="wtf-field">{{ field.label }}</td>
|
||||||
|
{% endif %}
|
||||||
<td class="wtf-field">{{ field(**kwargs)|safe }}
|
<td class="wtf-field">{{ field(**kwargs)|safe }}
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
<ul class=errors>
|
<ul class=errors>
|
||||||
@ -20,16 +24,20 @@
|
|||||||
<h1>Modification du compte ScoDoc <tt>{{form.user_name.data}}</tt></h1>
|
<h1>Modification du compte ScoDoc <tt>{{form.user_name.data}}</tt></h1>
|
||||||
<div class="help">
|
<div class="help">
|
||||||
<p>Identifiez-vous avez votre mot de passe actuel</p>
|
<p>Identifiez-vous avez votre mot de passe actuel</p>
|
||||||
<p>Vous pouvez changer le mot de passe et/ou l'adresse email.</p>
|
|
||||||
<p>Les champs vides ne seront pas changés.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<form method=post>
|
<form method=post>
|
||||||
{{ form.user_name }}
|
{{ form.user_name }}
|
||||||
{{ form.csrf_token }}
|
{{ form.csrf_token }}
|
||||||
<table class="tf"><tbody>
|
<table class="tf"><tbody>
|
||||||
{{ render_field(form.old_password, size=14,
|
{{ render_field(form.old_password, size=14, auth_name=auth_username,
|
||||||
style="padding:1px; margin-left: 1em; margin-top: 4px;") }}
|
style="padding:1px; margin-left: 1em; margin-top: 4px;") }}
|
||||||
{{ render_field(form.new_password, size=14,
|
<tr>
|
||||||
|
<td colspan=""2">
|
||||||
|
<p>Vous pouvez changer le mot de passe et/ou l'adresse email.</p>
|
||||||
|
<p>Les champs laissés vides ne seront pas modifiés.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{ render_field(form.new_password, size=14,
|
||||||
style="padding:1px; margin-left: 1em; margin-top: 12px;") }}
|
style="padding:1px; margin-left: 1em; margin-top: 12px;") }}
|
||||||
{{ render_field(form.bis_password, size=14,
|
{{ render_field(form.bis_password, size=14,
|
||||||
style="padding:1px; margin-left: 1em; margin-top: 4px;") }}
|
style="padding:1px; margin-left: 1em; margin-top: 4px;") }}
|
||||||
|
@ -15,4 +15,6 @@
|
|||||||
<p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
|
<p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p>A bientôt !</p>
|
<p>A bientôt !</p>
|
||||||
|
|
||||||
|
<p>Ce message a été généré automatiquement par le serveur ScoDoc.</p>
|
@ -8,4 +8,6 @@ Votre identifiant de connexion est: {{ user.user_name }}
|
|||||||
{{ url_for('auth.reset_password', token=token, _external=True) }}
|
{{ url_for('auth.reset_password', token=token, _external=True) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p>A bientôt !</p>
|
A bientôt !
|
||||||
|
|
||||||
|
Ce message a été généré automatiquement par le serveur ScoDoc.
|
@ -1058,7 +1058,8 @@ def AddBilletAbsence(
|
|||||||
code_nip=None,
|
code_nip=None,
|
||||||
code_ine=None,
|
code_ine=None,
|
||||||
justified=True,
|
justified=True,
|
||||||
xml_reply=True,
|
format="json",
|
||||||
|
xml_reply=True, # deprecated
|
||||||
):
|
):
|
||||||
"""Mémorise un "billet"
|
"""Mémorise un "billet"
|
||||||
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")
|
||||||
@ -1082,6 +1083,7 @@ def AddBilletAbsence(
|
|||||||
raise ValueError("invalid dates")
|
raise ValueError("invalid dates")
|
||||||
#
|
#
|
||||||
justified = bool(justified)
|
justified = bool(justified)
|
||||||
|
xml_reply = bool(xml_reply)
|
||||||
#
|
#
|
||||||
cnx = ndb.GetDBConnexion()
|
cnx = ndb.GetDBConnexion()
|
||||||
billet_id = sco_abs.billet_absence_create(
|
billet_id = sco_abs.billet_absence_create(
|
||||||
@ -1095,17 +1097,17 @@ def AddBilletAbsence(
|
|||||||
"justified": justified,
|
"justified": justified,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if xml_reply:
|
if xml_reply: # backward compat
|
||||||
# Renvoie le nouveau billet en XML
|
format = "xml"
|
||||||
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
|
|
||||||
tab = _tableBillets(billets, etud=etud)
|
# Renvoie le nouveau billet au format demandé
|
||||||
log("AddBilletAbsence: new billet_id=%s (%gs)" % (billet_id, time.time() - t0))
|
billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
|
||||||
return tab.make_page(format="xml")
|
tab = _tableBillets(billets, etud=etud)
|
||||||
else:
|
log("AddBilletAbsence: new billet_id=%s (%gs)" % (billet_id, time.time() - t0))
|
||||||
return billet_id
|
return tab.make_page(format=format)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/AddBilletAbsenceForm")
|
@bp.route("/AddBilletAbsenceForm", methods=["GET", "POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoAbsAddBillet)
|
@permission_required(Permission.ScoAbsAddBillet)
|
||||||
@scodoc7func
|
@scodoc7func
|
||||||
|
@ -264,7 +264,7 @@ sco_publish(
|
|||||||
|
|
||||||
|
|
||||||
@bp.route(
|
@bp.route(
|
||||||
"formsemestre_bulletinetud", methods=["GET", "POST"]
|
"/formsemestre_bulletinetud", methods=["GET", "POST"]
|
||||||
) # POST pour compat anciens clients PHP (deprecated)
|
) # POST pour compat anciens clients PHP (deprecated)
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required_compat_scodoc7(Permission.ScoView)
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
|
@ -110,7 +110,7 @@ def get_etud_dept():
|
|||||||
# il peut y avoir plusieurs réponses si l'étudiant est passé par plusieurs départements
|
# il peut y avoir plusieurs réponses si l'étudiant est passé par plusieurs départements
|
||||||
etuds = Identite.query.filter_by(code_nip=request.args["code_nip"]).all()
|
etuds = Identite.query.filter_by(code_nip=request.args["code_nip"]).all()
|
||||||
elif "code_ine" in request.args:
|
elif "code_ine" in request.args:
|
||||||
etuds = Identite.query.filter_by(code_nip=request.args["code_ine"]).all()
|
etuds = Identite.query.filter_by(code_ine=request.args["code_ine"]).all()
|
||||||
else:
|
else:
|
||||||
raise BadRequest(
|
raise BadRequest(
|
||||||
"missing argument (expected one among: etudid, code_nip or code_ine)"
|
"missing argument (expected one among: etudid, code_nip or code_ine)"
|
||||||
|
@ -302,7 +302,34 @@ sco_publish(
|
|||||||
methods=["GET", "POST"],
|
methods=["GET", "POST"],
|
||||||
)
|
)
|
||||||
|
|
||||||
sco_publish("/groups_view", sco_groups_view.groups_view, Permission.ScoView)
|
|
||||||
|
@bp.route("/groups_view")
|
||||||
|
@scodoc
|
||||||
|
@permission_required_compat_scodoc7(Permission.ScoView)
|
||||||
|
@scodoc7func
|
||||||
|
def groups_view(
|
||||||
|
group_ids=(),
|
||||||
|
format="html",
|
||||||
|
# Options pour listes:
|
||||||
|
with_codes=0,
|
||||||
|
etat=None,
|
||||||
|
with_paiement=0, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
|
||||||
|
with_archives=0, # ajoute colonne avec noms fichiers archivés
|
||||||
|
with_annotations=0,
|
||||||
|
formsemestre_id=None,
|
||||||
|
):
|
||||||
|
return sco_groups_view.groups_view(
|
||||||
|
group_ids=(),
|
||||||
|
format=format,
|
||||||
|
# Options pour listes:
|
||||||
|
with_codes=with_codes,
|
||||||
|
etat=etat,
|
||||||
|
with_paiement=with_paiement, # si vrai, ajoute colonnes infos paiement droits et finalisation inscription (lent car interrogation portail)
|
||||||
|
with_archives=with_archives, # ajoute colonne avec noms fichiers archivés
|
||||||
|
with_annotations=with_annotations,
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
sco_publish(
|
sco_publish(
|
||||||
"/export_groups_as_moodle_csv",
|
"/export_groups_as_moodle_csv",
|
||||||
|
@ -35,6 +35,7 @@ Emmanuel Viennet, 2021
|
|||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
from enum import auto, IntEnum
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
@ -116,6 +117,12 @@ class ChangePasswordForm(FlaskForm):
|
|||||||
raise ValidationError("Mot de passe actuel incorrect, ré-essayez")
|
raise ValidationError("Mot de passe actuel incorrect, ré-essayez")
|
||||||
|
|
||||||
|
|
||||||
|
class Mode(IntEnum):
|
||||||
|
WELCOME_AND_CHANGE_PASSWORD = auto()
|
||||||
|
WELCOME_ONLY = auto()
|
||||||
|
SILENT = auto()
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/")
|
@bp.route("/")
|
||||||
@bp.route("/index_html")
|
@bp.route("/index_html")
|
||||||
@scodoc
|
@scodoc
|
||||||
@ -145,6 +152,8 @@ def user_info(user_name, format="json"):
|
|||||||
def create_user_form(user_name=None, edit=0, all_roles=1):
|
def create_user_form(user_name=None, edit=0, all_roles=1):
|
||||||
"form. création ou edition utilisateur"
|
"form. création ou edition utilisateur"
|
||||||
auth_dept = current_user.dept
|
auth_dept = current_user.dept
|
||||||
|
auth_username = current_user.user_name
|
||||||
|
from_mail = current_user.email
|
||||||
initvalues = {}
|
initvalues = {}
|
||||||
edit = int(edit)
|
edit = int(edit)
|
||||||
all_roles = int(all_roles)
|
all_roles = int(all_roles)
|
||||||
@ -529,19 +538,20 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||||||
)
|
)
|
||||||
return "\n".join(H) + msg + "\n" + tf[1] + F
|
return "\n".join(H) + msg + "\n" + tf[1] + F
|
||||||
# Traitement initial (mode) : 3 cas
|
# Traitement initial (mode) : 3 cas
|
||||||
|
# cf énumération Mode
|
||||||
# A: envoi de welcome + procedure de reset
|
# A: envoi de welcome + procedure de reset
|
||||||
# B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
|
# B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
|
||||||
# C: Aucun envoi (mot de passe saisi dans le formulaire)
|
# C: Aucun envoi (mot de passe saisi dans le formulaire)
|
||||||
if vals["welcome:list"] == "1":
|
if vals["welcome:list"] == "1":
|
||||||
if vals["reset_password:list"] == "1":
|
if vals["reset_password:list"] == "1":
|
||||||
mode = "A"
|
mode = Mode.WELCOME_AND_CHANGE_PASSWORD
|
||||||
else:
|
else:
|
||||||
mode = "B"
|
mode = Mode.WELCOME_ONLY
|
||||||
else:
|
else:
|
||||||
mode = "C"
|
mode = Mode.SILENT
|
||||||
|
|
||||||
# check passwords
|
# check passwords
|
||||||
if mode == "A":
|
if mode == Mode.WELCOME_AND_CHANGE_PASSWORD:
|
||||||
vals["password"] = generate_password()
|
vals["password"] = generate_password()
|
||||||
else:
|
else:
|
||||||
if vals["password"]:
|
if vals["password"]:
|
||||||
@ -567,14 +577,14 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
|
|||||||
db.session.add(u)
|
db.session.add(u)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
# envoi éventuel d'un message
|
# envoi éventuel d'un message
|
||||||
if mode == "A" or mode == "B":
|
if mode == Mode.WELCOME_AND_CHANGE_PASSWORD or mode == Mode.WELCOME_ONLY:
|
||||||
if mode == "A":
|
if mode == Mode.WELCOME_AND_CHANGE_PASSWORD:
|
||||||
token = u.get_reset_password_token()
|
token = u.get_reset_password_token()
|
||||||
else:
|
else:
|
||||||
token = None
|
token = None
|
||||||
send_email(
|
send_email(
|
||||||
"[ScoDoc] Création de votre compte",
|
"[ScoDoc] Création de votre compte",
|
||||||
sender=current_app.config["ADMINS"][0],
|
sender=from_mail, # current_app.config["ADMINS"][0],
|
||||||
recipients=[u.email],
|
recipients=[u.email],
|
||||||
text_body=render_template("email/welcome.txt", user=u, token=token),
|
text_body=render_template("email/welcome.txt", user=u, token=token),
|
||||||
html_body=render_template(
|
html_body=render_template(
|
||||||
@ -787,7 +797,10 @@ def form_change_password(user_name=None):
|
|||||||
return redirect(destination)
|
return redirect(destination)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"auth/change_password.html", form=form, title="Modification compte ScoDoc"
|
"auth/change_password.html",
|
||||||
|
form=form,
|
||||||
|
title="Modification compte ScoDoc",
|
||||||
|
auth_username=current_user.user_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
34
migrations/versions/75cf18659984_cascade_tags_modules.py
Normal file
34
migrations/versions/75cf18659984_cascade_tags_modules.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"""cascade tags modules
|
||||||
|
|
||||||
|
Revision ID: 75cf18659984
|
||||||
|
Revises: d74b4e16fb3c
|
||||||
|
Create Date: 2021-10-26 10:17:15.547905
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '75cf18659984'
|
||||||
|
down_revision = 'd74b4e16fb3c'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint('notes_modules_tags_tag_id_fkey', 'notes_modules_tags', type_='foreignkey')
|
||||||
|
op.drop_constraint('notes_modules_tags_module_id_fkey', 'notes_modules_tags', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'notes_modules_tags', 'notes_tags', ['tag_id'], ['id'], ondelete='CASCADE')
|
||||||
|
op.create_foreign_key(None, 'notes_modules_tags', 'notes_modules', ['module_id'], ['id'], ondelete='CASCADE')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint(None, 'notes_modules_tags', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'notes_modules_tags', type_='foreignkey')
|
||||||
|
op.create_foreign_key('notes_modules_tags_module_id_fkey', 'notes_modules_tags', 'notes_modules', ['module_id'], ['id'])
|
||||||
|
op.create_foreign_key('notes_modules_tags_tag_id_fkey', 'notes_modules_tags', 'notes_tags', ['tag_id'], ['id'])
|
||||||
|
# ### end Alembic commands ###
|
@ -1,133 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- mode: python -*-
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""Exemple connexion sur ScoDoc et utilisation de l'API
|
|
||||||
|
|
||||||
- Ouverture session
|
|
||||||
- Liste semestres
|
|
||||||
- Liste modules
|
|
||||||
- Creation d'une évaluation
|
|
||||||
- Saisie d'une note
|
|
||||||
|
|
||||||
Attention: cet exemple est en Python 3 (>= 3.6)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import urllib3
|
|
||||||
import pdb
|
|
||||||
from pprint import pprint as pp
|
|
||||||
from flask import g, url_for
|
|
||||||
|
|
||||||
# A modifier pour votre serveur:
|
|
||||||
CHECK_CERTIFICATE = False # set to True in production
|
|
||||||
BASEURL = "https://scodoc.xxx.net/ScoDoc/RT/Scolarite"
|
|
||||||
USER = "XXX"
|
|
||||||
PASSWORD = "XXX"
|
|
||||||
|
|
||||||
# ---
|
|
||||||
if not CHECK_CERTIFICATE:
|
|
||||||
urllib3.disable_warnings()
|
|
||||||
|
|
||||||
|
|
||||||
class ScoError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def GET(s, path, errmsg=None):
|
|
||||||
"""Get and returns as JSON"""
|
|
||||||
r = s.get(BASEURL + "/" + path, verify=CHECK_CERTIFICATE)
|
|
||||||
if r.status_code != 200:
|
|
||||||
raise ScoError(errmsg or "erreur !")
|
|
||||||
return r.json() # decode la reponse JSON
|
|
||||||
|
|
||||||
|
|
||||||
def POST(s, path, data, errmsg=None):
|
|
||||||
"""Post"""
|
|
||||||
r = s.post(BASEURL + "/" + path, data=data, verify=CHECK_CERTIFICATE)
|
|
||||||
if r.status_code != 200:
|
|
||||||
raise ScoError(errmsg or "erreur !")
|
|
||||||
return r.text
|
|
||||||
|
|
||||||
|
|
||||||
# --- Ouverture session (login)
|
|
||||||
s = requests.Session()
|
|
||||||
s.post(
|
|
||||||
"https://deb11.viennet.net/api/auth/login",
|
|
||||||
data={"user_name": USER, "password": PASSWORD},
|
|
||||||
)
|
|
||||||
r = s.get(BASEURL, auth=(USER, PASSWORD), verify=CHECK_CERTIFICATE)
|
|
||||||
if r.status_code != 200:
|
|
||||||
raise ScoError("erreur de connection: vérifier adresse et identifiants")
|
|
||||||
|
|
||||||
# --- Recupere la liste de tous les semestres:
|
|
||||||
sems = GET(s, "Notes/formsemestre_list?format=json", "Aucun semestre !")
|
|
||||||
|
|
||||||
# sems est une liste de semestres (dictionnaires)
|
|
||||||
for sem in sems:
|
|
||||||
if sem["etat"]:
|
|
||||||
break
|
|
||||||
|
|
||||||
if sem["etat"] == "0":
|
|
||||||
raise ScoError("Aucun semestre non verrouillé !")
|
|
||||||
|
|
||||||
# Affiche le semestre trouvé:
|
|
||||||
pp(sem)
|
|
||||||
|
|
||||||
# ---- Récupère la description de ce semestre:
|
|
||||||
# semdescr = GET(s, f"Notes/formsemestre_description?formsemestre_id={sem['formsemestre_id']}&with_evals=0&format=json" )
|
|
||||||
|
|
||||||
# ---- Liste les modules et prend le premier
|
|
||||||
mods = GET(s, f"/Notes/moduleimpl_list?formsemestre_id={sem['formsemestre_id']}")
|
|
||||||
print(f"{len(mods)} modules dans le semestre {sem['titre']}")
|
|
||||||
|
|
||||||
mod = mods[0]
|
|
||||||
|
|
||||||
# ---- Etudiants inscrits dans ce module
|
|
||||||
inscrits = GET(
|
|
||||||
s, f"Notes/do_moduleimpl_inscription_list?moduleimpl_id={mod['moduleimpl_id']}"
|
|
||||||
)
|
|
||||||
print(f"{len(inscrits)} inscrits dans ce module")
|
|
||||||
# prend le premier inscrit, au hasard:
|
|
||||||
etudid = inscrits[0]["etudid"]
|
|
||||||
|
|
||||||
# ---- Création d'une evaluation le dernier jour du semestre
|
|
||||||
jour = sem["date_fin"]
|
|
||||||
evaluation_id = POST(
|
|
||||||
s,
|
|
||||||
"/Notes/do_evaluation_create",
|
|
||||||
data={
|
|
||||||
"moduleimpl_id": mod["moduleimpl_id"],
|
|
||||||
"coefficient": 1,
|
|
||||||
"jour": jour, # "5/9/2019",
|
|
||||||
"heure_debut": "9h00",
|
|
||||||
"heure_fin": "10h00",
|
|
||||||
"note_max": 20, # notes sur 20
|
|
||||||
"description": "essai",
|
|
||||||
},
|
|
||||||
errmsg="échec création évaluation",
|
|
||||||
)
|
|
||||||
|
|
||||||
print(
|
|
||||||
f"Evaluation créée dans le module {mod['moduleimpl_id']}, evaluation_id={evaluation_id}"
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
"Pour vérifier, aller sur: ",
|
|
||||||
url_for(
|
|
||||||
"notes.moduleimpl_status",
|
|
||||||
scodoc_dept="DEPT",
|
|
||||||
moduleimpl_id=mod["moduleimpl_id"],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---- Saisie d'une note
|
|
||||||
junk = POST(
|
|
||||||
s,
|
|
||||||
"/Notes/save_note",
|
|
||||||
data={
|
|
||||||
"etudid": etudid,
|
|
||||||
"evaluation_id": evaluation_id,
|
|
||||||
"value": 16.66, # la note !
|
|
||||||
"comment": "test API",
|
|
||||||
},
|
|
||||||
)
|
|
@ -319,9 +319,9 @@ def import_scodoc7_dept(dept: str, dept_db_name: str = ""): # import-scodoc7-de
|
|||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
@click.argument("dept", default="")
|
@click.argument("dept", default="")
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def migrate_scodoc7_dept_archive(dept: str): # migrate-scodoc7-dept-archive
|
def migrate_scodoc7_dept_archives(dept: str): # migrate-scodoc7-dept-archives
|
||||||
"""Post-migration: renomme les archives en fonction des id de ScoDoc 9"""
|
"""Post-migration: renomme les archives en fonction des id de ScoDoc 9"""
|
||||||
tools.migrate_scodoc7_dept_archive(dept)
|
tools.migrate_scodoc7_dept_archives(dept)
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
|
149
tests/api/exemple-api-basic.py
Normal file
149
tests/api/exemple-api-basic.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Exemple utilisation API ScoDoc 9 avec jeton obtenu par basic athentication
|
||||||
|
|
||||||
|
|
||||||
|
Utilisation: créer les variables d'environnement: (indiquer les valeurs
|
||||||
|
pour le serveur ScoDoc que vous voulez interroger)
|
||||||
|
|
||||||
|
export SCODOC_URL="https://scodoc.xxx.net/"
|
||||||
|
export SCODOC_USER="xxx"
|
||||||
|
export SCODOC_PASSWD="xxx"
|
||||||
|
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
|
||||||
|
|
||||||
|
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
|
||||||
|
|
||||||
|
|
||||||
|
Travail en cours, un seul point d'API (list_depts).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
import pdb
|
||||||
|
import requests
|
||||||
|
import urllib3
|
||||||
|
from pprint import pprint as pp
|
||||||
|
|
||||||
|
# --- Lecture configuration (variables d'env ou .env)
|
||||||
|
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
load_dotenv(os.path.join(BASEDIR, ".env"))
|
||||||
|
CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
|
||||||
|
SCODOC_URL = os.environ["SCODOC_URL"]
|
||||||
|
SCODOC_DEPT = os.environ["SCODOC_DEPT"]
|
||||||
|
DEPT_URL = SCODOC_URL + "/ScoDoc/" + SCODOC_DEPT + "/Scolarite/"
|
||||||
|
SCODOC_USER = os.environ["SCODOC_USER"]
|
||||||
|
SCODOC_PASSWORD = os.environ["SCODOC_PASSWD"]
|
||||||
|
print(f"SCODOC_URL={SCODOC_URL}")
|
||||||
|
|
||||||
|
# ---
|
||||||
|
if not CHECK_CERTIFICATE:
|
||||||
|
urllib3.disable_warnings()
|
||||||
|
|
||||||
|
|
||||||
|
class ScoError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def GET(path: str, headers={}, errmsg=None):
|
||||||
|
"""Get and returns as JSON"""
|
||||||
|
r = requests.get(
|
||||||
|
DEPT_URL + "/" + path, headers=headers or HEADERS, verify=CHECK_CERTIFICATE
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise ScoError(errmsg or "erreur !")
|
||||||
|
return r.json() # decode la reponse JSON
|
||||||
|
|
||||||
|
|
||||||
|
def POST(s, path: str, data: dict, errmsg=None):
|
||||||
|
"""Post"""
|
||||||
|
r = s.post(DEPT_URL + "/" + path, data=data, verify=CHECK_CERTIFICATE)
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise ScoError(errmsg or "erreur !")
|
||||||
|
return r.text
|
||||||
|
|
||||||
|
|
||||||
|
# --- Obtention du jeton (token)
|
||||||
|
r = requests.post(
|
||||||
|
SCODOC_URL + "/ScoDoc/api/tokens", auth=(SCODOC_USER, SCODOC_PASSWORD)
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
token = r.json()["token"]
|
||||||
|
HEADERS = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
|
r = requests.get(
|
||||||
|
SCODOC_URL + "/ScoDoc/api/list_depts", headers=HEADERS, verify=CHECK_CERTIFICATE
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise ScoError("erreur de connexion: vérifier adresse et identifiants")
|
||||||
|
|
||||||
|
pp(r.json())
|
||||||
|
|
||||||
|
|
||||||
|
# # --- Recupere la liste de tous les semestres:
|
||||||
|
# sems = GET(s, "Notes/formsemestre_list?format=json", "Aucun semestre !")
|
||||||
|
|
||||||
|
# # sems est une liste de semestres (dictionnaires)
|
||||||
|
# for sem in sems:
|
||||||
|
# if sem["etat"]:
|
||||||
|
# break
|
||||||
|
|
||||||
|
# if sem["etat"] == "0":
|
||||||
|
# raise ScoError("Aucun semestre non verrouillé !")
|
||||||
|
|
||||||
|
# # Affiche le semestre trouvé:
|
||||||
|
# pp(sem)
|
||||||
|
|
||||||
|
# # ---- Récupère la description de ce semestre:
|
||||||
|
# # semdescr = GET(s, f"Notes/formsemestre_description?formsemestre_id={sem['formsemestre_id']}&with_evals=0&format=json" )
|
||||||
|
|
||||||
|
# # ---- Liste les modules et prend le premier
|
||||||
|
# mods = GET(s, f"/Notes/moduleimpl_list?formsemestre_id={sem['formsemestre_id']}")
|
||||||
|
# print(f"{len(mods)} modules dans le semestre {sem['titre']}")
|
||||||
|
|
||||||
|
# mod = mods[0]
|
||||||
|
|
||||||
|
# # ---- Etudiants inscrits dans ce module
|
||||||
|
# inscrits = GET(
|
||||||
|
# s, f"Notes/do_moduleimpl_inscription_list?moduleimpl_id={mod['moduleimpl_id']}"
|
||||||
|
# )
|
||||||
|
# print(f"{len(inscrits)} inscrits dans ce module")
|
||||||
|
# # prend le premier inscrit, au hasard:
|
||||||
|
# etudid = inscrits[0]["etudid"]
|
||||||
|
|
||||||
|
# # ---- Création d'une evaluation le dernier jour du semestre
|
||||||
|
# jour = sem["date_fin"]
|
||||||
|
# evaluation_id = POST(
|
||||||
|
# s,
|
||||||
|
# "/Notes/do_evaluation_create",
|
||||||
|
# data={
|
||||||
|
# "moduleimpl_id": mod["moduleimpl_id"],
|
||||||
|
# "coefficient": 1,
|
||||||
|
# "jour": jour, # "5/9/2019",
|
||||||
|
# "heure_debut": "9h00",
|
||||||
|
# "heure_fin": "10h00",
|
||||||
|
# "note_max": 20, # notes sur 20
|
||||||
|
# "description": "essai",
|
||||||
|
# },
|
||||||
|
# errmsg="échec création évaluation",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# print(
|
||||||
|
# f"Evaluation créée dans le module {mod['moduleimpl_id']}, evaluation_id={evaluation_id}"
|
||||||
|
# )
|
||||||
|
# print(
|
||||||
|
# f"Pour vérifier, aller sur: {DEPT_URL}/Notes/moduleimpl_status?moduleimpl_id={mod['moduleimpl_id']}",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # ---- Saisie d'une note
|
||||||
|
# junk = POST(
|
||||||
|
# s,
|
||||||
|
# "/Notes/save_note",
|
||||||
|
# data={
|
||||||
|
# "etudid": etudid,
|
||||||
|
# "evaluation_id": evaluation_id,
|
||||||
|
# "value": 16.66, # la note !
|
||||||
|
# "comment": "test API",
|
||||||
|
# },
|
||||||
|
# )
|
197
tests/api/exemple-api-scodoc7.py
Normal file
197
tests/api/exemple-api-scodoc7.py
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Exemple connexion sur ScoDoc 9 et utilisation de l'ancienne API ScoDoc 7
|
||||||
|
à la mode "PHP": les gens passaient directement __ac_name et __ac_password
|
||||||
|
dans chaque requête, en POST ou en GET.
|
||||||
|
|
||||||
|
Cela n'a jamais été documenté mais était implicitement supporté. C'est "deprecated"
|
||||||
|
et ne sera plus supporté à partir de juillet 2022.
|
||||||
|
|
||||||
|
Ce script va tester:
|
||||||
|
- Liste semestres
|
||||||
|
- Liste modules
|
||||||
|
- Creation d'une évaluation
|
||||||
|
- Saisie d'une note
|
||||||
|
|
||||||
|
Utilisation: créer les variables d'environnement: (indiquer les valeurs
|
||||||
|
pour le serveur ScoDoc que vous voulez interroger)
|
||||||
|
|
||||||
|
export SCODOC_URL="https://scodoc.xxx.net/"
|
||||||
|
export SCODOC_USER="xxx"
|
||||||
|
export SCODOC_PASSWD="xxx"
|
||||||
|
export CHECK_CERTIFICATE=0 # ou 1 si serveur de production avec certif SSL valide
|
||||||
|
|
||||||
|
(on peut aussi placer ces valeurs dans un fichier .env du répertoire tests/api).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pdb
|
||||||
|
import requests
|
||||||
|
import urllib3
|
||||||
|
from pprint import pprint as pp
|
||||||
|
|
||||||
|
# --- Lecture configuration (variables d'env ou .env)
|
||||||
|
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
load_dotenv(os.path.join(BASEDIR, ".env"))
|
||||||
|
CHECK_CERTIFICATE = bool(int(os.environ.get("CHECK_CERTIFICATE", False)))
|
||||||
|
SCODOC_URL = os.environ["SCODOC_URL"]
|
||||||
|
SCODOC_DEPT = os.environ["SCODOC_DEPT"]
|
||||||
|
DEPT_URL = SCODOC_URL + "/ScoDoc/" + SCODOC_DEPT + "/Scolarite"
|
||||||
|
SCODOC_USER = os.environ["SCODOC_USER"]
|
||||||
|
SCODOC_PASSWORD = os.environ["SCODOC_PASSWD"]
|
||||||
|
print(f"SCODOC_URL={SCODOC_URL}")
|
||||||
|
|
||||||
|
# ---
|
||||||
|
if not CHECK_CERTIFICATE:
|
||||||
|
urllib3.disable_warnings()
|
||||||
|
|
||||||
|
|
||||||
|
class ScoError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def GET(path: str, params=None, errmsg=None):
|
||||||
|
"""Get and returns as JSON"""
|
||||||
|
# ajoute auth
|
||||||
|
params["__ac_name"] = SCODOC_USER
|
||||||
|
params["__ac_password"] = SCODOC_PASSWORD
|
||||||
|
r = requests.get(DEPT_URL + "/" + path, params=params, verify=CHECK_CERTIFICATE)
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise ScoError(errmsg or "erreur !")
|
||||||
|
return r.json() # decode la reponse JSON
|
||||||
|
|
||||||
|
|
||||||
|
def POST(path: str, data: dict, errmsg=None):
|
||||||
|
"""Post"""
|
||||||
|
data["__ac_name"] = data.get("__ac_name", SCODOC_USER)
|
||||||
|
data["__ac_password"] = data.get("__ac_password", SCODOC_PASSWORD)
|
||||||
|
r = requests.post(DEPT_URL + "/" + path, data=data, verify=CHECK_CERTIFICATE)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
# ---
|
||||||
|
# pas besoin d'ouvrir une session, on y va directement:
|
||||||
|
|
||||||
|
# --- Recupere la liste de tous les semestres:
|
||||||
|
sems = GET("Notes/formsemestre_list", params={"format": "json"})
|
||||||
|
|
||||||
|
# sems est une liste de semestres (dictionnaires)
|
||||||
|
for sem in sems:
|
||||||
|
if sem["etat"]:
|
||||||
|
break
|
||||||
|
|
||||||
|
if sem["etat"] == "0":
|
||||||
|
raise ScoError("Aucun semestre non verrouillé !")
|
||||||
|
|
||||||
|
# Affiche le semestre trouvé:
|
||||||
|
pp(sem)
|
||||||
|
|
||||||
|
# Liste des étudiants dans le 1er semestre non verrouillé:
|
||||||
|
group_list = GET(
|
||||||
|
"groups_view",
|
||||||
|
params={
|
||||||
|
"formsemestre_id": sem["formsemestre_id"],
|
||||||
|
"with_codes": 1,
|
||||||
|
"format": "json",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if not group_list:
|
||||||
|
# config inadaptée pour les tests...
|
||||||
|
raise ScoError("aucun étudiant inscrit dans le semestre")
|
||||||
|
|
||||||
|
etud = group_list[0] # le premier étudiant inscrit ici
|
||||||
|
# test un POST
|
||||||
|
r = POST(
|
||||||
|
"Absences/AddBilletAbsence",
|
||||||
|
{
|
||||||
|
"begin": "2021-10-25",
|
||||||
|
"end": "2021-10-26",
|
||||||
|
"description": "test API scodoc7",
|
||||||
|
"etudid": etud["etudid"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.text.startswith('<?xml version="1.0" encoding="utf-8"?>')
|
||||||
|
assert "billet_id" in r.text
|
||||||
|
# Essai avec un compte invalide
|
||||||
|
r_invalid = POST(
|
||||||
|
"Absences/AddBilletAbsence",
|
||||||
|
{
|
||||||
|
"__ac_name": "xxx",
|
||||||
|
"begin": "2021-10-25",
|
||||||
|
"end": "2021-10-26",
|
||||||
|
"description": "test API scodoc7",
|
||||||
|
"etudid": etud["etudid"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert r_invalid.status_code == 403 # compte invalide => not authorized
|
||||||
|
|
||||||
|
# AddBilletAbsence en json
|
||||||
|
r = POST(
|
||||||
|
"Absences/AddBilletAbsence",
|
||||||
|
{
|
||||||
|
"begin": "2021-10-25",
|
||||||
|
"end": "2021-10-26",
|
||||||
|
"description": "test API scodoc7",
|
||||||
|
"etudid": etud["etudid"],
|
||||||
|
"xml_reply": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert isinstance(json.loads(r.text)[0]["billet_id"], int)
|
||||||
|
|
||||||
|
# Les fonctions ci-dessous ne fonctionnent plus en ScoDoc 9
|
||||||
|
# Voir https://scodoc.org/git/viennet/ScoDoc/issues/149
|
||||||
|
|
||||||
|
# # ---- Liste les modules et prend le premier
|
||||||
|
# mods = GET("/Notes/moduleimpl_list", params={"formsemestre_id": sem["formsemestre_id"]})
|
||||||
|
# print(f"{len(mods)} modules dans le semestre {sem['titre']}")
|
||||||
|
|
||||||
|
# mod = mods[0]
|
||||||
|
|
||||||
|
# # ---- Etudiants inscrits dans ce module
|
||||||
|
# inscrits = GET(
|
||||||
|
# "Notes/do_moduleimpl_inscription_list",
|
||||||
|
# params={"moduleimpl_id": mod["moduleimpl_id"]},
|
||||||
|
# )
|
||||||
|
# print(f"{len(inscrits)} inscrits dans ce module")
|
||||||
|
# # prend le premier inscrit, au hasard:
|
||||||
|
# etudid = inscrits[0]["etudid"]
|
||||||
|
|
||||||
|
# # ---- Création d'une evaluation le dernier jour du semestre
|
||||||
|
# jour = sem["date_fin"]
|
||||||
|
# evaluation_id = POST(
|
||||||
|
# "/Notes/do_evaluation_create",
|
||||||
|
# data={
|
||||||
|
# "moduleimpl_id": mod["moduleimpl_id"],
|
||||||
|
# "coefficient": 1,
|
||||||
|
# "jour": jour, # "5/9/2019",
|
||||||
|
# "heure_debut": "9h00",
|
||||||
|
# "heure_fin": "10h00",
|
||||||
|
# "note_max": 20, # notes sur 20
|
||||||
|
# "description": "essai",
|
||||||
|
# },
|
||||||
|
# errmsg="échec création évaluation",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# print(
|
||||||
|
# f"Evaluation créée dans le module {mod['moduleimpl_id']}, evaluation_id={evaluation_id}"
|
||||||
|
# )
|
||||||
|
# print(
|
||||||
|
# f"Pour vérifier, aller sur: {DEPT_URL}/Notes/moduleimpl_status?moduleimpl_id={mod['moduleimpl_id']}",
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # ---- Saisie d'une note
|
||||||
|
# junk = POST(
|
||||||
|
# "/Notes/save_note",
|
||||||
|
# data={
|
||||||
|
# "etudid": etudid,
|
||||||
|
# "evaluation_id": evaluation_id,
|
||||||
|
# "value": 16.66, # la note !
|
||||||
|
# "comment": "test API",
|
||||||
|
# },
|
||||||
|
# )
|
307
tests/unit/test_notes_modules.py
Normal file
307
tests/unit/test_notes_modules.py
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
"""Test calculs moyennes de modules
|
||||||
|
Vérif moyennes de modules des bulletins
|
||||||
|
et aussi moyennes modules et UE internes (via nt)
|
||||||
|
"""
|
||||||
|
|
||||||
|
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_cache
|
||||||
|
from app.scodoc import sco_moduleimpl
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
DEPT = TestConfig.DEPT_TEST
|
||||||
|
|
||||||
|
|
||||||
|
def check_nt(
|
||||||
|
etudid,
|
||||||
|
formsemestre_id,
|
||||||
|
ue_id,
|
||||||
|
moduleimpl_id,
|
||||||
|
expected_moy_ue=False,
|
||||||
|
expected_mod_moy=False,
|
||||||
|
expected_sum_coefs_ue=False,
|
||||||
|
):
|
||||||
|
"""Vérification bas niveau: vérif resultat avec l'API internet "nt"
|
||||||
|
(peut changer dans le futur, ne pas utiliser hors ScoDoc !)
|
||||||
|
ne vérifie que les valeurs expected non False
|
||||||
|
"""
|
||||||
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
|
mod_moy = nt.get_etud_mod_moy(moduleimpl_id, etudid)
|
||||||
|
if expected_moy_ue is not False:
|
||||||
|
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||||
|
assert expected_moy_ue == ue_status["moy"]
|
||||||
|
if expected_mod_moy is not False:
|
||||||
|
assert expected_mod_moy == mod_moy
|
||||||
|
if expected_sum_coefs_ue is not False:
|
||||||
|
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||||
|
assert expected_sum_coefs_ue == ue_status["sum_coefs"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_notes_modules(test_client):
|
||||||
|
"""Test calcul des moyennes de modules et d'UE
|
||||||
|
Création étudiant, formation, semestre, inscription etudiant,
|
||||||
|
création evaluation, saisie de notes.
|
||||||
|
Vérifie calcul moyenne avec absences (ABS), excuse (EXC), attente (ATT)
|
||||||
|
"""
|
||||||
|
app.set_sco_dept(DEPT)
|
||||||
|
|
||||||
|
G = sco_fake_gen.ScoFake(verbose=False)
|
||||||
|
etuds = [G.create_etud(code_nip=None) for i in range(2)] # 2 étudiants
|
||||||
|
|
||||||
|
f = G.create_formation(acronyme="")
|
||||||
|
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
|
||||||
|
ue_id = ue["ue_id"]
|
||||||
|
mat = G.create_matiere(ue_id=ue_id, titre="matière test")
|
||||||
|
coef_mod_1 = 1.5
|
||||||
|
mod = G.create_module(
|
||||||
|
matiere_id=mat["matiere_id"],
|
||||||
|
code="TSM1",
|
||||||
|
coefficient=coef_mod_1,
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
formsemestre_id = sem["formsemestre_id"]
|
||||||
|
mi = G.create_moduleimpl(
|
||||||
|
module_id=mod["module_id"],
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
|
moduleimpl_id = mi["moduleimpl_id"]
|
||||||
|
# --- Inscription des étudiants
|
||||||
|
for etud in etuds:
|
||||||
|
G.inscrit_etudiant(sem, etud)
|
||||||
|
etud = etuds[0]
|
||||||
|
etudid = etud["etudid"]
|
||||||
|
# --- Creation évaluations: e1, e2
|
||||||
|
coef_1 = 1.0
|
||||||
|
coef_2 = 2.0
|
||||||
|
e1 = G.create_evaluation(
|
||||||
|
moduleimpl_id=moduleimpl_id,
|
||||||
|
jour="01/01/2020",
|
||||||
|
description="evaluation 1",
|
||||||
|
coefficient=coef_1,
|
||||||
|
)
|
||||||
|
e2 = G.create_evaluation(
|
||||||
|
moduleimpl_id=moduleimpl_id,
|
||||||
|
jour="01/01/2020",
|
||||||
|
description="evaluation 2",
|
||||||
|
coefficient=coef_2,
|
||||||
|
)
|
||||||
|
# --- Notes ordinaires
|
||||||
|
note_1 = 12.0
|
||||||
|
note_2 = 13.0
|
||||||
|
_, _, _ = G.create_note(evaluation=e1, etud=etuds[0], note=note_1)
|
||||||
|
_, _, _ = G.create_note(evaluation=e2, etud=etuds[0], note=note_2)
|
||||||
|
_, _, _ = G.create_note(evaluation=e1, etud=etuds[1], note=note_1 / 2)
|
||||||
|
_, _, _ = G.create_note(evaluation=e2, etud=etuds[1], note=note_2 / 3)
|
||||||
|
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:
|
||||||
|
note_th = (coef_1 * note_1 + coef_2 * note_2) / (coef_1 + coef_2)
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_th)
|
||||||
|
check_nt(
|
||||||
|
etudid,
|
||||||
|
formsemestre_id,
|
||||||
|
ue_id,
|
||||||
|
moduleimpl_id,
|
||||||
|
expected_mod_moy=note_th,
|
||||||
|
expected_moy_ue=note_th,
|
||||||
|
expected_sum_coefs_ue=coef_mod_1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Absence à une évaluation
|
||||||
|
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=None) # abs
|
||||||
|
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=note_2)
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
note_th = (coef_1 * 0.0 + coef_2 * note_2) / (coef_1 + coef_2)
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_th)
|
||||||
|
# Absences aux deux évaluations
|
||||||
|
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=None) # abs
|
||||||
|
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=None) # abs
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(0.0)
|
||||||
|
check_nt(
|
||||||
|
etudid,
|
||||||
|
formsemestre_id,
|
||||||
|
ue_id,
|
||||||
|
moduleimpl_id,
|
||||||
|
expected_mod_moy=0.0,
|
||||||
|
expected_moy_ue=0.0,
|
||||||
|
expected_sum_coefs_ue=0.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Note excusée EXC <-> scu.NOTES_NEUTRALISE
|
||||||
|
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=note_1)
|
||||||
|
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=scu.NOTES_NEUTRALISE) # EXC
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_1)
|
||||||
|
check_nt(
|
||||||
|
etudid,
|
||||||
|
formsemestre_id,
|
||||||
|
ue_id,
|
||||||
|
moduleimpl_id,
|
||||||
|
expected_mod_moy=note_1,
|
||||||
|
expected_moy_ue=note_1,
|
||||||
|
expected_sum_coefs_ue=coef_mod_1,
|
||||||
|
)
|
||||||
|
# Note en attente ATT <-> scu.NOTES_ATTENTE
|
||||||
|
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=note_1)
|
||||||
|
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=scu.NOTES_ATTENTE) # ATT
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == scu.fmt_note(note_1)
|
||||||
|
check_nt(
|
||||||
|
etudid,
|
||||||
|
formsemestre_id,
|
||||||
|
ue_id,
|
||||||
|
moduleimpl_id,
|
||||||
|
expected_mod_moy=note_1,
|
||||||
|
expected_moy_ue=note_1,
|
||||||
|
expected_sum_coefs_ue=coef_mod_1,
|
||||||
|
)
|
||||||
|
# Neutralisation (EXC) des 2 évals
|
||||||
|
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=scu.NOTES_NEUTRALISE) # EXC
|
||||||
|
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=scu.NOTES_NEUTRALISE) # EXC
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == "-"
|
||||||
|
check_nt(
|
||||||
|
etudid,
|
||||||
|
sem["formsemestre_id"],
|
||||||
|
ue["ue_id"],
|
||||||
|
mi["moduleimpl_id"],
|
||||||
|
expected_mod_moy="NA0",
|
||||||
|
expected_moy_ue=0.0,
|
||||||
|
expected_sum_coefs_ue=0.0,
|
||||||
|
)
|
||||||
|
# Attente (ATT) sur les 2 evals
|
||||||
|
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=scu.NOTES_ATTENTE) # ATT
|
||||||
|
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=scu.NOTES_ATTENTE) # ATT
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
assert b["ues"][0]["modules"][0]["mod_moy_txt"] == "-"
|
||||||
|
check_nt(
|
||||||
|
etudid,
|
||||||
|
sem["formsemestre_id"],
|
||||||
|
ue["ue_id"],
|
||||||
|
mi["moduleimpl_id"],
|
||||||
|
expected_mod_moy="NA0",
|
||||||
|
expected_moy_ue=0.0,
|
||||||
|
expected_sum_coefs_ue=0.0,
|
||||||
|
)
|
||||||
|
# Non inscrit
|
||||||
|
# - désinscrit notre étudiant:
|
||||||
|
inscr = sco_moduleimpl.do_moduleimpl_inscription_list(
|
||||||
|
moduleimpl_id=mi["moduleimpl_id"], etudid=etud["etudid"]
|
||||||
|
)
|
||||||
|
assert len(inscr) == 1
|
||||||
|
oid = inscr[0]["moduleimpl_inscription_id"]
|
||||||
|
sco_moduleimpl.do_moduleimpl_inscription_delete(
|
||||||
|
oid, formsemestre_id=mi["formsemestre_id"]
|
||||||
|
)
|
||||||
|
# -
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
assert b["ues"] == [] # inscrit à aucune UE !
|
||||||
|
check_nt(
|
||||||
|
etudid,
|
||||||
|
formsemestre_id,
|
||||||
|
ue_id,
|
||||||
|
moduleimpl_id,
|
||||||
|
expected_mod_moy="NI",
|
||||||
|
expected_moy_ue=0.0,
|
||||||
|
expected_sum_coefs_ue=0.0,
|
||||||
|
)
|
||||||
|
# --- Maintenant avec 2 modules dans l'UE
|
||||||
|
mod2 = G.create_module(
|
||||||
|
matiere_id=mat["matiere_id"],
|
||||||
|
code="TSM2",
|
||||||
|
coefficient=coef_mod_2,
|
||||||
|
titre="module test 2",
|
||||||
|
ue_id=ue_id,
|
||||||
|
formation_id=f["formation_id"],
|
||||||
|
)
|
||||||
|
mi2 = G.create_moduleimpl(
|
||||||
|
module_id=mod2["module_id"],
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
|
# Re-inscription au premier module de l'UE
|
||||||
|
sco_moduleimpl.do_moduleimpl_inscription_create(
|
||||||
|
{"etudid": etudid, "moduleimpl_id": mi["moduleimpl_id"]},
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
|
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=12.5)
|
||||||
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
|
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||||
|
assert ue_status["nb_missing"] == 1 # 1 même si etud non inscrit à l'autre module
|
||||||
|
assert ue_status["nb_notes"] == 1
|
||||||
|
assert not ue_status["was_capitalized"]
|
||||||
|
# Inscription au deuxième module de l'UE
|
||||||
|
sco_moduleimpl.do_moduleimpl_inscription_create(
|
||||||
|
{"etudid": etudid, "moduleimpl_id": mi2["moduleimpl_id"]},
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
)
|
||||||
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
|
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||||
|
assert ue_status["nb_missing"] == 1 # mi2 n'a pas encore de note
|
||||||
|
assert ue_status["nb_notes"] == 1
|
||||||
|
# Note dans module 2:
|
||||||
|
e_m2 = G.create_evaluation(
|
||||||
|
moduleimpl_id=mi2["moduleimpl_id"],
|
||||||
|
jour="01/01/2020",
|
||||||
|
description="evaluation mod 2",
|
||||||
|
coefficient=1.0,
|
||||||
|
)
|
||||||
|
_, _, _ = G.create_note(evaluation=e_m2, etud=etud, note=19.5)
|
||||||
|
nt = sco_cache.NotesTableCache.get(formsemestre_id)
|
||||||
|
ue_status = nt.get_etud_ue_status(etudid, ue_id)
|
||||||
|
assert ue_status["nb_missing"] == 0
|
||||||
|
assert ue_status["nb_notes"] == 2
|
||||||
|
|
||||||
|
# Moyenne d'UE si l'un des modules est EXC ("NA0")
|
||||||
|
# 2 modules, notes EXC dans le premier, note valide n dans le second
|
||||||
|
# la moyenne de l'UE doit être n
|
||||||
|
_, _, _ = G.create_note(evaluation=e1, etud=etud, note=scu.NOTES_NEUTRALISE) # EXC
|
||||||
|
_, _, _ = G.create_note(evaluation=e2, etud=etud, note=scu.NOTES_NEUTRALISE) # EXC
|
||||||
|
_, _, _ = G.create_note(evaluation=e_m2, etud=etud, note=12.5)
|
||||||
|
_, _, _ = G.create_note(evaluation=e1, etud=etuds[1], note=11.0)
|
||||||
|
_, _, _ = G.create_note(evaluation=e2, etud=etuds[1], note=11.0)
|
||||||
|
_, _, _ = G.create_note(evaluation=e_m2, etud=etuds[1], note=11.0)
|
||||||
|
b = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etud["etudid"]
|
||||||
|
)
|
||||||
|
assert b["ues"][0]["ue_status"]["cur_moy_ue"] == 12.5
|
||||||
|
assert b["ues"][0]["ue_status"]["moy"] == 12.5
|
||||||
|
b2 = sco_bulletins.formsemestre_bulletinetud_dict(
|
||||||
|
sem["formsemestre_id"], etuds[1]["etudid"]
|
||||||
|
)
|
||||||
|
assert b2["ues"][0]["ue_status"]["cur_moy_ue"] == 11.0
|
||||||
|
assert b2["ues"][0]["ue_status"]["moy"] == 11
|
@ -15,8 +15,8 @@ DEPT = TestConfig.DEPT_TEST
|
|||||||
|
|
||||||
def test_notes_rattrapage(test_client):
|
def test_notes_rattrapage(test_client):
|
||||||
"""Test quelques opérations élémentaires de ScoDoc
|
"""Test quelques opérations élémentaires de ScoDoc
|
||||||
Création 10 étudiants, formation, semestre, inscription etudiant,
|
Création 1 étudiant, formation, semestre, inscription etudiant,
|
||||||
creation 1 evaluation, saisie 10 notes.
|
creation 1 evaluation, saisie notes.
|
||||||
"""
|
"""
|
||||||
app.set_sco_dept(DEPT)
|
app.set_sco_dept(DEPT)
|
||||||
|
|
||||||
|
@ -6,4 +6,4 @@
|
|||||||
|
|
||||||
from tools.import_scodoc7_user_db import import_scodoc7_user_db
|
from tools.import_scodoc7_user_db import import_scodoc7_user_db
|
||||||
from tools.import_scodoc7_dept import import_scodoc7_dept
|
from tools.import_scodoc7_dept import import_scodoc7_dept
|
||||||
from tools.migrate_scodoc7_archives import migrate_scodoc7_dept_archive
|
from tools.migrate_scodoc7_archives import migrate_scodoc7_dept_archives
|
||||||
|
@ -233,7 +233,7 @@ do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# ----- Post-Migration: renomme archives en fonction des nouveaux ids
|
# ----- Post-Migration: renomme archives en fonction des nouveaux ids
|
||||||
su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask migrate-scodoc7-dept-archive)" "$SCODOC_USER" || die "Erreur de la post-migration des archives"
|
su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask migrate-scodoc7-dept-archives)" "$SCODOC_USER" || die "Erreur de la post-migration des archives"
|
||||||
|
|
||||||
|
|
||||||
# --- Si migration "en place", désactive ScoDoc 7
|
# --- Si migration "en place", désactive ScoDoc 7
|
||||||
|
@ -11,7 +11,7 @@ from app.models.formsemestre import FormSemestre
|
|||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
|
|
||||||
|
|
||||||
def migrate_scodoc7_dept_archive(dept_name=""):
|
def migrate_scodoc7_dept_archives(dept_name=""):
|
||||||
if dept_name:
|
if dept_name:
|
||||||
depts = Departement.query.filter_by(acronym=dept_name)
|
depts = Departement.query.filter_by(acronym=dept_name)
|
||||||
else:
|
else:
|
||||||
|
Loading…
Reference in New Issue
Block a user