resize select control for placement

This commit is contained in:
Jean-Marie Place 2021-09-30 16:52:23 +02:00
parent a447c6e5f9
commit 44db5028fd
19 changed files with 61 additions and 125 deletions

View File

@ -106,15 +106,13 @@ Ou avec couverture (`pip install pytest-cov`)
#### Utilisation des tests unitaires pour initialiser la base de dev #### Utilisation des tests unitaires pour initialiser la base de dev
On peut aussi utiliser les tests unitaires pour mettre la base On peut aussi utiliser les tests unitaires pour mettre la base
de données de développement dans un état connu, par exemple pour éviter de de données de développement dans un état connu, par exemple pour éviter de recréer à la main étudianst et semestres quand on développe.
recréer à la main étudianst et semestres quand on développe.
Il suffit de positionner une variable d'environnement indiquant la BD utilisée par les tests: Il suffit de positionner une variable d'environnement indiquant la BD utilisée par les tests:
export SCODOC_TEST_DATABASE_URI=postgresql:///SCODOC_DEV export SCODOC_TEST_DATABASE_URI=postgresql:///SCODOC_DEV
(si elle n'existe pas, voir plus loin pour la créer) puis de les lancer puis de les lancer normalement, par exemple:
normalement, par exemple:
pytest tests/unit/test_sco_basic.py pytest tests/unit/test_sco_basic.py
@ -135,8 +133,7 @@ On utilise SQLAlchemy avec Alembic et Flask-Migrate.
Ne pas oublier de commiter les migrations (`git add migrations` ...). Ne pas oublier de commiter les migrations (`git add migrations` ...).
Mémo pour développeurs: séquence re-création d'une base (vérifiez votre `.env` Mémo pour développeurs: séquence re-création d'une base:
ou variables d'environnement pour interroger la bonne base !).
dropdb SCODOC_DEV dropdb SCODOC_DEV
tools/create_database.sh SCODOC_DEV # créé base SQL tools/create_database.sh SCODOC_DEV # créé base SQL

View File

@ -259,19 +259,15 @@ def create_app(config_class=DevConfig):
) )
# ---- INITIALISATION SPECIFIQUES A SCODOC # ---- INITIALISATION SPECIFIQUES A SCODOC
from app.scodoc import sco_bulletins_generator from app.scodoc import sco_bulletins_generator
from app.scodoc.sco_bulletins_example import BulletinGeneratorExample
from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy from app.scodoc.sco_bulletins_legacy import BulletinGeneratorLegacy
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC from app.scodoc.sco_bulletins_ucac import BulletinGeneratorUCAC
# l'ordre est important, le premeir sera le "défaut" pour les nouveaux départements. sco_bulletins_generator.register_bulletin_class(BulletinGeneratorExample)
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy) sco_bulletins_generator.register_bulletin_class(BulletinGeneratorLegacy)
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorStandard)
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC) sco_bulletins_generator.register_bulletin_class(BulletinGeneratorUCAC)
if app.testing or app.debug:
from app.scodoc.sco_bulletins_example import BulletinGeneratorExample
sco_bulletins_generator.register_bulletin_class(BulletinGeneratorExample)
return app return app

View File

@ -177,9 +177,8 @@ class User(UserMixin, db.Model):
if "roles_string" in data: if "roles_string" in data:
self.user_roles = [] self.user_roles = []
for r_d in data["roles_string"].split(","): for r_d in data["roles_string"].split(","):
if r_d: role, dept = UserRole.role_dept_from_string(r_d)
role, dept = UserRole.role_dept_from_string(r_d) self.add_role(role, dept)
self.add_role(role, dept)
def get_token(self, expires_in=3600): def get_token(self, expires_in=3600):
now = datetime.utcnow() now = datetime.utcnow()
@ -330,7 +329,7 @@ class Role(db.Model):
"""Roles for ScoDoc""" """Roles for ScoDoc"""
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True) # TODO: , nullable=False)) name = db.Column(db.String(64), unique=True)
default = db.Column(db.Boolean, default=False, index=True) default = db.Column(db.Boolean, default=False, index=True)
permissions = db.Column(db.BigInteger) # 64 bits permissions = db.Column(db.BigInteger) # 64 bits
users = db.relationship("User", secondary="user_role", viewonly=True) users = db.relationship("User", secondary="user_role", viewonly=True)
@ -389,7 +388,7 @@ class UserRole(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id")) user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
role_id = db.Column(db.Integer, db.ForeignKey("role.id")) role_id = db.Column(db.Integer, db.ForeignKey("role.id"))
dept = db.Column(db.String(64)) # dept acronym ou NULL dept = db.Column(db.String(64)) # dept acronym
user = db.relationship( user = db.relationship(
User, backref=db.backref("user_roles", cascade="all, delete-orphan") User, backref=db.backref("user_roles", cascade="all, delete-orphan")
) )
@ -408,9 +407,6 @@ class UserRole(db.Model):
""" """
fields = role_dept.split("_", 1) # maxsplit=1, le dept peut contenir un "_" fields = role_dept.split("_", 1) # maxsplit=1, le dept peut contenir un "_"
if len(fields) != 2: if len(fields) != 2:
current_app.logger.warning(
f"role_dept_from_string: Invalid role_dept '{role_dept}'"
)
raise ScoValueError("Invalid role_dept") raise ScoValueError("Invalid role_dept")
role_name, dept = fields role_name, dept = fields
if dept == "": if dept == "":
@ -422,7 +418,7 @@ class UserRole(db.Model):
def get_super_admin(): def get_super_admin():
"""L'utilisateur admin (ou le premier, s'il y en a plusieurs). """L'utilisateur admin (où le premier, s'il y en a plusieurs).
Utilisé par les tests unitaires et le script de migration. Utilisé par les tests unitaires et le script de migration.
""" """
admin_role = Role.query.filter_by(name="SuperAdmin").first() admin_role = Role.query.filter_by(name="SuperAdmin").first()

