Améliore validation des dates et des ids

This commit is contained in:
Emmanuel Viennet 2022-01-10 12:00:02 +01:00
parent 00b6d19c0c
commit b2893a3371
11 changed files with 93 additions and 54 deletions

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

@ -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

@ -254,7 +254,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,
@ -264,7 +264,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,
@ -914,7 +914,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,
@ -924,7 +924,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

@ -1,8 +1,8 @@
/* Module par Seb. L. */ /* Module par Seb. L. */
class releveBUT extends HTMLElement { class releveBUT extends HTMLElement {
constructor(){ constructor() {
super(); super();
this.shadow = this.attachShadow({mode: 'open'}); this.shadow = this.attachShadow({ mode: 'open' });
/* Config par defaut */ /* Config par defaut */
this.config = { this.config = {
@ -20,19 +20,19 @@ class releveBUT extends HTMLElement {
} }
listeOnOff() { listeOnOff() {
this.parentElement.parentElement.classList.toggle("listeOff"); this.parentElement.parentElement.classList.toggle("listeOff");
this.parentElement.parentElement.querySelectorAll(".moduleOnOff").forEach(e=>{ this.parentElement.parentElement.querySelectorAll(".moduleOnOff").forEach(e => {
e.classList.remove("moduleOnOff") e.classList.remove("moduleOnOff")
}) })
} }
moduleOnOff(){ moduleOnOff() {
this.parentElement.classList.toggle("moduleOnOff"); this.parentElement.classList.toggle("moduleOnOff");
} }
goTo(){ goTo() {
let module = this.dataset.module; let module = this.dataset.module;
this.parentElement.parentElement.parentElement.parentElement.querySelector("#Module_" + module).scrollIntoView(); this.parentElement.parentElement.parentElement.parentElement.querySelector("#Module_" + module).scrollIntoView();
} }
set setConfig(config){ set setConfig(config) {
this.config.showURL = config.showURL ?? this.config.showURL; this.config.showURL = config.showURL ?? this.config.showURL;
} }
@ -57,7 +57,7 @@ class releveBUT extends HTMLElement {
this.shadow.children[0].classList.add("ready"); this.shadow.children[0].classList.add("ready");
} }
template(){ template() {
return ` return `
<div> <div>
<div class="wait"></div> <div class="wait"></div>
@ -143,7 +143,7 @@ class releveBUT extends HTMLElement {
let output = ''; let output = '';
if(this.config.showURL){ if (this.config.showURL) {
output += `<a href="${data.etudiant.fiche_url}" class=info_etudiant>`; output += `<a href="${data.etudiant.fiche_url}" class=info_etudiant>`;
} else { } else {
output += `<div class=info_etudiant>`; output += `<div class=info_etudiant>`;
@ -167,7 +167,7 @@ class releveBUT extends HTMLElement {
</div> </div>
<div>${data.formation.titre}</div> <div>${data.formation.titre}</div>
`; `;
if(this.config.showURL){ if (this.config.showURL) {
output += `</a>`; output += `</a>`;
} else { } else {
output += `</div>`; output += `</div>`;
@ -190,18 +190,18 @@ class releveBUT extends HTMLElement {
<div>Moy. promo. :</div><div>${data.semestre.notes.moy}</div> <div>Moy. promo. :</div><div>${data.semestre.notes.moy}</div>
<div>Min. promo. :</div><div>${data.semestre.notes.min}</div> <div>Min. promo. :</div><div>${data.semestre.notes.min}</div>
</div>`; </div>`;
/*${data.semestre.groupes.map(groupe => { /*${data.semestre.groupes.map(groupe => {
return ` return `
<div> <div>
<div class=enteteSemestre>Groupe</div><div class=enteteSemestre>${groupe.nom}</div> <div class=enteteSemestre>Groupe</div><div class=enteteSemestre>${groupe.nom}</div>
<div class=rang>Rang :</div><div class=rang>${groupe.rang.value} / ${groupe.rang.total}</div> <div class=rang>Rang :</div><div class=rang>${groupe.rang.value} / ${groupe.rang.total}</div>
<div>Max. groupe :</div><div>${groupe.notes.max}</div> <div>Max. groupe :</div><div>${groupe.notes.max}</div>
<div>Moy. groupe :</div><div>${groupe.notes.min}</div> <div>Moy. groupe :</div><div>${groupe.notes.min}</div>
<div>Min. groupe :</div><div>${groupe.notes.min}</div> <div>Min. groupe :</div><div>${groupe.notes.min}</div>
</div> </div>
`; `;
}).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 +213,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>
@ -335,8 +336,8 @@ class releveBUT extends HTMLElement {
/********************/ /********************/
/* Fonctions d'aide */ /* Fonctions d'aide */
/********************/ /********************/
URL(href, content){ URL(href, content) {
if(this.config.showURL){ if (this.config.showURL) {
return `<a href=${href}>${content}</a>`; return `<a href=${href}>${content}</a>`;
} else { } else {
return content; return content;

View File

@ -1151,8 +1151,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
@ -287,6 +282,8 @@ def formsemestre_bulletinetud(
): ):
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:
@ -744,6 +741,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

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.1.22" SCOVERSION = "9.1.23"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"