diff --git a/app/models/etudiants.py b/app/models/etudiants.py index f4a9318f1..35c3fc858 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -43,8 +43,8 @@ class Identite(db.Model): boursier = db.Column(db.Boolean()) # True si boursier ('O' en ScoDoc7) photo_filename = db.Column(db.Text()) # Codes INE et NIP pas unique car le meme etud peut etre ds plusieurs dept - code_nip = db.Column(db.Text()) - code_ine = db.Column(db.Text()) + code_nip = db.Column(db.Text(), index=True) + code_ine = db.Column(db.Text(), index=True) # Ancien id ScoDoc7 pour les migrations de bases anciennes # ne pas utiliser après migrate_scodoc7_dept_archives scodoc7_id = db.Column(db.Text(), nullable=True) diff --git a/app/scodoc/TrivialFormulator.py b/app/scodoc/TrivialFormulator.py index acbe8d8ca..722b7ec9a 100644 --- a/app/scodoc/TrivialFormulator.py +++ b/app/scodoc/TrivialFormulator.py @@ -9,6 +9,12 @@ v 1.3 (python3) """ 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( @@ -66,8 +72,8 @@ def TrivialFormulator( HTML elements: input_type : 'text', 'textarea', 'password', 'radio', 'menu', 'checkbox', - 'hidden', 'separator', 'file', 'date', 'boolcheckbox', - 'text_suggest' + 'hidden', 'separator', 'file', 'date', 'datedmy' (avec validation), + 'boolcheckbox', 'text_suggest' (default text) size : text field width rows, cols: textarea geometry @@ -243,6 +249,8 @@ class TF(object): "Le champ '%s' doit être renseigné" % descr.get("title", field) ) ok = 0 + elif val == "" or val == None: + continue # allowed empty field, skip # type typ = descr.get("type", "string") if val != "" and val != None: @@ -300,6 +308,10 @@ class TF(object): if not descr["validator"](val, field): msg.append("valeur invalide (%s) pour le champ '%s'" % (val, field)) 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 if descr.get("input_type", None) == "boolcheckbox": if int(val): @@ -564,7 +576,9 @@ class TF(object): '' % (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( '' % (field, values[field]) diff --git a/app/scodoc/html_sidebar.py b/app/scodoc/html_sidebar.py index b3bf18a36..53efbfc95 100644 --- a/app/scodoc/html_sidebar.py +++ b/app/scodoc/html_sidebar.py @@ -35,13 +35,14 @@ from flask_login import current_user import app.scodoc.sco_utils as scu from app.scodoc import sco_preferences from app.scodoc.sco_permissions import Permission +from sco_version import SCOVERSION def sidebar_common(): "partie commune à toutes les sidebar" home_link = url_for("scodoc.index", scodoc_dept=g.scodoc_dept) H = [ - f"""ScoDoc 9.1
+ f"""ScoDoc {SCOVERSION}
Accueil
Adresse de page invalide +

+ Vous utilisez un lien invalide, qui correspond probablement + à une ancienne version du logiciel.
+ Au besoin, mettre à jour vos marque-pages. +

+

Si le problème persiste, merci de contacter l'assistance + via la liste de diffusion Notes + ou le salon Discord. +

+

Message serveur: {msg}

+ """ + super().__init__(msg) + + class ScoGenError(ScoException): "exception avec affichage d'une page explicative ad-hoc" diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index 629da1456..f51afd791 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -27,21 +27,22 @@ """Operations de base sur les formsemestres """ -from app.scodoc.sco_exceptions import ScoValueError -import time from operator import itemgetter +import time from flask import g, request import app +from app import log from app.models import Departement + from app.scodoc import sco_codes_parcours from app.scodoc import sco_cache from app.scodoc import sco_formations from app.scodoc import sco_preferences 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_exceptions import ScoValueError, ScoInvalidIdType from app.scodoc.sco_vdi import ApoEtapeVDI import app.scodoc.notesdb as ndb 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: return g.stored_get_formsemestre[formsemestre_id] 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}) if not sems: log("get_formsemestre: invalid formsemestre_id (%s)" % formsemestre_id) diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index 59e6d924c..e6ac86e3a 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -253,7 +253,7 @@ def do_formsemestre_createwithmodules(edit=False): "date_debut", { "title": "Date de début", # j/m/a - "input_type": "date", + "input_type": "datedmy", "explanation": "j/m/a", "size": 9, "allow_null": False, @@ -263,7 +263,7 @@ def do_formsemestre_createwithmodules(edit=False): "date_fin", { "title": "Date de fin", # j/m/a - "input_type": "date", + "input_type": "datedmy", "explanation": "j/m/a", "size": 9, "allow_null": False, @@ -912,7 +912,7 @@ def formsemestre_clone(formsemestre_id): "date_debut", { "title": "Date de début", # j/m/a - "input_type": "date", + "input_type": "datedmy", "explanation": "j/m/a", "size": 9, "allow_null": False, @@ -922,7 +922,7 @@ def formsemestre_clone(formsemestre_id): "date_fin", { "title": "Date de fin", # j/m/a - "input_type": "date", + "input_type": "datedmy", "explanation": "j/m/a", "size": 9, "allow_null": False, diff --git a/app/scodoc/sco_formsemestre_exterieurs.py b/app/scodoc/sco_formsemestre_exterieurs.py index b3d601de7..7e4e3da89 100644 --- a/app/scodoc/sco_formsemestre_exterieurs.py +++ b/app/scodoc/sco_formsemestre_exterieurs.py @@ -154,7 +154,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id): "date_debut", { "title": "Date de début", # j/m/a - "input_type": "date", + "input_type": "datedmy", "explanation": "j/m/a (peut être approximatif)", "size": 9, "allow_null": False, @@ -164,7 +164,7 @@ def formsemestre_ext_create_form(etudid, formsemestre_id): "date_fin", { "title": "Date de fin", # j/m/a - "input_type": "date", + "input_type": "datedmy", "explanation": "j/m/a (peut être approximatif)", "size": 9, "allow_null": False, diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index 0ecc1a2af..8248491af 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -284,7 +284,9 @@ def get_group_infos(group_id, etat=None): # was _getlisteetud cnx = ndb.GetDBConnexion() 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) # add human readable description of state: diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py index 735ece8a0..904c74c9a 100644 --- a/app/scodoc/sco_moduleimpl_status.py +++ b/app/scodoc/sco_moduleimpl_status.py @@ -36,6 +36,7 @@ from app.auth.models import User from app.models import ModuleImpl from app.models.evaluations import Evaluation import app.scodoc.sco_utils as scu +from app.scodoc.sco_exceptions import ScoInvalidIdType from app.scodoc.sco_permissions import Permission 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): """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) M = modimpl.to_dict() formsemestre_id = M["formsemestre_id"] diff --git a/app/scodoc/sco_permissions_check.py b/app/scodoc/sco_permissions_check.py index 5c166dbe3..fe21b1672 100644 --- a/app/scodoc/sco_permissions_check.py +++ b/app/scodoc/sco_permissions_check.py @@ -168,7 +168,7 @@ def can_change_groups(formsemestre_id): "Vrai si l'utilisateur peut changer les groupes dans ce semestre" 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"]: return False # semestre verrouillé if current_user.has_permission(Permission.ScoEtudChangeGroups): diff --git a/app/static/js/releve-but.js b/app/static/js/releve-but.js index 5042031b3..521e97da7 100644 --- a/app/static/js/releve-but.js +++ b/app/static/js/releve-but.js @@ -1,42 +1,49 @@ /* Module par Seb. L. */ class releveBUT extends HTMLElement { - constructor(){ + constructor() { super(); - this.shadow = this.attachShadow({mode: 'open'}); + this.shadow = this.attachShadow({ mode: 'open' }); /* Config par defaut */ this.config = { showURL: true }; - + /* Template du module */ this.shadow.innerHTML = this.template(); - + /* Style du module */ const styles = document.createElement('link'); styles.setAttribute('rel', 'stylesheet'); styles.setAttribute('href', '/ScoDoc/static/css/releve-but.css'); - this.shadow.appendChild(styles); + /* 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); } listeOnOff() { this.parentElement.parentElement.classList.toggle("listeOff"); - this.parentElement.parentElement.querySelectorAll(".moduleOnOff").forEach(e=>{ + this.parentElement.parentElement.querySelectorAll(".moduleOnOff").forEach(e => { e.classList.remove("moduleOnOff") }) } - moduleOnOff(){ + moduleOnOff() { this.parentElement.classList.toggle("moduleOnOff"); } - goTo(){ + goTo() { let module = this.dataset.module; this.parentElement.parentElement.parentElement.parentElement.querySelector("#Module_" + module).scrollIntoView(); } - set setConfig(config){ + set setConfig(config) { this.config.showURL = config.showURL ?? this.config.showURL; } - set showData(data) { + set showData(data) { this.showInformations(data); this.showSemestre(data); this.showSynthese(data); @@ -46,7 +53,7 @@ class releveBUT extends HTMLElement { this.shadow.querySelectorAll(".CTA_Liste").forEach(e => { e.addEventListener("click", this.listeOnOff) - }) + }) this.shadow.querySelectorAll(".ue, .module").forEach(e => { e.addEventListener("click", this.moduleOnOff) }) @@ -57,7 +64,7 @@ class releveBUT extends HTMLElement { this.shadow.children[0].classList.add("ready"); } - template(){ + template() { return `
@@ -78,7 +85,7 @@ class releveBUT extends HTMLElement {
-
Validé !
+
Inscrit le
Les moyennes servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.
@@ -142,8 +149,8 @@ class releveBUT extends HTMLElement { this.shadow.querySelector(".studentPic").src = data.etudiant.photo_url || "default_Student.svg"; let output = ''; - - if(this.config.showURL){ + + if (this.config.showURL) { output += ``; } else { output += `
`; @@ -167,7 +174,7 @@ class releveBUT extends HTMLElement {
${data.formation.titre}
`; - if(this.config.showURL){ + if (this.config.showURL) { output += `
`; } else { output += `
`; @@ -190,20 +197,20 @@ class releveBUT extends HTMLElement {
Moy. promo. :
${data.semestre.notes.moy}
Min. promo. :
${data.semestre.notes.min}
`; - /*${data.semestre.groupes.map(groupe => { - return ` -
-
Groupe
${groupe.nom}
-
Rang :
${groupe.rang.value} / ${groupe.rang.total}
-
Max. groupe :
${groupe.notes.max}
-
Moy. groupe :
${groupe.notes.min}
-
Min. groupe :
${groupe.notes.min}
-
- `; - }).join("") - }*/ + /*${data.semestre.groupes.map(groupe => { + return ` +
+
Groupe
${groupe.nom}
+
Rang :
${groupe.rang.value} / ${groupe.rang.total}
+
Max. groupe :
${groupe.notes.max}
+
Moy. groupe :
${groupe.notes.min}
+
Min. groupe :
${groupe.notes.min}
+
+ `; + }).join("") + }*/ 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 = ``; Object.entries(data.ues).forEach(([ue, dataUE]) => { output += ` +

@@ -257,7 +265,7 @@ class releveBUT extends HTMLElement { }) return output; } - + /*******************************/ /* Evaluations */ /*******************************/ @@ -335,8 +343,8 @@ class releveBUT extends HTMLElement { /********************/ /* Fonctions d'aide */ /********************/ - URL(href, content){ - if(this.config.showURL){ + URL(href, content) { + if (this.config.showURL) { return `${content}`; } else { return content; diff --git a/app/views/absences.py b/app/views/absences.py index 209a1f183..a9ec8570b 100644 --- a/app/views/absences.py +++ b/app/views/absences.py @@ -1131,8 +1131,8 @@ def AddBilletAbsenceForm(etudid): scu.get_request_args(), ( ("etudid", {"input_type": "hidden"}), - ("begin", {"input_type": "date"}), - ("end", {"input_type": "date"}), + ("begin", {"input_type": "datedmy"}), + ("end", {"input_type": "datedmy"}), ( "justified", {"input_type": "boolcheckbox", "default": 0, "title": "Justifiée"}, diff --git a/app/views/notes.py b/app/views/notes.py index 395c52ce8..be32ead8b 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -72,12 +72,7 @@ from app import log, send_scodoc_alarm from app.scodoc import scolog from app.scodoc.scolog import logdb -from app.scodoc.sco_exceptions import ( - ScoValueError, - ScoLockedFormError, - ScoGenError, - AccessDenied, -) +from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoInvalidIdType from app.scodoc import html_sco_header from app.pe import pe_view from app.scodoc import sco_abs @@ -284,9 +279,12 @@ def formsemestre_bulletinetud( force_publishing=False, prefer_mail_perso=False, code_nip=None, + code_ine=None, ): if not 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) if formsemestre.formation.is_apc() and format != "oldjson": if etudid: @@ -295,6 +293,14 @@ def formsemestre_bulletinetud( etud = models.Identite.query.filter_by( code_nip=str(code_nip) ).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": r = bulletin_but.BulletinBUT(formsemestre) return jsonify(r.bulletin_etud(etud, formsemestre)) @@ -312,8 +318,10 @@ def formsemestre_bulletinetud( sco=ScoData(), ) - if not (etudid or code_nip): - raise ScoValueError("Paramètre manquant: spécifier code_nip ou etudid") + if not (etudid or code_nip or code_ine): + raise ScoValueError( + "Paramètre manquant: spécifier code_nip ou etudid ou code_ine" + ) if format == "oldjson": format = "json" return sco_bulletins.formsemestre_bulletinetud( @@ -744,6 +752,10 @@ def XMLgetFormsemestres(etape_apo=None, formsemestre_id=None): DEPRECATED: use formsemestre_list() """ 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 = {} if etape_apo: args["etape_apo"] = etape_apo diff --git a/app/views/users.py b/app/views/users.py index 608eb9ab7..af4a721ed 100644 --- a/app/views/users.py +++ b/app/views/users.py @@ -424,7 +424,7 @@ def create_user_form(user_name=None, edit=0, all_roles=1): "date_expiration", { "title": "Date d'expiration", # j/m/a - "input_type": "date", + "input_type": "datedmy", "explanation": "j/m/a, laisser vide si pas de limite", "size": 9, "allow_null": True, diff --git a/migrations/versions/f40fbaf5831c_index_ine_et_nip.py b/migrations/versions/f40fbaf5831c_index_ine_et_nip.py new file mode 100644 index 000000000..7ff0473d4 --- /dev/null +++ b/migrations/versions/f40fbaf5831c_index_ine_et_nip.py @@ -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 ###