View File

@ -167,23 +167,6 @@ def bonus_iutlh(notes_sport, coefs, infos=None):
return bonus return bonus
def bonus_nantes(notes_sport, coefs, infos=None):
"""IUT de Nantes (Septembre 2018)
Nous avons différents types de bonification
bonfication Sport / Culture / engagement citoyen
Nous ajoutons sur le bulletin une bonification de 0,2 pour chaque item
la bonification totale ne doit pas excéder les 0,5 point.
Sur le bulletin nous ne mettons pas une note sur 20 mais directement les bonifications.
Dans ScoDoc: on a déclaré une UE "sport&culture" dans laquelle on aura des modules
pour chaque activité (Sport, Associations, ...)
avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20, mais en fait ce sera la
valeur de la bonification: entrer 0,1/20 signifiera un bonus de 0,1 point la moyenne générale)
"""
bonus = min(0.5, sum([x for x in notes_sport])) # plafonnement à 0.5 points
return bonus
# Bonus sport IUT Tours # Bonus sport IUT Tours
def bonus_tours(notes_sport, coefs, infos=None): def bonus_tours(notes_sport, coefs, infos=None):
"""Calcul bonus sport & culture IUT Tours sur moyenne generale """Calcul bonus sport & culture IUT Tours sur moyenne generale

View File

@ -48,19 +48,9 @@ import sco_version
def report_debouche_date(start_year=None, format="html"): def report_debouche_date(start_year=None, format="html"):
"""Rapport (table) pour les débouchés des étudiants sortis """Rapport (table) pour les débouchés des étudiants sortis à partir de l'année indiquée."""
à partir de l'année indiquée.
"""
if not start_year: if not start_year:
return report_debouche_ask_date("Année de début de la recherche") return report_debouche_ask_date()
else:
try:
start_year = int(start_year)
except ValueError:
return report_debouche_ask_date(
"Année invalide. Année de début de la recherche"
)
if format == "xls": if format == "xls":
keep_numeric = True # pas de conversion des notes en strings keep_numeric = True # pas de conversion des notes en strings
else: else:
@ -106,9 +96,8 @@ def get_etudids_with_debouche(start_year):
FROM notes_formsemestre_inscription i, notes_formsemestre s, itemsuivi it FROM notes_formsemestre_inscription i, notes_formsemestre s, itemsuivi it
WHERE i.etudid = it.etudid WHERE i.etudid = it.etudid
AND i.formsemestre_id = s.id AND s.date_fin >= %(start_date)s AND i.formsemestre_id = s.id AND s.date_fin >= %(start_date)s
AND s.dept_id = %(dept_id)s
""", """,
{"start_date": start_date, "dept_id": g.scodoc_dept_id}, {"start_date": start_date},
) )
return [x["etudid"] for x in r] return [x["etudid"] for x in r]
@ -204,16 +193,15 @@ def table_debouche_etudids(etudids, keep_numeric=True):
return tab return tab
def report_debouche_ask_date(msg: str) -> str: def report_debouche_ask_date():
"""Formulaire demande date départ""" """Formulaire demande date départ"""
return f"""{html_sco_header.sco_header()} return (
<h2>Table des débouchés des étudiants</h2> html_sco_header.sco_header()
<form method="GET"> + """<form method="GET">
{msg} Date de départ de la recherche: <input type="text" name="start_year" value="" size=10/>
<input type="text" name="start_year" value="" size=10/> </form>"""
</form> + html_sco_header.sco_footer()
{html_sco_header.sco_footer()} )
"""
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------

View File

@ -568,9 +568,10 @@ def excel_bytes_to_list(bytes_content):
return _excel_to_list(filelike) return _excel_to_list(filelike)
except: except:
raise ScoValueError( raise ScoValueError(
"""Le fichier xlsx attendu n'est pas lisible !
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ..)
""" """
scolars_import_excel_file: un contenu xlsx semble corrompu!
peut-être avez vous fourni un fichier au mauvais format (txt, xls, ..)
"""
) )
@ -579,9 +580,10 @@ def excel_file_to_list(filename):
return _excel_to_list(filename) return _excel_to_list(filename)
except: except:
raise ScoValueError( raise ScoValueError(
"""Le fichier xlsx attendu n'est pas lisible ! """scolars_import_excel_file: un contenu xlsx
Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...) semble corrompu !
""" Peut-être avez-vous fourni un fichier au mauvais format (txt, xls, ...)
"""
) )

