Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into refactor_nt

This commit is contained in:
Emmanuel Viennet 2022-01-11 23:40:30 +01:00
commit 158ac7b1fc
16 changed files with 159 additions and 62 deletions

View File

@ -43,8 +43,8 @@ class Identite(db.Model):
boursier = db.Column(db.Boolean()) # True si boursier ('O' en ScoDoc7) boursier = db.Column(db.Boolean()) # True si boursier ('O' en ScoDoc7)
photo_filename = db.Column(db.Text()) photo_filename = db.Column(db.Text())
# Codes INE et NIP pas unique car le meme etud peut etre ds plusieurs dept # Codes INE et NIP pas unique car le meme etud peut etre ds plusieurs dept
code_nip = db.Column(db.Text()) code_nip = db.Column(db.Text(), index=True)
code_ine = db.Column(db.Text()) code_ine = db.Column(db.Text(), index=True)
# 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_archives # 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)

View File

@ -9,6 +9,12 @@
v 1.3 (python3) v 1.3 (python3)
""" """
import html import html
import re
# re validant dd/mm/yyyy
DMY_REGEXP = re.compile(
r"^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$"
)
def TrivialFormulator( def TrivialFormulator(
@ -66,8 +72,8 @@ def TrivialFormulator(
HTML elements: HTML elements:
input_type : 'text', 'textarea', 'password', input_type : 'text', 'textarea', 'password',
'radio', 'menu', 'checkbox', 'radio', 'menu', 'checkbox',
'hidden', 'separator', 'file', 'date', 'boolcheckbox', 'hidden', 'separator', 'file', 'date', 'datedmy' (avec validation),
'text_suggest' 'boolcheckbox', 'text_suggest'
(default text) (default text)
size : text field width size : text field width
rows, cols: textarea geometry rows, cols: textarea geometry
@ -243,6 +249,8 @@ class TF(object):
"Le champ '%s' doit être renseigné" % descr.get("title", field) "Le champ '%s' doit être renseigné" % descr.get("title", field)
) )
ok = 0 ok = 0
elif val == "" or val == None:
continue # allowed empty field, skip
# type # type
typ = descr.get("type", "string") typ = descr.get("type", "string")
if val != "" and val != None: if val != "" and val != None:
@ -300,6 +308,10 @@ class TF(object):
if not descr["validator"](val, field): if not descr["validator"](val, field):
msg.append("valeur invalide (%s) pour le champ '%s'" % (val, field)) msg.append("valeur invalide (%s) pour le champ '%s'" % (val, field))
ok = 0 ok = 0
elif descr.get("input_type") == "datedmy":
if not DMY_REGEXP.match(val):
msg.append("valeur invalide (%s) pour la date '%s'" % (val, field))
ok = 0
# boolean checkbox # boolean checkbox
if descr.get("input_type", None) == "boolcheckbox": if descr.get("input_type", None) == "boolcheckbox":
if int(val): if int(val):
@ -564,7 +576,9 @@ class TF(object):
'<input type="file" name="%s" size="%s" value="%s" %s>' '<input type="file" name="%s" size="%s" value="%s" %s>'
% (field, size, values[field], attribs) % (field, size, values[field], attribs)
) )
elif input_type == "date": # JavaScript widget for date input elif (
input_type == "date" or input_type == "datedmy"
): # JavaScript widget for date input
lem.append( lem.append(
'<input type="text" name="%s" size="10" value="%s" class="datepicker">' '<input type="text" name="%s" size="10" value="%s" class="datepicker">'
% (field, values[field]) % (field, values[field])

View File

@ -35,13 +35,14 @@ from flask_login import current_user
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from sco_version import SCOVERSION
def sidebar_common(): def sidebar_common():
"partie commune à toutes les sidebar" "partie commune à toutes les sidebar"
home_link = url_for("scodoc.index", scodoc_dept=g.scodoc_dept) home_link = url_for("scodoc.index", scodoc_dept=g.scodoc_dept)
H = [ H = [
f"""<a class="scodoc_title" href="{home_link}">ScoDoc 9.1</a><br> f"""<a class="scodoc_title" href="{home_link}">ScoDoc {SCOVERSION}</a><br>
<a href="{home_link}" class="sidebar">Accueil</a> <br> <a href="{home_link}" class="sidebar">Accueil</a> <br>
<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",

View File

@ -170,7 +170,7 @@ def evaluation_create_form(
( (
"jour", "jour",
{ {
"input_type": "date", "input_type": "datedmy",
"title": "Date", "title": "Date",
"size": 12, "size": 12,
"explanation": "date de l'examen, devoir ou contrôle", "explanation": "date de l'examen, devoir ou contrôle",

View File

@ -96,6 +96,28 @@ class ScoNonEmptyFormationObject(ScoValueError):
super().__init__(msg=msg, dest_url=dest_url) super().__init__(msg=msg, dest_url=dest_url)
class ScoInvalidIdType(ScoValueError):
"""Pour les clients qui s'obstinnent à utiliser des bookmarks ou
historiques anciens avec des ID ScoDoc7"""
def __init__(self, msg=""):
import app.scodoc.sco_utils as scu
msg = f"""<h3>Adresse de page invalide</h3>
<p class="help">
Vous utilisez un lien invalide, qui correspond probablement
à une ancienne version du logiciel. <br>
Au besoin, mettre à jour vos marque-pages.
</p>
<p> Si le problème persiste, merci de contacter l'assistance
via la liste de diffusion <a href="{scu.SCO_USERS_LIST}">Notes</a>
ou le salon Discord.
</p>
<p>Message serveur: <tt>{msg}</tt></p>
"""
super().__init__(msg)
class ScoGenError(ScoException): class ScoGenError(ScoException):
"exception avec affichage d'une page explicative ad-hoc" "exception avec affichage d'une page explicative ad-hoc"

View File

@ -27,21 +27,22 @@
"""Operations de base sur les formsemestres """Operations de base sur les formsemestres
""" """
from app.scodoc.sco_exceptions import ScoValueError
import time
from operator import itemgetter from operator import itemgetter
import time
from flask import g, request from flask import g, request
import app import app
from app import log
from app.models import Departement from app.models import Departement
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import sco_formations from app.scodoc import sco_formations
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app import log
from app.scodoc.sco_codes_parcours import NO_SEMESTRE_ID from app.scodoc.sco_codes_parcours import NO_SEMESTRE_ID
from app.scodoc.sco_exceptions import ScoValueError, ScoInvalidIdType
from app.scodoc.sco_vdi import ApoEtapeVDI from app.scodoc.sco_vdi import ApoEtapeVDI
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -97,7 +98,7 @@ def get_formsemestre(formsemestre_id, raise_soft_exc=False):
if formsemestre_id in g.stored_get_formsemestre: if formsemestre_id in g.stored_get_formsemestre:
return g.stored_get_formsemestre[formsemestre_id] 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 ScoInvalidIdType("formsemestre_id must be an integer !")
sems = do_formsemestre_list(args={"formsemestre_id": formsemestre_id}) sems = do_formsemestre_list(args={"formsemestre_id": formsemestre_id})
if not sems: if not sems:
log("get_formsemestre: invalid formsemestre_id (%s)" % formsemestre_id) log("get_formsemestre: invalid formsemestre_id (%s)" % formsemestre_id)

View File

@ -253,7 +253,7 @@ def do_formsemestre_createwithmodules(edit=False):
"date_debut", "date_debut",
{ {
"title": "Date de début", # j/m/a "title": "Date de début", # j/m/a
"input_type": "date", "input_type": "datedmy",
"explanation": "j/m/a", "explanation": "j/m/a",
"size": 9, "size": 9,
"allow_null": False, "allow_null": False,
@ -263,7 +263,7 @@ def do_formsemestre_createwithmodules(edit=False):
"date_fin", "date_fin",
{ {
"title": "Date de fin", # j/m/a "title": "Date de fin", # j/m/a
"input_type": "date", "input_type": "datedmy",
"explanation": "j/m/a", "explanation": "j/m/a",
"size": 9, "size": 9,
"allow_null": False, "allow_null": False,
@ -912,7 +912,7 @@ def formsemestre_clone(formsemestre_id):
"date_debut", "date_debut",
{ {
"title": "Date de début", # j/m/a "title": "Date de début", # j/m/a
"input_type": "date", "input_type": "datedmy",
"explanation": "j/m/a", "explanation": "j/m/a",
"size": 9, "size": 9,
"allow_null": False, "allow_null": False,
@ -922,7 +922,7 @@ def formsemestre_clone(formsemestre_id):
"date_fin", "date_fin",
{ {
"title": "Date de fin", # j/m/a "title": "Date de fin", # j/m/a
"input_type": "date", "input_type": "datedmy",
"explanation": "j/m/a", "explanation": "j/m/a",
"size": 9, "size": 9,
"allow_null": False, "allow_null": False,

View File

@ -154,7 +154,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
"date_debut", "date_debut",
{ {
"title": "Date de début", # j/m/a "title": "Date de début", # j/m/a
"input_type": "date", "input_type": "datedmy",
"explanation": "j/m/a (peut être approximatif)", "explanation": "j/m/a (peut être approximatif)",
"size": 9, "size": 9,
"allow_null": False, "allow_null": False,
@ -164,7 +164,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id):
"date_fin", "date_fin",
{ {
"title": "Date de fin", # j/m/a "title": "Date de fin", # j/m/a
"input_type": "date", "input_type": "datedmy",
"explanation": "j/m/a (peut être approximatif)", "explanation": "j/m/a (peut être approximatif)",
"size": 9, "size": 9,
"allow_null": False, "allow_null": False,

View File

@ -284,7 +284,9 @@ def get_group_infos(group_id, etat=None): # was _getlisteetud
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
group = get_group(group_id) group = get_group(group_id)
sem = sco_formsemestre.get_formsemestre(group["formsemestre_id"]) sem = sco_formsemestre.get_formsemestre(
group["formsemestre_id"], raise_soft_exc=True
)
members = get_group_members(group_id, etat=etat) members = get_group_members(group_id, etat=etat)
# add human readable description of state: # add human readable description of state:

View File

@ -36,6 +36,7 @@ from app.auth.models import User
from app.models import ModuleImpl from app.models import ModuleImpl
from app.models.evaluations import Evaluation from app.models.evaluations import Evaluation
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.sco_exceptions import ScoInvalidIdType
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
@ -184,6 +185,8 @@ def _ue_coefs_html(coefs_lst) -> str:
def moduleimpl_status(moduleimpl_id=None, partition_id=None): def moduleimpl_status(moduleimpl_id=None, partition_id=None):
"""Tableau de bord module (liste des evaluations etc)""" """Tableau de bord module (liste des evaluations etc)"""
if not isinstance(moduleimpl_id, int):
raise ScoInvalidIdType("moduleimpl_id must be an integer !")
modimpl = ModuleImpl.query.get_or_404(moduleimpl_id) modimpl = ModuleImpl.query.get_or_404(moduleimpl_id)
M = modimpl.to_dict() M = modimpl.to_dict()
formsemestre_id = M["formsemestre_id"] formsemestre_id = M["formsemestre_id"]

View File

@ -168,7 +168,7 @@ def can_change_groups(formsemestre_id):
"Vrai si l'utilisateur peut changer les groupes dans ce semestre" "Vrai si l'utilisateur peut changer les groupes dans ce semestre"
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id, raise_soft_exc=True)
if not sem["etat"]: if not sem["etat"]:
return False # semestre verrouillé return False # semestre verrouillé
if current_user.has_permission(Permission.ScoEtudChangeGroups): if current_user.has_permission(Permission.ScoEtudChangeGroups):

View File

@ -16,6 +16,13 @@ class releveBUT extends HTMLElement {
const styles = document.createElement('link'); const styles = document.createElement('link');
styles.setAttribute('rel', 'stylesheet'); styles.setAttribute('rel', 'stylesheet');
styles.setAttribute('href', '/ScoDoc/static/css/releve-but.css'); styles.setAttribute('href', '/ScoDoc/static/css/releve-but.css');
/* variante "ScoDoc" ou "Passerelle" (ENT) ? */
if (location.href.split("/")[3] == "ScoDoc") { /* un peu osé... */
styles.setAttribute('href', '/ScoDoc/static/css/releve-but.css');
} else {
// Passerelle
styles.setAttribute('href', '/assets/styles/releve-but.css');
}
this.shadow.appendChild(styles); this.shadow.appendChild(styles);
} }
listeOnOff() { listeOnOff() {
@ -78,7 +85,7 @@ class releveBUT extends HTMLElement {
<div class=flex> <div class=flex>
<div class=infoSemestre></div> <div class=infoSemestre></div>
<div> <div>
<div class=decision>Validé !</div> <div class=decision></div>
<div class=dateInscription>Inscrit le </div> <div class=dateInscription>Inscrit le </div>
<em>Les moyennes servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.</em> <em>Les moyennes servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.</em>
</div> </div>
@ -203,7 +210,7 @@ class releveBUT extends HTMLElement {
}).join("") }).join("")
}*/ }*/
this.shadow.querySelector(".infoSemestre").innerHTML = output; this.shadow.querySelector(".infoSemestre").innerHTML = output;
this.shadow.querySelector(".decision").innerHTML = data.semestre.decision.code; /*this.shadow.querySelector(".decision").innerHTML = data.semestre.decision.code;*/
} }
/*******************************/ /*******************************/
@ -213,6 +220,7 @@ class releveBUT extends HTMLElement {
let output = ``; let output = ``;
Object.entries(data.ues).forEach(([ue, dataUE]) => { Object.entries(data.ues).forEach(([ue, dataUE]) => {
output += ` output += `
<div> <div>
<div class=ue> <div class=ue>
<h3> <h3>

View File

@ -1131,8 +1131,8 @@ def AddBilletAbsenceForm(etudid):
scu.get_request_args(), scu.get_request_args(),
( (
("etudid", {"input_type": "hidden"}), ("etudid", {"input_type": "hidden"}),
("begin", {"input_type": "date"}), ("begin", {"input_type": "datedmy"}),
("end", {"input_type": "date"}), ("end", {"input_type": "datedmy"}),
( (
"justified", "justified",
{"input_type": "boolcheckbox", "default": 0, "title": "Justifiée"}, {"input_type": "boolcheckbox", "default": 0, "title": "Justifiée"},

View File

@ -72,12 +72,7 @@ from app import log, send_scodoc_alarm
from app.scodoc import scolog from app.scodoc import scolog
from app.scodoc.scolog import logdb from app.scodoc.scolog import logdb
from app.scodoc.sco_exceptions import ( from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoInvalidIdType
ScoValueError,
ScoLockedFormError,
ScoGenError,
AccessDenied,
)
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.pe import pe_view from app.pe import pe_view
from app.scodoc import sco_abs from app.scodoc import sco_abs
@ -284,9 +279,12 @@ def formsemestre_bulletinetud(
force_publishing=False, force_publishing=False,
prefer_mail_perso=False, prefer_mail_perso=False,
code_nip=None, code_nip=None,
code_ine=None,
): ):
if not formsemestre_id: if not formsemestre_id:
flask.abort(404, "argument manquant: formsemestre_id") flask.abort(404, "argument manquant: formsemestre_id")
if not isinstance(formsemestre_id, int):
raise ScoInvalidIdType("formsemestre_id must be an integer !")
formsemestre = FormSemestre.query.get_or_404(formsemestre_id) formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
if formsemestre.formation.is_apc() and format != "oldjson": if formsemestre.formation.is_apc() and format != "oldjson":
if etudid: if etudid:
@ -295,6 +293,14 @@ def formsemestre_bulletinetud(
etud = models.Identite.query.filter_by( etud = models.Identite.query.filter_by(
code_nip=str(code_nip) code_nip=str(code_nip)
).first_or_404() ).first_or_404()
elif code_ine:
etud = models.Identite.query.filter_by(
code_ine=str(code_ine)
).first_or_404()
else:
raise ScoValueError(
"Paramètre manquant: spécifier code_nip ou etudid ou code_ine"
)
if format == "json": if format == "json":
r = bulletin_but.BulletinBUT(formsemestre) r = bulletin_but.BulletinBUT(formsemestre)
return jsonify(r.bulletin_etud(etud, formsemestre)) return jsonify(r.bulletin_etud(etud, formsemestre))
@ -312,8 +318,10 @@ def formsemestre_bulletinetud(
sco=ScoData(), sco=ScoData(),
) )
if not (etudid or code_nip): if not (etudid or code_nip or code_ine):
raise ScoValueError("Paramètre manquant: spécifier code_nip ou etudid") raise ScoValueError(
"Paramètre manquant: spécifier code_nip ou etudid ou code_ine"
)
if format == "oldjson": if format == "oldjson":
format = "json" format = "json"
return sco_bulletins.formsemestre_bulletinetud( return sco_bulletins.formsemestre_bulletinetud(
@ -744,6 +752,10 @@ def XMLgetFormsemestres(etape_apo=None, formsemestre_id=None):
DEPRECATED: use formsemestre_list() DEPRECATED: use formsemestre_list()
""" """
current_app.logger.debug("Warning: calling deprecated XMLgetFormsemestres") current_app.logger.debug("Warning: calling deprecated XMLgetFormsemestres")
if not formsemestre_id:
return flask.abort(404, "argument manquant: formsemestre_id")
if not isinstance(formsemestre_id, int):
return flask.abort(404, "formsemestre_id must be an integer !")
args = {} args = {}
if etape_apo: if etape_apo:
args["etape_apo"] = etape_apo args["etape_apo"] = etape_apo

View File

@ -424,7 +424,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
"date_expiration", "date_expiration",
{ {
"title": "Date d'expiration", # j/m/a "title": "Date d'expiration", # j/m/a
"input_type": "date", "input_type": "datedmy",
"explanation": "j/m/a, laisser vide si pas de limite", "explanation": "j/m/a, laisser vide si pas de limite",
"size": 9, "size": 9,
"allow_null": True, "allow_null": True,

View File

@ -0,0 +1,34 @@
"""index ine et nip
Revision ID: f40fbaf5831c
Revises: 91be8a06d423
Create Date: 2022-01-10 15:13:06.867903
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = "f40fbaf5831c"
down_revision = "91be8a06d423"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_index(
op.f("ix_identite_code_ine"), "identite", ["code_ine"], unique=False
)
op.create_index(
op.f("ix_identite_code_nip"), "identite", ["code_nip"], unique=False
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f("ix_identite_code_nip"), table_name="identite")
op.drop_index(op.f("ix_identite_code_ine"), table_name="identite")
# ### end Alembic commands ###