View File

@ -25,16 +25,16 @@
# #
############################################################################## ##############################################################################
""" Importation des étudiants à partir de fichiers CSV """ Importation des etudiants à partir de fichiers CSV
""" """
import collections import collections
import io
import os import os
import re import re
import time import time
from datetime import date from datetime import date
import flask
from flask import g, url_for from flask import g, url_for
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -252,7 +252,7 @@ def students_import_excel(
def scolars_import_excel_file( def scolars_import_excel_file(
datafile: io.BytesIO, datafile,
formsemestre_id=None, formsemestre_id=None,
check_homonyms=True, check_homonyms=True,
require_ine=False, require_ine=False,
@ -414,14 +414,16 @@ def scolars_import_excel_file(
if NbHomonyms: if NbHomonyms:
NbImportedHomonyms += 1 NbImportedHomonyms += 1
# Insert in DB tables # Insert in DB tables
formsemestre_id_etud = _import_one_student( formsemestre_to_invalidate.add(
cnx, _import_one_student(
formsemestre_id, cnx,
values, formsemestre_id,
GroupIdInferers, values,
annee_courante, GroupIdInferers,
created_etudids, annee_courante,
linenum, created_etudids,
linenum,
)
) )
# Verification proportion d'homonymes: si > 10%, abandonne # Verification proportion d'homonymes: si > 10%, abandonne
@ -520,7 +522,7 @@ def _import_one_student(
annee_courante, annee_courante,
created_etudids, created_etudids,
linenum, linenum,
) -> int: ):
""" """
Import d'un étudiant et inscription dans le semestre. Import d'un étudiant et inscription dans le semestre.
Return: id du semestre dans lequel il a été inscrit. Return: id du semestre dans lequel il a été inscrit.
@ -564,7 +566,7 @@ def _import_one_student(
) )
do_formsemestre_inscription_with_modules( do_formsemestre_inscription_with_modules(
int(args["formsemestre_id"]), args["formsemestre_id"],
etudid, etudid,
group_ids, group_ids,
etat="I", etat="I",

View File

@ -231,10 +231,8 @@ def import_users(users):
roles_list = [] roles_list = []
for role in u["roles"].split(","): for role in u["roles"].split(","):
try: try:
role = role.strip() _, _ = UserRole.role_dept_from_string(role.strip())
if role: roles_list.append(role.strip())
_, _ = UserRole.role_dept_from_string(role)
roles_list.append(role)
except ScoValueError as value_error: except ScoValueError as value_error:
user_ok = False user_ok = False
append_msg("role %s : %s" % (role, value_error)) append_msg("role %s : %s" % (role, value_error))

View File

@ -61,7 +61,7 @@ def do_evaluation_listenotes():
Affichage des notes d'une évaluation Affichage des notes d'une évaluation
args: evaluation_id ou moduleimpl_id args: evaluation_id ou moduleimpl_id
(si moduleimpl_id, affiche toutes les évaluations du module) (si moduleimpl_id, affiche toutes les évaluatons du module)
""" """
mode = None mode = None
vals = scu.get_request_args() vals = scu.get_request_args()
@ -69,7 +69,7 @@ def do_evaluation_listenotes():
evaluation_id = int(vals["evaluation_id"]) evaluation_id = int(vals["evaluation_id"])
mode = "eval" mode = "eval"
evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id}) evals = sco_evaluations.do_evaluation_list({"evaluation_id": evaluation_id})
if "moduleimpl_id" in vals and vals["moduleimpl_id"]: if "moduleimpl_id" in vals:
moduleimpl_id = int(vals["moduleimpl_id"]) moduleimpl_id = int(vals["moduleimpl_id"])
mode = "module" mode = "module"
evals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id}) evals = sco_evaluations.do_evaluation_list({"moduleimpl_id": moduleimpl_id})

View File

@ -916,7 +916,7 @@ def formsemestre_validate_ues(formsemestre_id, etudid, code_etat_sem, assiduite)
and ue_status["moy"] >= nt.parcours.NOTES_BARRE_VALID_UE and ue_status["moy"] >= nt.parcours.NOTES_BARRE_VALID_UE
): ):
code_ue = ADM code_ue = ADM
elif not isinstance(ue_status["moy"], float): elif isinstance(ue_status["moy"], float):
# aucune note (pas de moyenne) dans l'UE: ne la valide pas # aucune note (pas de moyenne) dans l'UE: ne la valide pas
code_ue = None code_ue = None
elif valid_semestre: elif valid_semestre:

View File

@ -88,10 +88,9 @@ def _get_group_info(evaluation_id):
groups_tree[partition][group_name] = group_id groups_tree[partition][group_name] = group_id
if partition != TOUS: if partition != TOUS:
has_groups = True has_groups = True
nb_groups = len(groups_tree)
else: else:
has_groups = False has_groups = False
nb_groups = 1 nb_groups = sum([len(groups_tree[p]) for p in groups_tree])
return groups_tree, has_groups, nb_groups return groups_tree, has_groups, nb_groups

View File

@ -3,7 +3,7 @@
{% macro render_field(field) %} {% macro render_field(field) %}
<tr> <tr>
<td class="wtf-field">{{ field.label }}</td> <td class="wtf-field">{{ field.label }}</td>
<td class="wtf-field">{{ field()|safe }} <td class="wtf-field">{{ field(**kwargs)|safe }}
{% if field.errors %} {% if field.errors %}
<ul class=errors> <ul class=errors>
{% for error in field.errors %} {% for error in field.errors %}
@ -27,7 +27,7 @@
{{ render_field(form.nb_rangs) }} {{ render_field(form.nb_rangs) }}
{{ render_field(form.etiquetage) }} {{ render_field(form.etiquetage) }}
{% if form.has_groups %} {% if form.has_groups %}
{{ render_field(form.groups) }} {{ render_field(form.groups, size=form.nb_groups) }}
<!-- Tentative de recréer le choix des groupes sous forme de cases à cocher // demande à créer des champs wtf dynamiquement <!-- Tentative de recréer le choix des groupes sous forme de cases à cocher // demande à créer des champs wtf dynamiquement
{% for partition in form.groups_tree %} {% for partition in form.groups_tree %}
<tr> <tr>

View File

@ -275,10 +275,6 @@ def formsemestre_bulletinetud(
force_publishing=False, force_publishing=False,
prefer_mail_perso=False, prefer_mail_perso=False,
): ):
if not etudid:
raise ScoValueError("Paramètre manquant: etudid est requis")
if not formsemestre_id:
raise ScoValueError("Paramètre manquant: formsemestre_id est requis")
return sco_bulletins.formsemestre_bulletinetud( return sco_bulletins.formsemestre_bulletinetud(
etudid=etudid, etudid=etudid,
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,

View File

@ -1589,7 +1589,6 @@ def etudident_delete(etudid, dialog_confirmed=False):
"admissions", "admissions",
"adresse", "adresse",
"absences", "absences",
"absences_notifications",
"billet_absence", "billet_absence",
] ]
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
@ -1757,7 +1756,6 @@ def check_group_apogee(group_id, etat=None, fix=False, fixmail=False):
@scodoc7func @scodoc7func
def form_students_import_excel(formsemestre_id=None): def form_students_import_excel(formsemestre_id=None):
"formulaire import xls" "formulaire import xls"
formsemestre_id = int(formsemestre_id) if formsemestre_id else None
if formsemestre_id: if formsemestre_id:
sem = sco_formsemestre.get_formsemestre(formsemestre_id) sem = sco_formsemestre.get_formsemestre(formsemestre_id)
dest_url = ( dest_url = (
@ -1890,7 +1888,7 @@ Les champs avec un astérisque (*) doivent être présents (nulls non autorisés
else: else:
return sco_import_etuds.students_import_excel( return sco_import_etuds.students_import_excel(
tf[2]["csvfile"], tf[2]["csvfile"],
formsemestre_id=int(formsemestre_id) if formsemestre_id else None, formsemestre_id=formsemestre_id,
check_homonyms=tf[2]["check_homonyms"], check_homonyms=tf[2]["check_homonyms"],
require_ine=tf[2]["require_ine"], require_ine=tf[2]["require_ine"],
) )

View File

@ -168,21 +168,18 @@ def create_user_form(user_name=None, edit=0, all_roles=1):
orig_roles = { # set des roles existants avant édition orig_roles = { # set des roles existants avant édition
UserRole.role_dept_from_string(role_dept) UserRole.role_dept_from_string(role_dept)
for role_dept in initvalues["roles"] for role_dept in initvalues["roles"]
if role_dept
} }
if not initvalues["active"]: if not initvalues["active"]:
editable_roles_set = set() # can't change roles of a disabled user editable_roles_set = set() # can't change roles of a disabled user
editable_roles_strings = { editable_roles_strings = {r.name + "_" + dept for (r, dept) in editable_roles_set}
r.name + "_" + (dept or "") for (r, dept) in editable_roles_set orig_roles_strings = {r.name + "_" + dept for (r, dept) in orig_roles}
}
orig_roles_strings = {r.name + "_" + (dept or "") for (r, dept) in orig_roles}
# add existing user roles # add existing user roles
displayed_roles = list(editable_roles_set.union(orig_roles)) displayed_roles = list(editable_roles_set.union(orig_roles))
displayed_roles.sort(key=lambda x: (x[1], x[0].name)) displayed_roles.sort(key=lambda x: (x[1], x[0].name))
displayed_roles_strings = [ displayed_roles_strings = [r.name + "_" + dept for (r, dept) in displayed_roles]
r.name + "_" + (dept or "") for (r, dept) in displayed_roles displayed_roles_labels = [
"{dept}: {r.name}".format(dept=dept, r=r) for (r, dept) in displayed_roles
] ]
displayed_roles_labels = [f"{dept}: {r.name}" for (r, dept) in displayed_roles]
disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer
for i in range(len(displayed_roles_strings)): for i in range(len(displayed_roles_strings)):
if displayed_roles_strings[i] not in editable_roles_strings: if displayed_roles_strings[i] not in editable_roles_strings:

View File

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

View File

@ -95,6 +95,7 @@ def _clear_users_db():
try: try:
db.session.query(UserRole).delete() db.session.query(UserRole).delete()
db.session.query(User).delete() db.session.query(User).delete()
db.session.query(User).delete()
db.session.commit() db.session.commit()
except: except:
db.session.rollback() db.session.rollback()

View File

@ -27,7 +27,6 @@ from app.scodoc import sco_codes_parcours
from app.scodoc import sco_evaluations from app.scodoc import sco_evaluations
from app.scodoc import sco_formsemestre_validation from app.scodoc import sco_formsemestre_validation
from app.scodoc import sco_parcours_dut from app.scodoc import sco_parcours_dut
from app.scodoc import sco_cache
from app.scodoc import sco_saisie_notes from app.scodoc import sco_saisie_notes
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -198,19 +197,3 @@ def run_sco_basic(verbose=False):
assert not sco_parcours_dut.formsemestre_has_decisions( assert not sco_parcours_dut.formsemestre_has_decisions(
sem["formsemestre_id"] sem["formsemestre_id"]
), "décisions non effacées" ), "décisions non effacées"
# --- Décision de jury et validations des ECTS d'UE
for etud in etuds[:5]: # les etudiants notés
sco_formsemestre_validation.formsemestre_validation_etud_manu(
sem["formsemestre_id"],
etud["etudid"],
code_etat=sco_codes_parcours.ADJ,
assidu=True,
redirect=False,
)
# Vérifie que toutes les UE des étudiants notés ont été acquises:
nt = sco_cache.NotesTableCache.get(sem["formsemestre_id"])
for etud in etuds[:5]:
dec_ues = nt.get_etud_decision_ues(etud["etudid"])
for ue_id in dec_ues:
assert dec_ues[ue_id]["code"] in {"ADM", "CMP"}

View File

@ -30,7 +30,7 @@ def import_scodoc7_user_db(scodoc7_db="dbname=SCOUSERS"):
# ensure that user_name will match VALID_LOGIN_EXP # ensure that user_name will match VALID_LOGIN_EXP
user_name = scu.purge_chars( user_name = scu.purge_chars(
user_name, user_name,
allowed_chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@\\-_.", allowed_chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@\\-_.",
) )
if user_name != u7["user_name"]: if user_name != u7["user_name"]:
msg = f"""Changing login '{u7["user_name"]}' to '{user_name}'""" msg = f"""Changing login '{u7["user_name"]}' to '{user_name}'"""