forked from ScoDoc/ScoDoc
Compare commits
11 Commits
9bb7a1add7
...
b38c79a100
Author | SHA1 | Date | |
---|---|---|---|
b38c79a100 | |||
2377918b54 | |||
532fb3e701 | |||
457a9ddf51 | |||
ea1a03a654 | |||
e41879a1e1 | |||
|
38ba20aef6 | ||
|
33c9a606b0 | ||
|
54906c1bde | ||
4d3cbf7e75 | |||
939371cff9 |
@ -18,6 +18,7 @@ from sqlalchemy import desc, func, or_
|
|||||||
from sqlalchemy.dialects.postgresql import VARCHAR
|
from sqlalchemy.dialects.postgresql import VARCHAR
|
||||||
|
|
||||||
import app
|
import app
|
||||||
|
from app import db
|
||||||
from app.api import api_bp as bp, api_web_bp
|
from app.api import api_bp as bp, api_web_bp
|
||||||
from app.api import tools
|
from app.api import tools
|
||||||
from app.but import bulletin_but_court
|
from app.but import bulletin_but_court
|
||||||
@ -28,10 +29,12 @@ from app.models import (
|
|||||||
FormSemestreInscription,
|
FormSemestreInscription,
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
Identite,
|
Identite,
|
||||||
|
ScolarNews,
|
||||||
)
|
)
|
||||||
from app.scodoc import sco_bulletins
|
from app.scodoc import sco_bulletins
|
||||||
from app.scodoc import sco_groups
|
from app.scodoc import sco_groups
|
||||||
from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud
|
from app.scodoc.sco_bulletins import do_formsemestre_bulletinetud
|
||||||
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_utils import json_error, suppress_accents
|
from app.scodoc.sco_utils import json_error, suppress_accents
|
||||||
|
|
||||||
@ -475,3 +478,48 @@ def etudiant_groups(formsemestre_id: int, etudid: int = None):
|
|||||||
data = sco_groups.get_etud_groups(etud.id, formsemestre.id)
|
data = sco_groups.get_etud_groups(etud.id, formsemestre.id)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/etudiant/create", methods=["POST"], defaults={"force": False})
|
||||||
|
@bp.route("/etudiant/create/force", methods=["POST"], defaults={"force": True})
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.EtudInscrit)
|
||||||
|
@as_json
|
||||||
|
def etudiant_create(force=False):
|
||||||
|
"""Création d'un nouvel étudiant
|
||||||
|
Si force, crée même si homonymie détectée.
|
||||||
|
L'étudiant créé n'est pas inscrit à un semestre.
|
||||||
|
Champs requis: nom, prenom (sauf si config sans prénom), dept (string:acronyme)
|
||||||
|
"""
|
||||||
|
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
|
dept = args.get("dept", None)
|
||||||
|
if not dept:
|
||||||
|
return scu.json_error(400, "dept requis")
|
||||||
|
dept_o = Departement.query.filter_by(acronym=dept).first()
|
||||||
|
if not dept_o:
|
||||||
|
return scu.json_error(400, "dept invalide")
|
||||||
|
app.set_sco_dept(dept)
|
||||||
|
args["dept_id"] = dept_o.id
|
||||||
|
# vérifie que le département de création est bien autorisé
|
||||||
|
if not current_user.has_permission(Permission.EtudInscrit, dept):
|
||||||
|
return json_error(403, "departement non autorisé")
|
||||||
|
nom = args.get("nom", None)
|
||||||
|
prenom = args.get("prenom", None)
|
||||||
|
ok, homonyms = sco_etud.check_nom_prenom_homonyms(nom=nom, prenom=prenom)
|
||||||
|
if not ok:
|
||||||
|
return scu.json_error(400, "nom ou prénom invalide")
|
||||||
|
if len(homonyms) > 0 and not force:
|
||||||
|
return scu.json_error(
|
||||||
|
400, f"{len(homonyms)} homonymes détectés. Vous pouvez utiliser /force."
|
||||||
|
)
|
||||||
|
etud = Identite.create_etud(**args)
|
||||||
|
# Poste une nouvelle dans le département concerné:
|
||||||
|
ScolarNews.add(
|
||||||
|
typ=ScolarNews.NEWS_INSCR,
|
||||||
|
text=f"Nouvel étudiant {etud.html_link_fiche()}",
|
||||||
|
url=etud.url_fiche(),
|
||||||
|
max_frequency=0,
|
||||||
|
dept_id=dept_o.id,
|
||||||
|
)
|
||||||
|
db.session.commit()
|
||||||
|
return etud.to_dict_short()
|
||||||
|
@ -303,15 +303,19 @@ def group_create(partition_id: int): # partition-group-create
|
|||||||
return json_error(403, "partition non editable")
|
return json_error(403, "partition non editable")
|
||||||
if not partition.formsemestre.can_change_groups():
|
if not partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(401, "opération non autorisée")
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
|
||||||
group_name = data.get("group_name")
|
|
||||||
if group_name is None:
|
|
||||||
return json_error(API_CLIENT_ERROR, "missing group name or invalid data format")
|
|
||||||
if not GroupDescr.check_name(partition, group_name):
|
|
||||||
return json_error(API_CLIENT_ERROR, "invalid group_name")
|
|
||||||
group_name = group_name.strip()
|
|
||||||
|
|
||||||
group = GroupDescr(group_name=group_name, partition_id=partition_id)
|
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
|
group_name = args.get("group_name")
|
||||||
|
if not isinstance(group_name, str):
|
||||||
|
return json_error(API_CLIENT_ERROR, "missing group name or invalid data format")
|
||||||
|
args["group_name"] = args["group_name"].strip()
|
||||||
|
if not GroupDescr.check_name(partition, args["group_name"]):
|
||||||
|
return json_error(API_CLIENT_ERROR, "invalid group_name")
|
||||||
|
args["partition_id"] = partition_id
|
||||||
|
try:
|
||||||
|
group = GroupDescr(**args)
|
||||||
|
except TypeError:
|
||||||
|
return json_error(API_CLIENT_ERROR, "invalid arguments")
|
||||||
db.session.add(group)
|
db.session.add(group)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
log(f"created group {group}")
|
log(f"created group {group}")
|
||||||
@ -369,16 +373,22 @@ def group_edit(group_id: int):
|
|||||||
return json_error(403, "partition non editable")
|
return json_error(403, "partition non editable")
|
||||||
if not group.partition.formsemestre.can_change_groups():
|
if not group.partition.formsemestre.can_change_groups():
|
||||||
return json_error(401, "opération non autorisée")
|
return json_error(401, "opération non autorisée")
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
|
||||||
group_name = data.get("group_name")
|
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
if group_name is not None:
|
if "group_name" in args:
|
||||||
group_name = group_name.strip()
|
if not isinstance(args["group_name"], str):
|
||||||
if not GroupDescr.check_name(group.partition, group_name, existing=True):
|
return json_error(API_CLIENT_ERROR, "invalid data format for group_name")
|
||||||
|
args["group_name"] = args["group_name"].strip() if args["group_name"] else ""
|
||||||
|
if not GroupDescr.check_name(
|
||||||
|
group.partition, args["group_name"], existing=True
|
||||||
|
):
|
||||||
return json_error(API_CLIENT_ERROR, "invalid group_name")
|
return json_error(API_CLIENT_ERROR, "invalid group_name")
|
||||||
group.group_name = group_name
|
|
||||||
|
group.from_dict(args)
|
||||||
db.session.add(group)
|
db.session.add(group)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
log(f"modified {group}")
|
log(f"modified {group}")
|
||||||
|
|
||||||
app.set_sco_dept(group.partition.formsemestre.departement.acronym)
|
app.set_sco_dept(group.partition.formsemestre.departement.acronym)
|
||||||
sco_cache.invalidate_formsemestre(group.partition.formsemestre_id)
|
sco_cache.invalidate_formsemestre(group.partition.formsemestre_id)
|
||||||
return group.to_dict(with_partition=True)
|
return group.to_dict(with_partition=True)
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"""
|
"""
|
||||||
ScoDoc 9 API : accès aux utilisateurs
|
ScoDoc 9 API : accès aux utilisateurs
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
@ -85,6 +85,20 @@ def users_info_query():
|
|||||||
return [user.to_dict() for user in query]
|
return [user.to_dict() for user in query]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_allowed_user_edit(args: dict) -> tuple[bool, str]:
|
||||||
|
"Vrai si on peut"
|
||||||
|
if "cas_id" in args and not current_user.has_permission(
|
||||||
|
Permission.UsersChangeCASId
|
||||||
|
):
|
||||||
|
return False, "non autorise a changer cas_id"
|
||||||
|
|
||||||
|
if not current_user.is_administrator():
|
||||||
|
for field in ("cas_allow_login", "cas_allow_scodoc_login"):
|
||||||
|
if field in args:
|
||||||
|
return False, f"non autorise a changer {field}"
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/user/create", methods=["POST"])
|
@bp.route("/user/create", methods=["POST"])
|
||||||
@api_web_bp.route("/user/create", methods=["POST"])
|
@api_web_bp.route("/user/create", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@ -95,21 +109,22 @@ def user_create():
|
|||||||
"""Création d'un utilisateur
|
"""Création d'un utilisateur
|
||||||
The request content type should be "application/json":
|
The request content type should be "application/json":
|
||||||
{
|
{
|
||||||
"user_name": str,
|
"active":bool (default True),
|
||||||
"dept": str or null,
|
"dept": str or null,
|
||||||
"nom": str,
|
"nom": str,
|
||||||
"prenom": str,
|
"prenom": str,
|
||||||
"active":bool (default True)
|
"user_name": str,
|
||||||
|
...
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
user_name = data.get("user_name")
|
user_name = args.get("user_name")
|
||||||
if not user_name:
|
if not user_name:
|
||||||
return json_error(404, "empty user_name")
|
return json_error(404, "empty user_name")
|
||||||
user = User.query.filter_by(user_name=user_name).first()
|
user = User.query.filter_by(user_name=user_name).first()
|
||||||
if user:
|
if user:
|
||||||
return json_error(404, f"user_create: user {user} already exists\n")
|
return json_error(404, f"user_create: user {user} already exists\n")
|
||||||
dept = data.get("dept")
|
dept = args.get("dept")
|
||||||
if dept == "@all":
|
if dept == "@all":
|
||||||
dept = None
|
dept = None
|
||||||
allowed_depts = current_user.get_depts_with_permission(Permission.UsersAdmin)
|
allowed_depts = current_user.get_depts_with_permission(Permission.UsersAdmin)
|
||||||
@ -119,10 +134,12 @@ def user_create():
|
|||||||
Departement.query.filter_by(acronym=dept).first() is None
|
Departement.query.filter_by(acronym=dept).first() is None
|
||||||
):
|
):
|
||||||
return json_error(404, "user_create: departement inexistant")
|
return json_error(404, "user_create: departement inexistant")
|
||||||
nom = data.get("nom")
|
args["dept"] = dept
|
||||||
prenom = data.get("prenom")
|
ok, msg = _is_allowed_user_edit(args)
|
||||||
active = scu.to_bool(data.get("active", True))
|
if not ok:
|
||||||
user = User(user_name=user_name, active=active, dept=dept, nom=nom, prenom=prenom)
|
return json_error(403, f"user_create: {msg}")
|
||||||
|
user = User(user_name=user_name)
|
||||||
|
user.from_dict(args, new_user=True)
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return user.to_dict()
|
return user.to_dict()
|
||||||
@ -142,13 +159,14 @@ def user_edit(uid: int):
|
|||||||
"nom": str,
|
"nom": str,
|
||||||
"prenom": str,
|
"prenom": str,
|
||||||
"active":bool
|
"active":bool
|
||||||
|
...
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
data = request.get_json(force=True) # may raise 400 Bad Request
|
args = request.get_json(force=True) # may raise 400 Bad Request
|
||||||
user: User = User.query.get_or_404(uid)
|
user: User = User.query.get_or_404(uid)
|
||||||
# L'utilisateur doit avoir le droit dans le département de départ et celui d'arrivée
|
# L'utilisateur doit avoir le droit dans le département de départ et celui d'arrivée
|
||||||
orig_dept = user.dept
|
orig_dept = user.dept
|
||||||
dest_dept = data.get("dept", False)
|
dest_dept = args.get("dept", False)
|
||||||
if dest_dept is not False:
|
if dest_dept is not False:
|
||||||
if dest_dept == "@all":
|
if dest_dept == "@all":
|
||||||
dest_dept = None
|
dest_dept = None
|
||||||
@ -164,10 +182,11 @@ def user_edit(uid: int):
|
|||||||
return json_error(404, "user_edit: departement inexistant")
|
return json_error(404, "user_edit: departement inexistant")
|
||||||
user.dept = dest_dept
|
user.dept = dest_dept
|
||||||
|
|
||||||
user.nom = data.get("nom", user.nom)
|
ok, msg = _is_allowed_user_edit(args)
|
||||||
user.prenom = data.get("prenom", user.prenom)
|
if not ok:
|
||||||
user.active = scu.to_bool(data.get("active", user.active))
|
return json_error(403, f"user_edit: {msg}")
|
||||||
|
|
||||||
|
user.from_dict(args)
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return user.to_dict()
|
return user.to_dict()
|
||||||
|
@ -12,7 +12,6 @@ from typing import Optional
|
|||||||
|
|
||||||
import cracklib # pylint: disable=import-error
|
import cracklib # pylint: disable=import-error
|
||||||
|
|
||||||
import flask
|
|
||||||
from flask import current_app, g
|
from flask import current_app, g
|
||||||
from flask_login import UserMixin, AnonymousUserMixin
|
from flask_login import UserMixin, AnonymousUserMixin
|
||||||
|
|
||||||
@ -21,14 +20,13 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
|||||||
import jwt
|
import jwt
|
||||||
|
|
||||||
from app import db, email, log, login
|
from app import db, email, log, login
|
||||||
from app.models import Departement
|
from app.models import Departement, ScoDocModel
|
||||||
from app.models import SHORT_STR_LEN, USERNAME_STR_LEN
|
from app.models import SHORT_STR_LEN, USERNAME_STR_LEN
|
||||||
from app.models.config import ScoDocSiteConfig
|
from app.models.config import ScoDocSiteConfig
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
|
from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_etud # a deplacer dans scu
|
|
||||||
|
|
||||||
VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\.]+$")
|
VALID_LOGIN_EXP = re.compile(r"^[a-zA-Z0-9@\\\-_\.]+$")
|
||||||
|
|
||||||
@ -53,13 +51,14 @@ def is_valid_password(cleartxt) -> bool:
|
|||||||
def invalid_user_name(user_name: str) -> bool:
|
def invalid_user_name(user_name: str) -> bool:
|
||||||
"Check that user_name (aka login) is invalid"
|
"Check that user_name (aka login) is invalid"
|
||||||
return (
|
return (
|
||||||
(len(user_name) < 2)
|
not user_name
|
||||||
|
or (len(user_name) < 2)
|
||||||
or (len(user_name) >= USERNAME_STR_LEN)
|
or (len(user_name) >= USERNAME_STR_LEN)
|
||||||
or not VALID_LOGIN_EXP.match(user_name)
|
or not VALID_LOGIN_EXP.match(user_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class User(UserMixin, db.Model):
|
class User(UserMixin, db.Model, ScoDocModel):
|
||||||
"""ScoDoc users, handled by Flask / SQLAlchemy"""
|
"""ScoDoc users, handled by Flask / SQLAlchemy"""
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
@ -116,12 +115,17 @@ class User(UserMixin, db.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
"user_name:str is mandatory"
|
||||||
self.roles = []
|
self.roles = []
|
||||||
self.user_roles = []
|
self.user_roles = []
|
||||||
# check login:
|
# check login:
|
||||||
if kwargs.get("user_name") and invalid_user_name(kwargs["user_name"]):
|
if not "user_name" in kwargs:
|
||||||
|
raise ValueError("missing user_name argument")
|
||||||
|
if invalid_user_name(kwargs["user_name"]):
|
||||||
raise ValueError(f"invalid user_name: {kwargs['user_name']}")
|
raise ValueError(f"invalid user_name: {kwargs['user_name']}")
|
||||||
super(User, self).__init__(**kwargs)
|
kwargs["nom"] = kwargs.get("nom", "") or ""
|
||||||
|
kwargs["prenom"] = kwargs.get("prenom", "") or ""
|
||||||
|
super().__init__(**kwargs)
|
||||||
# Ajoute roles:
|
# Ajoute roles:
|
||||||
if (
|
if (
|
||||||
not self.roles
|
not self.roles
|
||||||
@ -251,12 +255,13 @@ class User(UserMixin, db.Model):
|
|||||||
"cas_last_login": self.cas_last_login.isoformat() + "Z"
|
"cas_last_login": self.cas_last_login.isoformat() + "Z"
|
||||||
if self.cas_last_login
|
if self.cas_last_login
|
||||||
else None,
|
else None,
|
||||||
|
"edt_id": self.edt_id,
|
||||||
"status_txt": "actif" if self.active else "fermé",
|
"status_txt": "actif" if self.active else "fermé",
|
||||||
"last_seen": self.last_seen.isoformat() + "Z" if self.last_seen else None,
|
"last_seen": self.last_seen.isoformat() + "Z" if self.last_seen else None,
|
||||||
"nom": (self.nom or ""), # sco8
|
"nom": self.nom or "",
|
||||||
"prenom": (self.prenom or ""), # sco8
|
"prenom": self.prenom or "",
|
||||||
"roles_string": self.get_roles_string(), # eg "Ens_RT, Ens_Info"
|
"roles_string": self.get_roles_string(), # eg "Ens_RT, Ens_Info"
|
||||||
"user_name": self.user_name, # sco8
|
"user_name": self.user_name,
|
||||||
# Les champs calculés:
|
# Les champs calculés:
|
||||||
"nom_fmt": self.get_nom_fmt(),
|
"nom_fmt": self.get_nom_fmt(),
|
||||||
"prenom_fmt": self.get_prenom_fmt(),
|
"prenom_fmt": self.get_prenom_fmt(),
|
||||||
@ -270,37 +275,50 @@ class User(UserMixin, db.Model):
|
|||||||
data["email_institutionnel"] = self.email_institutionnel or ""
|
data["email_institutionnel"] = self.email_institutionnel or ""
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert_dict_fields(cls, args: dict) -> dict:
|
||||||
|
"""Convert fields in the given dict. No other side effect.
|
||||||
|
args: dict with args in application.
|
||||||
|
returns: dict to store in model's db.
|
||||||
|
Convert boolean values to bools.
|
||||||
|
"""
|
||||||
|
args_dict = args
|
||||||
|
# Dates
|
||||||
|
if "date_expiration" in args:
|
||||||
|
date_expiration = args.get("date_expiration")
|
||||||
|
if isinstance(date_expiration, str):
|
||||||
|
args["date_expiration"] = (
|
||||||
|
datetime.datetime.fromisoformat(date_expiration)
|
||||||
|
if date_expiration
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
# booléens:
|
||||||
|
for field in ("active", "cas_allow_login", "cas_allow_scodoc_login"):
|
||||||
|
if field in args:
|
||||||
|
args_dict[field] = scu.to_bool(args.get(field))
|
||||||
|
|
||||||
|
# chaines ne devant pas être NULLs
|
||||||
|
for field in ("nom", "prenom"):
|
||||||
|
if field in args:
|
||||||
|
args[field] = args[field] or ""
|
||||||
|
|
||||||
|
return args_dict
|
||||||
|
|
||||||
def from_dict(self, data: dict, new_user=False):
|
def from_dict(self, data: dict, new_user=False):
|
||||||
"""Set users' attributes from given dict values.
|
"""Set users' attributes from given dict values.
|
||||||
Roles must be encoded as "roles_string", like "Ens_RT, Secr_CJ"
|
- roles_string : roles, encoded like "Ens_RT, Secr_CJ"
|
||||||
|
- date_expiration is a dateime object.
|
||||||
|
Does not check permissions here.
|
||||||
"""
|
"""
|
||||||
for field in [
|
|
||||||
"nom",
|
|
||||||
"prenom",
|
|
||||||
"dept",
|
|
||||||
"active",
|
|
||||||
"email",
|
|
||||||
"email_institutionnel",
|
|
||||||
"date_expiration",
|
|
||||||
"cas_id",
|
|
||||||
]:
|
|
||||||
if field in data:
|
|
||||||
setattr(self, field, data[field] or None)
|
|
||||||
# required boolean fields
|
|
||||||
for field in [
|
|
||||||
"cas_allow_login",
|
|
||||||
"cas_allow_scodoc_login",
|
|
||||||
]:
|
|
||||||
setattr(self, field, scu.to_bool(data.get(field, False)))
|
|
||||||
|
|
||||||
if new_user:
|
if new_user:
|
||||||
if "user_name" in data:
|
if "user_name" in data:
|
||||||
# never change name of existing users
|
# never change name of existing users
|
||||||
|
if invalid_user_name(data["user_name"]):
|
||||||
|
raise ValueError(f"invalid user_name: {data['user_name']}")
|
||||||
self.user_name = data["user_name"]
|
self.user_name = data["user_name"]
|
||||||
if "password" in data:
|
if "password" in data:
|
||||||
self.set_password(data["password"])
|
self.set_password(data["password"])
|
||||||
if invalid_user_name(self.user_name):
|
|
||||||
raise ValueError(f"invalid user_name: {self.user_name}")
|
|
||||||
# Roles: roles_string is "Ens_RT, Secr_RT, ..."
|
# Roles: roles_string is "Ens_RT, Secr_RT, ..."
|
||||||
if "roles_string" in data:
|
if "roles_string" in data:
|
||||||
self.user_roles = []
|
self.user_roles = []
|
||||||
@ -309,6 +327,8 @@ class User(UserMixin, db.Model):
|
|||||||
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)
|
||||||
|
|
||||||
|
super().from_dict(data, excluded={"user_name", "roles_string", "roles"})
|
||||||
|
|
||||||
# Set cas_id using regexp if configured:
|
# Set cas_id using regexp if configured:
|
||||||
exp = ScoDocSiteConfig.get("cas_uid_from_mail_regexp")
|
exp = ScoDocSiteConfig.get("cas_uid_from_mail_regexp")
|
||||||
if exp and self.email_institutionnel:
|
if exp and self.email_institutionnel:
|
||||||
@ -441,8 +461,8 @@ class User(UserMixin, db.Model):
|
|||||||
"""nomplogin est le nom en majuscules suivi du prénom et du login
|
"""nomplogin est le nom en majuscules suivi du prénom et du login
|
||||||
e.g. Dupont Pierre (dupont)
|
e.g. Dupont Pierre (dupont)
|
||||||
"""
|
"""
|
||||||
nom = sco_etud.format_nom(self.nom) if self.nom else self.user_name.upper()
|
nom = scu.format_nom(self.nom) if self.nom else self.user_name.upper()
|
||||||
return f"{nom} {sco_etud.format_prenom(self.prenom)} ({self.user_name})"
|
return f"{nom} {scu.format_prenom(self.prenom)} ({self.user_name})"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_user_id_from_nomplogin(nomplogin: str) -> Optional[int]:
|
def get_user_id_from_nomplogin(nomplogin: str) -> Optional[int]:
|
||||||
@ -460,29 +480,29 @@ class User(UserMixin, db.Model):
|
|||||||
def get_nom_fmt(self):
|
def get_nom_fmt(self):
|
||||||
"""Nom formaté: "Martin" """
|
"""Nom formaté: "Martin" """
|
||||||
if self.nom:
|
if self.nom:
|
||||||
return sco_etud.format_nom(self.nom, uppercase=False)
|
return scu.format_nom(self.nom, uppercase=False)
|
||||||
else:
|
else:
|
||||||
return self.user_name
|
return self.user_name
|
||||||
|
|
||||||
def get_prenom_fmt(self):
|
def get_prenom_fmt(self):
|
||||||
"""Prénom formaté (minuscule capitalisées)"""
|
"""Prénom formaté (minuscule capitalisées)"""
|
||||||
return sco_etud.format_prenom(self.prenom)
|
return scu.format_prenom(self.prenom)
|
||||||
|
|
||||||
def get_nomprenom(self):
|
def get_nomprenom(self):
|
||||||
"""Nom capitalisé suivi de l'initiale du prénom:
|
"""Nom capitalisé suivi de l'initiale du prénom:
|
||||||
Viennet E.
|
Viennet E.
|
||||||
"""
|
"""
|
||||||
prenom_abbrv = scu.abbrev_prenom(sco_etud.format_prenom(self.prenom))
|
prenom_abbrv = scu.abbrev_prenom(scu.format_prenom(self.prenom))
|
||||||
return (self.get_nom_fmt() + " " + prenom_abbrv).strip()
|
return (self.get_nom_fmt() + " " + prenom_abbrv).strip()
|
||||||
|
|
||||||
def get_prenomnom(self):
|
def get_prenomnom(self):
|
||||||
"""L'initiale du prénom suivie du nom: "J.-C. Dupont" """
|
"""L'initiale du prénom suivie du nom: "J.-C. Dupont" """
|
||||||
prenom_abbrv = scu.abbrev_prenom(sco_etud.format_prenom(self.prenom))
|
prenom_abbrv = scu.abbrev_prenom(scu.format_prenom(self.prenom))
|
||||||
return (prenom_abbrv + " " + self.get_nom_fmt()).strip()
|
return (prenom_abbrv + " " + self.get_nom_fmt()).strip()
|
||||||
|
|
||||||
def get_nomcomplet(self):
|
def get_nomcomplet(self):
|
||||||
"Prénom et nom complets"
|
"Prénom et nom complets"
|
||||||
return sco_etud.format_prenom(self.prenom) + " " + self.get_nom_fmt()
|
return scu.format_prenom(self.prenom) + " " + self.get_nom_fmt()
|
||||||
|
|
||||||
# nomnoacc était le nom en minuscules sans accents (inutile)
|
# nomnoacc était le nom en minuscules sans accents (inutile)
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ from flask import Blueprint
|
|||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.models import Departement
|
from app.models import Departement
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
bp = Blueprint("entreprises", __name__)
|
bp = Blueprint("entreprises", __name__)
|
||||||
|
|
||||||
@ -15,12 +16,12 @@ SIRET_PROVISOIRE_START = "xx"
|
|||||||
|
|
||||||
@bp.app_template_filter()
|
@bp.app_template_filter()
|
||||||
def format_prenom(s):
|
def format_prenom(s):
|
||||||
return sco_etud.format_prenom(s)
|
return scu.format_prenom(s)
|
||||||
|
|
||||||
|
|
||||||
@bp.app_template_filter()
|
@bp.app_template_filter()
|
||||||
def format_nom(s):
|
def format_nom(s):
|
||||||
return sco_etud.format_nom(s)
|
return scu.format_nom(s)
|
||||||
|
|
||||||
|
|
||||||
@bp.app_template_filter()
|
@bp.app_template_filter()
|
||||||
|
@ -1580,8 +1580,8 @@ def edit_stage_apprentissage(entreprise_id, stage_apprentissage_id):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif request.method == "GET":
|
elif request.method == "GET":
|
||||||
form.etudiant.data = f"""{sco_etud.format_nom(etudiant.nom)} {
|
form.etudiant.data = f"""{scu.format_nom(etudiant.nom)} {
|
||||||
sco_etud.format_prenom(etudiant.prenom)}"""
|
scu.format_prenom(etudiant.prenom)}"""
|
||||||
form.etudid.data = etudiant.id
|
form.etudid.data = etudiant.id
|
||||||
form.type_offre.data = stage_apprentissage.type_offre
|
form.type_offre.data = stage_apprentissage.type_offre
|
||||||
form.date_debut.data = stage_apprentissage.date_debut
|
form.date_debut.data = stage_apprentissage.date_debut
|
||||||
@ -1699,7 +1699,7 @@ def json_etudiants():
|
|||||||
list = []
|
list = []
|
||||||
for etudiant in etudiants:
|
for etudiant in etudiants:
|
||||||
content = {}
|
content = {}
|
||||||
value = f"{sco_etud.format_nom(etudiant.nom)} {sco_etud.format_prenom(etudiant.prenom)}"
|
value = f"{scu.format_nom(etudiant.nom)} {scu.format_prenom(etudiant.prenom)}"
|
||||||
if etudiant.inscription_courante() is not None:
|
if etudiant.inscription_courante() is not None:
|
||||||
content = {
|
content = {
|
||||||
"id": f"{etudiant.id}",
|
"id": f"{etudiant.id}",
|
||||||
|
@ -53,8 +53,9 @@ class ScoDocModel:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict:
|
def filter_model_attributes(cls, data: dict, excluded: set[str] = None) -> dict:
|
||||||
"""Returns a copy of dict with only the keys belonging to the Model and not in excluded.
|
"""Returns a copy of dict with only the keys belonging to the Model and not in excluded.
|
||||||
By default, excluded == { 'id' }"""
|
Add 'id' to excluded."""
|
||||||
excluded = {"id"} if excluded is None else set()
|
excluded = excluded or set()
|
||||||
|
excluded.add("id") # always exclude id
|
||||||
# Les attributs du modèle qui sont des variables: (élimine les __ et les alias comme adm_id)
|
# Les attributs du modèle qui sont des variables: (élimine les __ et les alias comme adm_id)
|
||||||
my_attributes = [
|
my_attributes = [
|
||||||
a
|
a
|
||||||
@ -70,7 +71,7 @@ class ScoDocModel:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert_dict_fields(cls, args: dict) -> dict:
|
def convert_dict_fields(cls, args: dict) -> dict:
|
||||||
"""Convert fields in the given dict. No side effect.
|
"""Convert fields from the given dict to model's attributes values. No side effect.
|
||||||
By default, do nothing, but is overloaded by some subclasses.
|
By default, do nothing, but is overloaded by some subclasses.
|
||||||
args: dict with args in application.
|
args: dict with args in application.
|
||||||
returns: dict to store in model's db.
|
returns: dict to store in model's db.
|
||||||
@ -78,9 +79,11 @@ class ScoDocModel:
|
|||||||
# virtual, by default, do nothing
|
# virtual, by default, do nothing
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def from_dict(self, args: dict):
|
def from_dict(self, args: dict, excluded: set[str] = None):
|
||||||
"Update object's fields given in dict. Add to session but don't commit."
|
"Update object's fields given in dict. Add to session but don't commit."
|
||||||
args_dict = self.convert_dict_fields(self.filter_model_attributes(args))
|
args_dict = self.convert_dict_fields(
|
||||||
|
self.filter_model_attributes(args, excluded=excluded)
|
||||||
|
)
|
||||||
for key, value in args_dict.items():
|
for key, value in args_dict.items():
|
||||||
if hasattr(self, key):
|
if hasattr(self, key):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
@ -130,7 +133,6 @@ from app.models.notes import (
|
|||||||
NotesNotesLog,
|
NotesNotesLog,
|
||||||
)
|
)
|
||||||
from app.models.validations import (
|
from app.models.validations import (
|
||||||
ScolarEvent,
|
|
||||||
ScolarFormSemestreValidation,
|
ScolarFormSemestreValidation,
|
||||||
ScolarAutorisationInscription,
|
ScolarAutorisationInscription,
|
||||||
)
|
)
|
||||||
@ -149,3 +151,4 @@ from app.models.but_validations import ApcValidationAnnee, ApcValidationRCUE
|
|||||||
from app.models.config import ScoDocSiteConfig
|
from app.models.config import ScoDocSiteConfig
|
||||||
|
|
||||||
from app.models.assiduites import Assiduite, Justificatif
|
from app.models.assiduites import Assiduite, Justificatif
|
||||||
|
from app.models.scolar_event import ScolarEvent
|
||||||
|
@ -15,7 +15,7 @@ from sqlalchemy import desc, text
|
|||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app import models
|
from app import models
|
||||||
|
from app.models.scolar_event import ScolarEvent
|
||||||
from app.scodoc import notesdb as ndb
|
from app.scodoc import notesdb as ndb
|
||||||
from app.scodoc.sco_bac import Baccalaureat
|
from app.scodoc.sco_bac import Baccalaureat
|
||||||
from app.scodoc.sco_exceptions import ScoInvalidParamError, ScoValueError
|
from app.scodoc.sco_exceptions import ScoInvalidParamError, ScoValueError
|
||||||
@ -170,9 +170,13 @@ class Identite(db.Model, models.ScoDocModel):
|
|||||||
|
|
||||||
def html_link_fiche(self) -> str:
|
def html_link_fiche(self) -> str:
|
||||||
"lien vers la fiche"
|
"lien vers la fiche"
|
||||||
return f"""<a class="stdlink" href="{
|
return f"""<a class="stdlink" href="{self.url_fiche()}">{self.nomprenom}</a>"""
|
||||||
url_for("scolar.ficheEtud", scodoc_dept=self.departement.acronym, etudid=self.id)
|
|
||||||
}">{self.nomprenom}</a>"""
|
def url_fiche(self) -> str:
|
||||||
|
"url de la fiche étudiant"
|
||||||
|
return url_for(
|
||||||
|
"scolar.ficheEtud", scodoc_dept=self.departement.acronym, etudid=self.id
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_request(cls, etudid=None, code_nip=None) -> "Identite":
|
def from_request(cls, etudid=None, code_nip=None) -> "Identite":
|
||||||
@ -211,6 +215,10 @@ class Identite(db.Model, models.ScoDocModel):
|
|||||||
etud.admission = Admission()
|
etud.admission = Admission()
|
||||||
etud.adresses.append(Adresse(typeadresse="domicile"))
|
etud.adresses.append(Adresse(typeadresse="domicile"))
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
|
event = ScolarEvent(etud=etud, event_type="CREATION")
|
||||||
|
db.session.add(event)
|
||||||
|
log(f"Identite.create {etud}")
|
||||||
return etud
|
return etud
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -133,7 +133,7 @@ class ScolarNews(db.Model):
|
|||||||
return query.order_by(cls.date.desc()).limit(n).all()
|
return query.order_by(cls.date.desc()).limit(n).all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add(cls, typ, obj=None, text="", url=None, max_frequency=600):
|
def add(cls, typ, obj=None, text="", url=None, max_frequency=600, dept_id=None):
|
||||||
"""Enregistre une nouvelle
|
"""Enregistre une nouvelle
|
||||||
Si max_frequency, ne génère pas 2 nouvelles "identiques"
|
Si max_frequency, ne génère pas 2 nouvelles "identiques"
|
||||||
à moins de max_frequency secondes d'intervalle (10 minutes par défaut).
|
à moins de max_frequency secondes d'intervalle (10 minutes par défaut).
|
||||||
@ -141,10 +141,11 @@ class ScolarNews(db.Model):
|
|||||||
même (obj, typ, user).
|
même (obj, typ, user).
|
||||||
La nouvelle enregistrée est aussi envoyée par mail.
|
La nouvelle enregistrée est aussi envoyée par mail.
|
||||||
"""
|
"""
|
||||||
|
dept_id = dept_id if dept_id is not None else g.scodoc_dept_id
|
||||||
if max_frequency:
|
if max_frequency:
|
||||||
last_news = (
|
last_news = (
|
||||||
cls.query.filter_by(
|
cls.query.filter_by(
|
||||||
dept_id=g.scodoc_dept_id,
|
dept_id=dept_id,
|
||||||
authenticated_user=current_user.user_name,
|
authenticated_user=current_user.user_name,
|
||||||
type=typ,
|
type=typ,
|
||||||
object=obj,
|
object=obj,
|
||||||
@ -163,7 +164,7 @@ class ScolarNews(db.Model):
|
|||||||
return
|
return
|
||||||
|
|
||||||
news = ScolarNews(
|
news = ScolarNews(
|
||||||
dept_id=g.scodoc_dept_id,
|
dept_id=dept_id,
|
||||||
authenticated_user=current_user.user_name,
|
authenticated_user=current_user.user_name,
|
||||||
type=typ,
|
type=typ,
|
||||||
object=obj,
|
object=obj,
|
||||||
|
@ -11,14 +11,14 @@ from operator import attrgetter
|
|||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.models import Scolog, GROUPNAME_STR_LEN, SHORT_STR_LEN
|
from app.models import ScoDocModel, Scolog, GROUPNAME_STR_LEN, SHORT_STR_LEN
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
|
||||||
|
|
||||||
|
|
||||||
class Partition(db.Model):
|
class Partition(db.Model, ScoDocModel):
|
||||||
"""Partition: découpage d'une promotion en groupes"""
|
"""Partition: découpage d'une promotion en groupes"""
|
||||||
|
|
||||||
__table_args__ = (db.UniqueConstraint("formsemestre_id", "partition_name"),)
|
__table_args__ = (db.UniqueConstraint("formsemestre_id", "partition_name"),)
|
||||||
@ -204,7 +204,7 @@ class Partition(db.Model):
|
|||||||
return group
|
return group
|
||||||
|
|
||||||
|
|
||||||
class GroupDescr(db.Model):
|
class GroupDescr(db.Model, ScoDocModel):
|
||||||
"""Description d'un groupe d'une partition"""
|
"""Description d'un groupe d'une partition"""
|
||||||
|
|
||||||
__tablename__ = "group_descr"
|
__tablename__ = "group_descr"
|
||||||
|
48
app/models/scolar_event.py
Normal file
48
app/models/scolar_event.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
"""évènements scolaires dans la vie d'un étudiant(inscription, ...)
|
||||||
|
"""
|
||||||
|
from app import db
|
||||||
|
from app.models import SHORT_STR_LEN
|
||||||
|
|
||||||
|
|
||||||
|
class ScolarEvent(db.Model):
|
||||||
|
"""Evenement dans le parcours scolaire d'un étudiant"""
|
||||||
|
|
||||||
|
__tablename__ = "scolar_events"
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
event_id = db.synonym("id")
|
||||||
|
etudid = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("identite.id", ondelete="CASCADE"),
|
||||||
|
)
|
||||||
|
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||||||
|
formsemestre_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_formsemestre.id", ondelete="SET NULL"),
|
||||||
|
)
|
||||||
|
ue_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_ue.id", ondelete="SET NULL"),
|
||||||
|
)
|
||||||
|
# 'CREATION', 'INSCRIPTION', 'DEMISSION',
|
||||||
|
# 'AUT_RED', 'EXCLUS', 'VALID_UE', 'VALID_SEM'
|
||||||
|
# 'ECHEC_SEM'
|
||||||
|
# 'UTIL_COMPENSATION'
|
||||||
|
event_type = db.Column(db.String(SHORT_STR_LEN))
|
||||||
|
# Semestre compensé par formsemestre_id:
|
||||||
|
comp_formsemestre_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey("notes_formsemestre.id"),
|
||||||
|
)
|
||||||
|
etud = db.relationship("Identite", lazy="select", backref="events", uselist=False)
|
||||||
|
formsemestre = db.relationship(
|
||||||
|
"FormSemestre", lazy="select", uselist=False, foreign_keys=[formsemestre_id]
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"as a dict"
|
||||||
|
d = dict(self.__dict__)
|
||||||
|
d.pop("_sa_instance_state", None)
|
||||||
|
return d
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.__class__.__name__}({self.event_type}, {self.event_date.isoformat()}, {self.formsemestre})"
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: UTF-8 -*
|
# -*- coding: UTF-8 -*
|
||||||
|
|
||||||
"""Notes, décisions de jury, évènements scolaires
|
"""Notes, décisions de jury
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
@ -218,47 +218,3 @@ class ScolarAutorisationInscription(db.Model):
|
|||||||
msg=f"Passage vers S{autorisation.semestre_id}: effacé",
|
msg=f"Passage vers S{autorisation.semestre_id}: effacé",
|
||||||
)
|
)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
|
|
||||||
class ScolarEvent(db.Model):
|
|
||||||
"""Evenement dans le parcours scolaire d'un étudiant"""
|
|
||||||
|
|
||||||
__tablename__ = "scolar_events"
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
event_id = db.synonym("id")
|
|
||||||
etudid = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("identite.id", ondelete="CASCADE"),
|
|
||||||
)
|
|
||||||
event_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
|
||||||
formsemestre_id = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("notes_formsemestre.id", ondelete="SET NULL"),
|
|
||||||
)
|
|
||||||
ue_id = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("notes_ue.id", ondelete="SET NULL"),
|
|
||||||
)
|
|
||||||
# 'CREATION', 'INSCRIPTION', 'DEMISSION',
|
|
||||||
# 'AUT_RED', 'EXCLUS', 'VALID_UE', 'VALID_SEM'
|
|
||||||
# 'ECHEC_SEM'
|
|
||||||
# 'UTIL_COMPENSATION'
|
|
||||||
event_type = db.Column(db.String(SHORT_STR_LEN))
|
|
||||||
# Semestre compensé par formsemestre_id:
|
|
||||||
comp_formsemestre_id = db.Column(
|
|
||||||
db.Integer,
|
|
||||||
db.ForeignKey("notes_formsemestre.id"),
|
|
||||||
)
|
|
||||||
etud = db.relationship("Identite", lazy="select", backref="events", uselist=False)
|
|
||||||
formsemestre = db.relationship(
|
|
||||||
"FormSemestre", lazy="select", uselist=False, foreign_keys=[formsemestre_id]
|
|
||||||
)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
|
||||||
"as a dict"
|
|
||||||
d = dict(self.__dict__)
|
|
||||||
d.pop("_sa_instance_state", None)
|
|
||||||
return d
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"{self.__class__.__name__}({self.event_type}, {self.event_date.isoformat()}, {self.formsemestre})"
|
|
||||||
|
@ -45,6 +45,12 @@ from app.models.etudiants import (
|
|||||||
pivot_year,
|
pivot_year,
|
||||||
)
|
)
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
from app.scodoc.sco_utils import (
|
||||||
|
format_civilite,
|
||||||
|
format_nom,
|
||||||
|
format_nomprenom,
|
||||||
|
format_prenom,
|
||||||
|
)
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||||
from app.scodoc import safehtml
|
from app.scodoc import safehtml
|
||||||
@ -102,60 +108,6 @@ def force_uppercase(s):
|
|||||||
return s.upper() if s else s
|
return s.upper() if s else s
|
||||||
|
|
||||||
|
|
||||||
def format_nomprenom(etud, reverse=False):
|
|
||||||
"""Formatte civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
|
||||||
Si reverse, "Dupont Pierre", sans civilité.
|
|
||||||
|
|
||||||
DEPRECATED: utiliser Identite.nomprenom
|
|
||||||
"""
|
|
||||||
nom = etud.get("nom_disp", "") or etud.get("nom_usuel", "") or etud["nom"]
|
|
||||||
prenom = format_prenom(etud["prenom"])
|
|
||||||
civilite = format_civilite(etud["civilite"])
|
|
||||||
if reverse:
|
|
||||||
fs = [nom, prenom]
|
|
||||||
else:
|
|
||||||
fs = [civilite, prenom, nom]
|
|
||||||
return " ".join([x for x in fs if x])
|
|
||||||
|
|
||||||
|
|
||||||
def format_prenom(s):
|
|
||||||
"""Formatte prenom etudiant pour affichage
|
|
||||||
DEPRECATED: utiliser Identite.prenom_str
|
|
||||||
"""
|
|
||||||
if not s:
|
|
||||||
return ""
|
|
||||||
frags = s.split()
|
|
||||||
r = []
|
|
||||||
for frag in frags:
|
|
||||||
fs = frag.split("-")
|
|
||||||
r.append("-".join([x.lower().capitalize() for x in fs]))
|
|
||||||
return " ".join(r)
|
|
||||||
|
|
||||||
|
|
||||||
def format_nom(s, uppercase=True):
|
|
||||||
if not s:
|
|
||||||
return ""
|
|
||||||
if uppercase:
|
|
||||||
return s.upper()
|
|
||||||
else:
|
|
||||||
return format_prenom(s)
|
|
||||||
|
|
||||||
|
|
||||||
def format_civilite(civilite):
|
|
||||||
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
|
||||||
personne ne souhaitant pas d'affichage).
|
|
||||||
Raises ScoValueError if conversion fails.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return {
|
|
||||||
"M": "M.",
|
|
||||||
"F": "Mme",
|
|
||||||
"X": "",
|
|
||||||
}[civilite]
|
|
||||||
except KeyError as exc:
|
|
||||||
raise ScoValueError(f"valeur invalide pour la civilité: {civilite}") from exc
|
|
||||||
|
|
||||||
|
|
||||||
def _format_etat_civil(etud: dict) -> str:
|
def _format_etat_civil(etud: dict) -> str:
|
||||||
"Mme Béatrice DUPONT, en utilisant les données d'état civil si indiquées."
|
"Mme Béatrice DUPONT, en utilisant les données d'état civil si indiquées."
|
||||||
if etud["prenom_etat_civil"] or etud["civilite_etat_civil"]:
|
if etud["prenom_etat_civil"] or etud["civilite_etat_civil"]:
|
||||||
@ -657,16 +609,6 @@ def create_etud(cnx, args: dict = None):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
etudid = etud.id
|
etudid = etud.id
|
||||||
|
|
||||||
# event
|
|
||||||
scolar_events_create(
|
|
||||||
cnx,
|
|
||||||
args={
|
|
||||||
"etudid": etudid,
|
|
||||||
"event_date": time.strftime("%d/%m/%Y"),
|
|
||||||
"formsemestre_id": None,
|
|
||||||
"event_type": "CREATION",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
# log
|
# log
|
||||||
logdb(
|
logdb(
|
||||||
cnx,
|
cnx,
|
||||||
@ -674,16 +616,18 @@ def create_etud(cnx, args: dict = None):
|
|||||||
etudid=etudid,
|
etudid=etudid,
|
||||||
msg="creation initiale",
|
msg="creation initiale",
|
||||||
)
|
)
|
||||||
etud = etudident_list(cnx, {"etudid": etudid})[0]
|
etud_dict = etudident_list(cnx, {"etudid": etudid})[0]
|
||||||
fill_etuds_info([etud])
|
fill_etuds_info([etud_dict])
|
||||||
etud["url"] = url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
|
etud_dict["url"] = url_for(
|
||||||
|
"scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid
|
||||||
|
)
|
||||||
ScolarNews.add(
|
ScolarNews.add(
|
||||||
typ=ScolarNews.NEWS_INSCR,
|
typ=ScolarNews.NEWS_INSCR,
|
||||||
text='Nouvel étudiant <a href="%(url)s">%(nomprenom)s</a>' % etud,
|
text=f"Nouvel étudiant {etud.html_link_fiche()}",
|
||||||
url=etud["url"],
|
url=etud["url"],
|
||||||
max_frequency=0,
|
max_frequency=0,
|
||||||
)
|
)
|
||||||
return etud
|
return etud_dict
|
||||||
|
|
||||||
|
|
||||||
# ---------- "EVENTS"
|
# ---------- "EVENTS"
|
||||||
|
@ -42,6 +42,7 @@ from app.scodoc import sco_groups
|
|||||||
from app.scodoc.sco_exceptions import ScoException
|
from app.scodoc.sco_exceptions import ScoException
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
def form_search_etud(
|
def form_search_etud(
|
||||||
@ -271,7 +272,7 @@ def search_etud_by_name(term: str) -> list:
|
|||||||
data = [
|
data = [
|
||||||
{
|
{
|
||||||
"label": "%s %s %s"
|
"label": "%s %s %s"
|
||||||
% (x["code_nip"], x["nom"], sco_etud.format_prenom(x["prenom"])),
|
% (x["code_nip"], x["nom"], scu.format_prenom(x["prenom"])),
|
||||||
"value": x["code_nip"],
|
"value": x["code_nip"],
|
||||||
}
|
}
|
||||||
for x in r
|
for x in r
|
||||||
@ -290,7 +291,7 @@ def search_etud_by_name(term: str) -> list:
|
|||||||
|
|
||||||
data = [
|
data = [
|
||||||
{
|
{
|
||||||
"label": "%s %s" % (x["nom"], sco_etud.format_prenom(x["prenom"])),
|
"label": "%s %s" % (x["nom"], scu.format_prenom(x["prenom"])),
|
||||||
"value": x["etudid"],
|
"value": x["etudid"],
|
||||||
}
|
}
|
||||||
for x in r
|
for x in r
|
||||||
|
@ -39,7 +39,7 @@ from app.comp.res_compat import NotesTableCompat
|
|||||||
from app.models import Formation, FormSemestre, FormSemestreInscription, Scolog
|
from app.models import Formation, FormSemestre, FormSemestreInscription, Scolog
|
||||||
from app.models.etudiants import Identite
|
from app.models.etudiants import Identite
|
||||||
from app.models.groups import Partition, GroupDescr
|
from app.models.groups import Partition, GroupDescr
|
||||||
from app.models.validations import ScolarEvent
|
from app.models.scolar_event import ScolarEvent
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
from app import log
|
from app import log
|
||||||
from app.scodoc.scolog import logdb
|
from app.scodoc.scolog import logdb
|
||||||
|
@ -53,6 +53,7 @@ from app.scodoc import codes_cursus
|
|||||||
from app.scodoc import sco_cursus
|
from app.scodoc import sco_cursus
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc.sco_etud import etud_sort_key
|
from app.scodoc.sco_etud import etud_sort_key
|
||||||
|
import app.scodoc.sco_utils as scu
|
||||||
from app.scodoc import sco_xml
|
from app.scodoc import sco_xml
|
||||||
from app.scodoc.sco_exceptions import ScoException, AccessDenied, ScoValueError
|
from app.scodoc.sco_exceptions import ScoException, AccessDenied, ScoValueError
|
||||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||||
@ -573,8 +574,8 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
|
|||||||
etudid=str(e["etudid"]),
|
etudid=str(e["etudid"]),
|
||||||
civilite=etud["civilite_str"] or "",
|
civilite=etud["civilite_str"] or "",
|
||||||
sexe=etud["civilite_str"] or "", # compat
|
sexe=etud["civilite_str"] or "", # compat
|
||||||
nom=sco_etud.format_nom(etud["nom"] or ""),
|
nom=scu.format_nom(etud["nom"] or ""),
|
||||||
prenom=sco_etud.format_prenom(etud["prenom"] or ""),
|
prenom=scu.format_prenom(etud["prenom"] or ""),
|
||||||
origin=_comp_etud_origin(etud, formsemestre),
|
origin=_comp_etud_origin(etud, formsemestre),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -599,8 +600,8 @@ def XMLgetGroupsInPartition(partition_id): # was XMLgetGroupesTD
|
|||||||
"etud",
|
"etud",
|
||||||
etudid=str(etud["etudid"]),
|
etudid=str(etud["etudid"]),
|
||||||
sexe=etud["civilite_str"] or "",
|
sexe=etud["civilite_str"] or "",
|
||||||
nom=sco_etud.format_nom(etud["nom"] or ""),
|
nom=scu.format_nom(etud["nom"] or ""),
|
||||||
prenom=sco_etud.format_prenom(etud["prenom"] or ""),
|
prenom=scu.format_prenom(etud["prenom"] or ""),
|
||||||
origin=_comp_etud_origin(etud, formsemestre),
|
origin=_comp_etud_origin(etud, formsemestre),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -101,7 +101,8 @@ def group_rename(group_id):
|
|||||||
"allow_null": True,
|
"allow_null": True,
|
||||||
"explanation": """optionnel : identifiant du groupe dans le logiciel
|
"explanation": """optionnel : identifiant du groupe dans le logiciel
|
||||||
d'emploi du temps, pour le cas où les noms de groupes ne seraient pas
|
d'emploi du temps, pour le cas où les noms de groupes ne seraient pas
|
||||||
les mêmes dans ScoDoc et dans l'emploi du temps (si plusieurs ids,
|
les mêmes dans ScoDoc et dans l'emploi du temps (si plusieurs ids de
|
||||||
|
groupes EDT doivent correspondre au même groupe ScoDoc,
|
||||||
les séparer par des virgules).""",
|
les séparer par des virgules).""",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -254,7 +254,7 @@ def import_users(users, force="") -> tuple[bool, list[str], int]:
|
|||||||
if import_ok:
|
if import_ok:
|
||||||
for u in created.values():
|
for u in created.values():
|
||||||
# Création de l'utilisateur (via SQLAlchemy)
|
# Création de l'utilisateur (via SQLAlchemy)
|
||||||
user = User()
|
user = User(user_name=u["user_name"])
|
||||||
user.from_dict(u, new_user=True)
|
user.from_dict(u, new_user=True)
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -244,8 +244,8 @@ def feuille_preparation_jury(formsemestre_id):
|
|||||||
[
|
[
|
||||||
etud.id,
|
etud.id,
|
||||||
etud.civilite_str,
|
etud.civilite_str,
|
||||||
sco_etud.format_nom(etud.nom),
|
scu.format_nom(etud.nom),
|
||||||
sco_etud.format_prenom(etud.prenom),
|
scu.format_prenom(etud.prenom),
|
||||||
etud.date_naissance,
|
etud.date_naissance,
|
||||||
etud.admission.bac if etud.admission else "",
|
etud.admission.bac if etud.admission else "",
|
||||||
etud.admission.specialite if etud.admission else "",
|
etud.admission.specialite if etud.admission else "",
|
||||||
|
@ -1244,7 +1244,7 @@ def _form_saisie_notes(
|
|||||||
'<span class="%s">' % classdem
|
'<span class="%s">' % classdem
|
||||||
+ e["civilite_str"]
|
+ e["civilite_str"]
|
||||||
+ " "
|
+ " "
|
||||||
+ sco_etud.format_nomprenom(e, reverse=True)
|
+ scu.format_nomprenom(e, reverse=True)
|
||||||
+ "</span>"
|
+ "</span>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -164,9 +164,9 @@ def trombino_html(groups_infos):
|
|||||||
H.append("</span>")
|
H.append("</span>")
|
||||||
H.append(
|
H.append(
|
||||||
'<span class="trombi_legend"><span class="trombi_prenom">'
|
'<span class="trombi_legend"><span class="trombi_prenom">'
|
||||||
+ sco_etud.format_prenom(t["prenom"])
|
+ scu.format_prenom(t["prenom"])
|
||||||
+ '</span><span class="trombi_nom">'
|
+ '</span><span class="trombi_nom">'
|
||||||
+ sco_etud.format_nom(t["nom"])
|
+ scu.format_nom(t["nom"])
|
||||||
+ (" <i>(dem.)</i>" if t["etat"] == "D" else "")
|
+ (" <i>(dem.)</i>" if t["etat"] == "D" else "")
|
||||||
)
|
)
|
||||||
H.append("</span></span></span>")
|
H.append("</span></span></span>")
|
||||||
@ -349,7 +349,7 @@ def _trombino_pdf(groups_infos):
|
|||||||
[img],
|
[img],
|
||||||
[
|
[
|
||||||
Paragraph(
|
Paragraph(
|
||||||
SU(sco_etud.format_nomprenom(t)),
|
SU(scu.format_nomprenom(t)),
|
||||||
style_sheet["Normal"],
|
style_sheet["Normal"],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -428,7 +428,7 @@ def _listeappel_photos_pdf(groups_infos):
|
|||||||
t = groups_infos.members[i]
|
t = groups_infos.members[i]
|
||||||
img = _get_etud_platypus_image(t, image_width=PHOTO_WIDTH)
|
img = _get_etud_platypus_image(t, image_width=PHOTO_WIDTH)
|
||||||
txt = Paragraph(
|
txt = Paragraph(
|
||||||
SU(sco_etud.format_nomprenom(t)),
|
SU(scu.format_nomprenom(t)),
|
||||||
style_sheet["Normal"],
|
style_sheet["Normal"],
|
||||||
)
|
)
|
||||||
if currow:
|
if currow:
|
||||||
|
@ -55,7 +55,7 @@ def trombino_doc(groups_infos):
|
|||||||
cell = table.rows[2 * li + 1].cells[co]
|
cell = table.rows[2 * li + 1].cells[co]
|
||||||
cell.vertical_alignment = WD_ALIGN_VERTICAL.TOP
|
cell.vertical_alignment = WD_ALIGN_VERTICAL.TOP
|
||||||
cell_p, cell_f, cell_r = _paragraph_format_run(cell)
|
cell_p, cell_f, cell_r = _paragraph_format_run(cell)
|
||||||
cell_r.add_text(sco_etud.format_nomprenom(t))
|
cell_r.add_text(scu.format_nomprenom(t))
|
||||||
cell_f.space_after = Mm(8)
|
cell_f.space_after = Mm(8)
|
||||||
|
|
||||||
return scu.send_docx(document, filename)
|
return scu.send_docx(document, filename)
|
||||||
|
@ -196,9 +196,9 @@ def pdf_trombino_tours(
|
|||||||
Paragraph(
|
Paragraph(
|
||||||
SU(
|
SU(
|
||||||
"<para align=center><font size=8>"
|
"<para align=center><font size=8>"
|
||||||
+ sco_etud.format_prenom(m["prenom"])
|
+ scu.format_prenom(m["prenom"])
|
||||||
+ " "
|
+ " "
|
||||||
+ sco_etud.format_nom(m["nom"])
|
+ scu.format_nom(m["nom"])
|
||||||
+ text_group
|
+ text_group
|
||||||
+ "</font></para>"
|
+ "</font></para>"
|
||||||
),
|
),
|
||||||
@ -413,11 +413,7 @@ def pdf_feuille_releve_absences(
|
|||||||
for m in members:
|
for m in members:
|
||||||
currow = [
|
currow = [
|
||||||
Paragraph(
|
Paragraph(
|
||||||
SU(
|
SU(scu.format_nom(m["nom"]) + " " + scu.format_prenom(m["prenom"])),
|
||||||
sco_etud.format_nom(m["nom"])
|
|
||||||
+ " "
|
|
||||||
+ sco_etud.format_prenom(m["prenom"])
|
|
||||||
),
|
|
||||||
StyleSheet["Normal"],
|
StyleSheet["Normal"],
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
@ -204,7 +204,7 @@ def list_users(
|
|||||||
"cas_allow_scodoc_login",
|
"cas_allow_scodoc_login",
|
||||||
"cas_last_login",
|
"cas_last_login",
|
||||||
]
|
]
|
||||||
columns_ids.append("email_institutionnel")
|
columns_ids += ["email_institutionnel", "edt_id"]
|
||||||
|
|
||||||
title = "Utilisateurs définis dans ScoDoc"
|
title = "Utilisateurs définis dans ScoDoc"
|
||||||
tab = GenTable(
|
tab = GenTable(
|
||||||
@ -227,6 +227,7 @@ def list_users(
|
|||||||
"cas_allow_login": "CAS autorisé",
|
"cas_allow_login": "CAS autorisé",
|
||||||
"cas_allow_scodoc_login": "Cnx sans CAS",
|
"cas_allow_scodoc_login": "Cnx sans CAS",
|
||||||
"cas_last_login": "Dernier login CAS",
|
"cas_last_login": "Dernier login CAS",
|
||||||
|
"edt_id": "Identifiant emploi du temps",
|
||||||
},
|
},
|
||||||
caption=title,
|
caption=title,
|
||||||
page_title="title",
|
page_title="title",
|
||||||
@ -431,15 +432,3 @@ def check_modif_user(
|
|||||||
)
|
)
|
||||||
# Roles ?
|
# Roles ?
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
def user_edit(user_name, vals):
|
|
||||||
"""Edit the user specified by user_name
|
|
||||||
(ported from Zope to SQLAlchemy, hence strange !)
|
|
||||||
"""
|
|
||||||
u: User = User.query.filter_by(user_name=user_name).first()
|
|
||||||
if not u:
|
|
||||||
raise ScoValueError("Invalid user_name")
|
|
||||||
u.from_dict(vals)
|
|
||||||
db.session.add(u)
|
|
||||||
db.session.commit()
|
|
||||||
|
@ -64,6 +64,7 @@ from config import Config
|
|||||||
from app import log, ScoDocJSONEncoder
|
from app import log, ScoDocJSONEncoder
|
||||||
|
|
||||||
from app.scodoc.codes_cursus import NOTES_TOLERANCE, CODES_EXPL
|
from app.scodoc.codes_cursus import NOTES_TOLERANCE, CODES_EXPL
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from app.scodoc import sco_xml
|
from app.scodoc import sco_xml
|
||||||
import sco_version
|
import sco_version
|
||||||
|
|
||||||
@ -1139,6 +1140,61 @@ def abbrev_prenom(prenom):
|
|||||||
return abrv
|
return abrv
|
||||||
|
|
||||||
|
|
||||||
|
def format_civilite(civilite):
|
||||||
|
"""returns 'M.' ou 'Mme' ou '' (pour le genre neutre,
|
||||||
|
personne ne souhaitant pas d'affichage).
|
||||||
|
Raises ScoValueError if conversion fails.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return {
|
||||||
|
"M": "M.",
|
||||||
|
"F": "Mme",
|
||||||
|
"X": "",
|
||||||
|
}[civilite]
|
||||||
|
except KeyError as exc:
|
||||||
|
raise ScoValueError(f"valeur invalide pour la civilité: {civilite}") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def format_nomprenom(etud, reverse=False):
|
||||||
|
"""Formatte civilité/nom/prenom pour affichages: "M. Pierre Dupont"
|
||||||
|
Si reverse, "Dupont Pierre", sans civilité.
|
||||||
|
|
||||||
|
DEPRECATED: utiliser Identite.nomprenom
|
||||||
|
"""
|
||||||
|
nom = etud.get("nom_disp", "") or etud.get("nom_usuel", "") or etud["nom"]
|
||||||
|
prenom = format_prenom(etud["prenom"])
|
||||||
|
civilite = format_civilite(etud["civilite"])
|
||||||
|
if reverse:
|
||||||
|
fs = [nom, prenom]
|
||||||
|
else:
|
||||||
|
fs = [civilite, prenom, nom]
|
||||||
|
return " ".join([x for x in fs if x])
|
||||||
|
|
||||||
|
|
||||||
|
def format_nom(s, uppercase=True):
|
||||||
|
"Formatte le nom"
|
||||||
|
if not s:
|
||||||
|
return ""
|
||||||
|
if uppercase:
|
||||||
|
return s.upper()
|
||||||
|
else:
|
||||||
|
return format_prenom(s)
|
||||||
|
|
||||||
|
|
||||||
|
def format_prenom(s):
|
||||||
|
"""Formatte prenom etudiant pour affichage
|
||||||
|
DEPRECATED: utiliser Identite.prenom_str
|
||||||
|
"""
|
||||||
|
if not s:
|
||||||
|
return ""
|
||||||
|
frags = s.split()
|
||||||
|
r = []
|
||||||
|
for frag in frags:
|
||||||
|
fs = frag.split("-")
|
||||||
|
r.append("-".join([x.lower().capitalize() for x in fs]))
|
||||||
|
return " ".join(r)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
def timedate_human_repr():
|
def timedate_human_repr():
|
||||||
"representation du temps courant pour utilisateur"
|
"representation du temps courant pour utilisateur"
|
||||||
@ -1476,3 +1532,15 @@ def is_assiduites_module_forced(
|
|||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
retour = sco_preferences.get_preference("forcer_module", dept_id=dept_id)
|
retour = sco_preferences.get_preference("forcer_module", dept_id=dept_id)
|
||||||
return retour
|
return retour
|
||||||
|
|
||||||
|
|
||||||
|
def get_assiduites_time_config(config_type: str) -> str:
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
|
|
||||||
|
match config_type:
|
||||||
|
case "matin":
|
||||||
|
return ScoDocSiteConfig.get("assi_morning_time", "08:00:00")
|
||||||
|
case "aprem":
|
||||||
|
return ScoDocSiteConfig.get("assi_afternoon_time", "18:00:00")
|
||||||
|
case "pivot":
|
||||||
|
return ScoDocSiteConfig.get("assi_lunch_time", "13:00:00")
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
:root {
|
:root {
|
||||||
--color-present: #6bdb83;
|
--color-present: #6bdb83;
|
||||||
--color-absent: #e62a11;
|
--color-absent: #e62a11;
|
||||||
|
--color-absent-clair: #F25D4A;
|
||||||
--color-retard: #f0c865;
|
--color-retard: #f0c865;
|
||||||
--color-justi: #7059FF;
|
--color-justi: #7059FF;
|
||||||
|
--color-justi-clair: #6885E3;
|
||||||
--color-justi-invalide: #a84476;
|
--color-justi-invalide: #a84476;
|
||||||
--color-nonwork: #badfff;
|
--color-nonwork: #badfff;
|
||||||
|
|
||||||
--color-absent-justi: #e65ab7;
|
--color-absent-justi: #e65ab7;
|
||||||
--color-retard-justi: #ffef7a;
|
--color-retard-justi: #ffef7a;
|
||||||
|
|
||||||
--color-error: #FF0000;
|
--color-error: #e62a11;
|
||||||
--color-warning: #eec660;
|
--color-warning: #eec660;
|
||||||
--color-information: #658ef0;
|
--color-information: #658ef0;
|
||||||
|
|
||||||
@ -21,7 +23,7 @@
|
|||||||
|
|
||||||
--color-defaut: #FFF;
|
--color-defaut: #FFF;
|
||||||
--color-defaut-dark: #444;
|
--color-defaut-dark: #444;
|
||||||
|
--color-default-text: #1F1F1F;
|
||||||
|
|
||||||
|
|
||||||
--motif-justi: repeating-linear-gradient(135deg, transparent, transparent 4px, var(--color-justi) 4px, var(--color-justi) 8px);
|
--motif-justi: repeating-linear-gradient(135deg, transparent, transparent 4px, var(--color-justi) 4px, var(--color-justi) 8px);
|
||||||
|
@ -4347,6 +4347,10 @@ table.dataTable td.group {
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#zonePartitions .edt_id {
|
||||||
|
color: rgb(85, 255, 24);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------- Nouveau tableau recap ------------ */
|
/* ------------- Nouveau tableau recap ------------ */
|
||||||
div.table_recap {
|
div.table_recap {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
@ -4856,7 +4860,3 @@ div.cas_etat_certif_ssl {
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: rgb(231, 0, 0);
|
color: rgb(231, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edt_id {
|
|
||||||
color: rgb(85, 255, 24);
|
|
||||||
}
|
|
||||||
|
@ -953,6 +953,89 @@ function createAssiduite(etat, etudid) {
|
|||||||
);
|
);
|
||||||
return !with_errors;
|
return !with_errors;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Création d'une assiduité pour un étudiant
|
||||||
|
* @param {String} etat l'état de l'étudiant
|
||||||
|
* @param {Number | String} etudid l'identifiant de l'étudiant
|
||||||
|
*
|
||||||
|
* TODO : Rendre asynchrone
|
||||||
|
*/
|
||||||
|
function createAssiduiteComplete(assiduite, etudid) {
|
||||||
|
if (!hasModuleImpl(assiduite) && window.forceModule) {
|
||||||
|
const html = `
|
||||||
|
<h3>Aucun module n'a été spécifié</h3>
|
||||||
|
`;
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.innerHTML = html;
|
||||||
|
openAlertModal("Erreur Module", div);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = getUrl() + `/api/assiduite/${etudid}/create`;
|
||||||
|
|
||||||
|
let with_errors = false;
|
||||||
|
|
||||||
|
sync_post(
|
||||||
|
path,
|
||||||
|
[assiduite],
|
||||||
|
(data, status) => {
|
||||||
|
//success
|
||||||
|
if (data.success.length > 0) {
|
||||||
|
let obj = data.success["0"].message.assiduite_id;
|
||||||
|
}
|
||||||
|
if (data.errors.length > 0) {
|
||||||
|
console.error(data.errors["0"].message);
|
||||||
|
if (data.errors["0"].message == "Module non renseigné") {
|
||||||
|
const HTML = `
|
||||||
|
<p>Attention, le module doit obligatoirement être renseigné.</p>
|
||||||
|
<p>Cela vient de la configuration du semestre ou plus largement du département.</p>
|
||||||
|
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const content = document.createElement("div");
|
||||||
|
content.innerHTML = HTML;
|
||||||
|
|
||||||
|
openAlertModal("Sélection du module", content);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
data.errors["0"].message == "L'étudiant n'est pas inscrit au module"
|
||||||
|
) {
|
||||||
|
const HTML = `
|
||||||
|
<p>Attention, l'étudiant n'est pas inscrit à ce module.</p>
|
||||||
|
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const content = document.createElement("div");
|
||||||
|
content.innerHTML = HTML;
|
||||||
|
|
||||||
|
openAlertModal("Sélection du module", content);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
data.errors["0"].message ==
|
||||||
|
"Duplication: la période rentre en conflit avec une plage enregistrée"
|
||||||
|
) {
|
||||||
|
const HTML = `
|
||||||
|
<p>L'assiduité n'a pas pu être enregistrée car une autre assiduité existe sur la période sélectionnée</p>
|
||||||
|
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const content = document.createElement("div");
|
||||||
|
content.innerHTML = HTML;
|
||||||
|
|
||||||
|
openAlertModal("Période conflictuelle", content);
|
||||||
|
}
|
||||||
|
with_errors = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(data, status) => {
|
||||||
|
//error
|
||||||
|
console.error(data, status);
|
||||||
|
errorAlert();
|
||||||
|
with_errors = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return !with_errors;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suppression d'une assiduité
|
* Suppression d'une assiduité
|
||||||
|
234
app/templates/assiduites/pages/ajout_assiduites.j2
Normal file
234
app/templates/assiduites/pages/ajout_assiduites.j2
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
{% include "assiduites/widgets/toast.j2" %}
|
||||||
|
{% block pageContent %}
|
||||||
|
<div class="pageContent">
|
||||||
|
<h3>Ajouter une assiduité</h3>
|
||||||
|
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||||
|
{% if saisie_eval %}
|
||||||
|
<div id="saisie_eval">
|
||||||
|
<br>
|
||||||
|
<h3>
|
||||||
|
La saisie de l'assiduité a été préconfigurée en fonction de l'évaluation. <br>
|
||||||
|
Une fois la saisie finie, cliquez sur le lien si dessous pour revenir sur la gestion de l'évaluation
|
||||||
|
</h3>
|
||||||
|
<a href="{{redirect_url}}">retourner sur la page de l'évaluation</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<section class="assi-form page">
|
||||||
|
<fieldset>
|
||||||
|
<div class="assi-row">
|
||||||
|
<div class="assi-label">
|
||||||
|
<legend for="assi_date_debut" required>Date de début</legend>
|
||||||
|
<scodoc-datetime name="assi_date_debut" id="assi_date_debut"> </scodoc-datetime>
|
||||||
|
<span>Journée entière</span> <input type="checkbox" name="assi_journee" id="assi_journee">
|
||||||
|
</div>
|
||||||
|
<div class="assi-label" id="date_fin">
|
||||||
|
<legend for="assi_date_fin" required>Date de fin</legend>
|
||||||
|
<scodoc-datetime name="assi_date_fin" id="assi_date_fin"></scodoc-datetime>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="assi-row">
|
||||||
|
<div class="assi-label">
|
||||||
|
<legend for="assi_etat" required>Etat de l'assiduité</legend>
|
||||||
|
<select name="assi_etat" id="assi_etat">
|
||||||
|
<option value="absent" selected>Absent</option>
|
||||||
|
<option value="retard">Retard</option>
|
||||||
|
<option value="present">Présent</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="assi-row">
|
||||||
|
<div class="assi-label">
|
||||||
|
<legend for="assi_module" required>Module</legend>
|
||||||
|
{% with moduleid="ajout_assiduite_module_impl",label=false %}
|
||||||
|
{% include "assiduites/widgets/moduleimpl_dynamic_selector.j2" %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="assi-row">
|
||||||
|
<div class="assi-label">
|
||||||
|
<legend for="assi_raison">Raison</legend>
|
||||||
|
<textarea name="assi_raison" id="assi_raison" cols="50" rows="10" maxlength="500"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="assi-row">
|
||||||
|
<button onclick="validerFormulaire(this)">Créer l'assiduité</button>
|
||||||
|
<button onclick="effacerFormulaire()">Remettre à zero</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<section class="liste">
|
||||||
|
<a class="icon filter" onclick="filterAssi()"></a>
|
||||||
|
{% include "assiduites/widgets/tableau_assi.j2" %}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.assi-row {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assi-form fieldset {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageContent {
|
||||||
|
max-width: var(--sco-content-max-width);
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assi-label {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[required]::after {
|
||||||
|
content: "*";
|
||||||
|
color: var(--color-error);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
|
||||||
|
function validateFields() {
|
||||||
|
const field = document.querySelector('.assi-form')
|
||||||
|
const { deb, fin } = getDates()
|
||||||
|
const date_debut = new Date(deb);
|
||||||
|
const date_fin = new Date(fin);
|
||||||
|
|
||||||
|
if (deb == "" || fin == "" || !date_debut.isValid() || !date_fin.isValid()) {
|
||||||
|
openAlertModal("Erreur détéctée", document.createTextNode("Il faut indiquer une date de début et une date de fin valide."), "", color = "crimson");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (date_fin.isBefore(date_debut)) {
|
||||||
|
openAlertModal("Erreur détéctée", document.createTextNode("La date de fin doit se trouver après la date de début."), "", color = "crimson");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function fieldsToAssiduite() {
|
||||||
|
const field = document.querySelector('.assi-form.page')
|
||||||
|
|
||||||
|
const { deb, fin } = getDates()
|
||||||
|
|
||||||
|
const etat = field.querySelector('#assi_etat').value;
|
||||||
|
const raison = field.querySelector('#assi_raison').value;
|
||||||
|
const module = field.querySelector("#ajout_assiduite_module_impl").value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
date_debut: new Date(deb).toFakeIso(),
|
||||||
|
date_fin: new Date(fin).toFakeIso(),
|
||||||
|
etat: etat,
|
||||||
|
description: raison,
|
||||||
|
moduleimpl_id: module,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validerFormulaire(btn) {
|
||||||
|
if (!validateFields()) return
|
||||||
|
|
||||||
|
const assiduite = fieldsToAssiduite();
|
||||||
|
let assiduite_id = null;
|
||||||
|
|
||||||
|
createAssiduiteComplete(assiduite, etudid);
|
||||||
|
loadAll();
|
||||||
|
btn.disabled = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.disabled = false;
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function effacerFormulaire() {
|
||||||
|
const field = document.querySelector('.assi-form')
|
||||||
|
|
||||||
|
field.querySelector('#assi_date_debut').value = "";
|
||||||
|
field.querySelector('#assi_date_fin').value = "";
|
||||||
|
field.querySelector('#assi_etat').value = "attente";
|
||||||
|
field.querySelector('#assi_raison').value = "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function dayOnly() {
|
||||||
|
const date_deb = document.getElementById("assi_date_debut");
|
||||||
|
const date_fin = document.getElementById("assi_date_fin");
|
||||||
|
|
||||||
|
if (document.getElementById('assi_journee').checked) {
|
||||||
|
date_deb.setAttribute("show", "date")
|
||||||
|
date_fin.setAttribute("show", "date")
|
||||||
|
document.querySelector(`legend[for="assi_date_fin"]`).removeAttribute("required")
|
||||||
|
} else {
|
||||||
|
date_deb.removeAttribute("show")
|
||||||
|
date_fin.removeAttribute("show")
|
||||||
|
document.querySelector(`legend[for="assi_date_fin"]`).setAttribute("required", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDates() {
|
||||||
|
const date_deb = document.querySelector(".page #assi_date_debut")
|
||||||
|
const date_fin = document.querySelector(".page #assi_date_fin")
|
||||||
|
const journee = document.querySelector('.page #assi_journee').checked
|
||||||
|
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time)
|
||||||
|
let fin = "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
|
||||||
|
if (journee) {
|
||||||
|
fin = (date_fin.valueAsObject.date || date_deb.valueAsObject.date) + fin
|
||||||
|
} else {
|
||||||
|
fin = date_fin.valueAsObject.date + fin
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"deb": deb,
|
||||||
|
"fin": fin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const etudid = {{ sco.etud.id }};
|
||||||
|
|
||||||
|
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
||||||
|
const assi_morning = '{{assi_morning}}';
|
||||||
|
const assi_evening = '{{assi_evening}}';
|
||||||
|
|
||||||
|
{% if saisie_eval %}
|
||||||
|
const saisie_eval = true;
|
||||||
|
const date_deb = "{{date_deb}}";
|
||||||
|
const date_fin = "{{date_fin}}";
|
||||||
|
const moduleimpl = {{ moduleimpl_id }};
|
||||||
|
{% else %}
|
||||||
|
const saisie_eval = false;
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
loadAll();
|
||||||
|
document.getElementById('assi_journee').addEventListener('click', () => { dayOnly() });
|
||||||
|
dayOnly()
|
||||||
|
|
||||||
|
if (saisie_eval) {
|
||||||
|
document.getElementById("assi_date_debut").value = Date.removeUTC(date_deb);
|
||||||
|
document.getElementById("assi_date_fin").value = Date.removeUTC(date_fin);
|
||||||
|
} else {
|
||||||
|
const today = (new Date()).format("YYYY-MM-DD");
|
||||||
|
document.getElementById("assi_date_debut").valueAsObject = { date: today, time: assi_morning }
|
||||||
|
document.getElementById("assi_date_fin").valueAsObject = { time: assi_evening }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById("assi_date_debut").addEventListener("blur", (event) => {
|
||||||
|
updateSelect(null, "#ajout_assiduite_module_impl", event.target.valueAsObject.date)
|
||||||
|
})
|
||||||
|
|
||||||
|
updateSelect(saisie_eval ? moduleimpl : "", "#ajout_assiduite_module_impl", document.getElementById("assi_date_debut").valueAsObject.date);
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock pageContent %}
|
@ -3,10 +3,7 @@
|
|||||||
<div class="pageContent">
|
<div class="pageContent">
|
||||||
<h3>Justifier des absences ou retards</h3>
|
<h3>Justifier des absences ou retards</h3>
|
||||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||||
<section class="liste">
|
|
||||||
<a class="icon filter" onclick="filterJusti()"></a>
|
|
||||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="justi-form page">
|
<section class="justi-form page">
|
||||||
|
|
||||||
@ -60,6 +57,10 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
<section class="liste">
|
||||||
|
<a class="icon filter" onclick="filterJusti()"></a>
|
||||||
|
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="legende">
|
<div class="legende">
|
||||||
|
|
||||||
@ -224,12 +225,12 @@
|
|||||||
if (document.getElementById('justi_journee').checked) {
|
if (document.getElementById('justi_journee').checked) {
|
||||||
date_deb.setAttribute("show", "date")
|
date_deb.setAttribute("show", "date")
|
||||||
date_fin.setAttribute("show", "date")
|
date_fin.setAttribute("show", "date")
|
||||||
document.getElementById("date_fin").classList.add("hidden");
|
document.querySelector(`legend[for="justi_date_fin"]`).removeAttribute("required")
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
date_deb.removeAttribute("show")
|
date_deb.removeAttribute("show")
|
||||||
date_fin.removeAttribute("show")
|
date_fin.removeAttribute("show")
|
||||||
document.getElementById("date_fin").classList.remove("hidden");
|
document.querySelector(`legend[for="justi_date_fin"]`).setAttribute("required", "")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,8 +239,12 @@
|
|||||||
const date_fin = document.querySelector(".page #justi_date_fin")
|
const date_fin = document.querySelector(".page #justi_date_fin")
|
||||||
const journee = document.querySelector('.page #justi_journee').checked
|
const journee = document.querySelector('.page #justi_journee').checked
|
||||||
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time)
|
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time)
|
||||||
const fin = (journee ? date_deb.valueAsObject.date : date_fin.valueAsObject.date) + "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
|
let fin = "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
|
||||||
|
if (journee) {
|
||||||
|
fin = (date_fin.valueAsObject.date || date_deb.valueAsObject.date) + fin
|
||||||
|
} else {
|
||||||
|
fin = date_fin.valueAsObject.date + fin
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
"deb": deb,
|
"deb": deb,
|
||||||
"fin": fin,
|
"fin": fin,
|
||||||
|
@ -343,6 +343,9 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
const datePivot = "{{scu.get_assiduites_time_config("pivot")}}".split(":").map((el) => Number(el))
|
||||||
|
|
||||||
function getDaysBetweenDates(start, end) {
|
function getDaysBetweenDates(start, end) {
|
||||||
let now = new Date(start);
|
let now = new Date(start);
|
||||||
end = new Date(end);
|
end = new Date(end);
|
||||||
@ -476,7 +479,7 @@
|
|||||||
const matin = [new Date(date), new Date(date)]
|
const matin = [new Date(date), new Date(date)]
|
||||||
color = "sans_etat"
|
color = "sans_etat"
|
||||||
matin[0].setHours(0, 0, 0, 0)
|
matin[0].setHours(0, 0, 0, 0)
|
||||||
matin[1].setHours(12, 59, 59)
|
matin[1].setHours(...datePivot)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -515,7 +518,8 @@
|
|||||||
span_aprem.classList.add("color");
|
span_aprem.classList.add("color");
|
||||||
const aprem = [new Date(date), new Date(date)]
|
const aprem = [new Date(date), new Date(date)]
|
||||||
color = "sans_etat"
|
color = "sans_etat"
|
||||||
aprem[0].setHours(13, 0, 0, 0)
|
aprem[0].setHours(...datePivot)
|
||||||
|
aprem[0].add(1, "seconds")
|
||||||
aprem[1].setHours(23, 59, 59)
|
aprem[1].setHours(23, 59, 59)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
|
<div>
|
||||||
|
{% if label != false%}
|
||||||
<label for="moduleimpl_select">
|
<label for="moduleimpl_select">
|
||||||
Module
|
Module
|
||||||
|
</label>
|
||||||
|
{% else %}
|
||||||
|
{% endif %}
|
||||||
|
{% if moduleid %}
|
||||||
|
<select id="{{moduleid}}" class="dynaSelect">
|
||||||
|
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
||||||
|
</select>
|
||||||
|
{% else %}
|
||||||
<select id="moduleimpl_select" class="dynaSelect">
|
<select id="moduleimpl_select" class="dynaSelect">
|
||||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
||||||
</select>
|
</select>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div id="saved" style="display: none;">
|
<div id="saved" style="display: none;">
|
||||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -88,6 +88,7 @@
|
|||||||
td.textContent = getModuleImpl(assiduite);
|
td.textContent = getModuleImpl(assiduite);
|
||||||
} else if (k.indexOf('est_just') != -1) {
|
} else if (k.indexOf('est_just') != -1) {
|
||||||
td.textContent = assiduite[k] ? "Oui" : "Non"
|
td.textContent = assiduite[k] ? "Oui" : "Non"
|
||||||
|
if (assiduite[k]) row.classList.add("est_just")
|
||||||
} else if (k.indexOf('etudid') != -1) {
|
} else if (k.indexOf('etudid') != -1) {
|
||||||
const e = getEtudiant(assiduite.etudid);
|
const e = getEtudiant(assiduite.etudid);
|
||||||
|
|
||||||
|
@ -456,6 +456,7 @@
|
|||||||
td {
|
td {
|
||||||
border: 1px solid #dddddd;
|
border: 1px solid #dddddd;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
color: var(--color-default-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
@ -498,17 +499,25 @@
|
|||||||
|
|
||||||
.l-absent,
|
.l-absent,
|
||||||
.l-invalid {
|
.l-invalid {
|
||||||
background-color: var(--color-absent);
|
background-color: var(--color-absent-clair);
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-valid {
|
.l-valid {
|
||||||
background-color: var(--color-primary);
|
background-color: var(--color-justi-clair);
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-retard {
|
.l-retard {
|
||||||
background-color: var(--color-retard);
|
background-color: var(--color-retard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.l-absent.est_just {
|
||||||
|
background-color: var(--color-absent-justi);
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-retard.est_just {
|
||||||
|
background-color: var(--color-retard-justi);
|
||||||
|
}
|
||||||
|
|
||||||
/* Ajoutez des styles pour le conteneur de pagination et les boutons */
|
/* Ajoutez des styles pour le conteneur de pagination et les boutons */
|
||||||
.pagination-container {
|
.pagination-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -9,15 +9,18 @@
|
|||||||
<div class="user_basics">
|
<div class="user_basics">
|
||||||
<b>Login :</b> {{user.user_name}}<br>
|
<b>Login :</b> {{user.user_name}}<br>
|
||||||
<b>CAS id:</b> {{user.cas_id or "(aucun)"}}
|
<b>CAS id:</b> {{user.cas_id or "(aucun)"}}
|
||||||
|
{% if ScoDocSiteConfig.is_cas_enabled() %}
|
||||||
(CAS {{'autorisé' if user.cas_allow_login else 'interdit'}} pour cet utilisateur)
|
(CAS {{'autorisé' if user.cas_allow_login else 'interdit'}} pour cet utilisateur)
|
||||||
{% if user.cas_allow_scodoc_login %}
|
{% if user.cas_allow_scodoc_login %}
|
||||||
(connexion sans CAS autorisée)
|
(connexion sans CAS autorisée)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
<br>
|
<br>
|
||||||
<b>Nom :</b> {{user.nom or ""}}<br>
|
<b>Nom :</b> {{user.nom or ""}}<br>
|
||||||
<b>Prénom :</b> {{user.prenom or ""}}<br>
|
<b>Prénom :</b> {{user.prenom or ""}}<br>
|
||||||
<b>Mail :</b> {{user.email}}<br>
|
<b>Mail :</b> {{user.email}}<br>
|
||||||
<b>Mail institutionnel:</b> {{user.email_institutionnel or ""}}<br>
|
<b>Mail institutionnel:</b> {{user.email_institutionnel or ""}}<br>
|
||||||
|
<b>Identifiant EDT:</b> {{user.edt_id or ""}}<br>
|
||||||
<b>Rôles :</b> {{user.get_roles_string()}}<br>
|
<b>Rôles :</b> {{user.get_roles_string()}}<br>
|
||||||
<b>Dept :</b> {{user.dept or ""}}<br>
|
<b>Dept :</b> {{user.dept or ""}}<br>
|
||||||
{% if user.passwd_temp or user.password_scodoc7 %}
|
{% if user.passwd_temp or user.password_scodoc7 %}
|
||||||
|
@ -617,7 +617,7 @@
|
|||||||
listeGroupesAutoaffectation();
|
listeGroupesAutoaffectation();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
|
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (1).</h2>";
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -665,13 +665,13 @@
|
|||||||
document.querySelector(`#zoneGroupes .partition[data-idpartition="${idPartition}"]`).innerHTML += templateGroupe_zoneGroupes(r.id, name);
|
document.querySelector(`#zoneGroupes .partition[data-idpartition="${idPartition}"]`).innerHTML += templateGroupe_zoneGroupes(r.id, name);
|
||||||
|
|
||||||
// Lancement de l'édition du nom
|
// Lancement de l'édition du nom
|
||||||
divGroupe.querySelector(".modif").click();
|
// divGroupe.querySelector(".modif").click();
|
||||||
|
|
||||||
listeGroupesAutoaffectation();
|
listeGroupesAutoaffectation();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
|
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (4).</h2>";
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************/
|
/********************/
|
||||||
@ -746,12 +746,12 @@
|
|||||||
.then(r => { return r.json() })
|
.then(r => { return r.json() })
|
||||||
.then(r => {
|
.then(r => {
|
||||||
if (!r) {
|
if (!r) {
|
||||||
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
|
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (2).</h2>";
|
||||||
}
|
}
|
||||||
listeGroupesAutoaffectation();
|
listeGroupesAutoaffectation();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
|
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (3).</h2>";
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -802,7 +802,7 @@
|
|||||||
.then(r => { return r.json() })
|
.then(r => { return r.json() })
|
||||||
.then(r => {
|
.then(r => {
|
||||||
if (r.OK != true) {
|
if (r.OK != true) {
|
||||||
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
|
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (5).</h2>";
|
||||||
}
|
}
|
||||||
listeGroupesAutoaffectation();
|
listeGroupesAutoaffectation();
|
||||||
})
|
})
|
||||||
@ -916,12 +916,12 @@
|
|||||||
.then(r => { return r.json() })
|
.then(r => { return r.json() })
|
||||||
.then(r => {
|
.then(r => {
|
||||||
if (!r) {
|
if (!r) {
|
||||||
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
|
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (6).</h2>";
|
||||||
}
|
}
|
||||||
listeGroupesAutoaffectation();
|
listeGroupesAutoaffectation();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données.</h2>";
|
document.querySelector("main").innerHTML = "<h2>Une erreur s'est produite lors de la sauvegarde des données (7).</h2>";
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,7 +293,8 @@ def signal_assiduites_etud():
|
|||||||
"js/date_utils.js",
|
"js/date_utils.js",
|
||||||
"js/etud_info.js",
|
"js/etud_info.js",
|
||||||
],
|
],
|
||||||
cssstyles=[
|
cssstyles=CSSSTYLES
|
||||||
|
+ [
|
||||||
"css/assiduites.css",
|
"css/assiduites.css",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -318,27 +319,43 @@ def signal_assiduites_etud():
|
|||||||
header,
|
header,
|
||||||
_mini_timeline(),
|
_mini_timeline(),
|
||||||
render_template(
|
render_template(
|
||||||
"assiduites/pages/signal_assiduites_etud.j2",
|
"assiduites/pages/ajout_assiduites.j2",
|
||||||
sco=ScoData(etud),
|
sco=ScoData(etud),
|
||||||
date=_dateiso_to_datefr(date),
|
assi_limit_annee=sco_preferences.get_preference(
|
||||||
morning=morning,
|
"assi_limit_annee",
|
||||||
lunch=lunch,
|
dept_id=g.scodoc_dept_id,
|
||||||
timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
|
|
||||||
afternoon=afternoon,
|
|
||||||
nonworkdays=_non_work_days(),
|
|
||||||
forcer_module=sco_preferences.get_preference(
|
|
||||||
"forcer_module", dept_id=g.scodoc_dept_id
|
|
||||||
),
|
|
||||||
diff=_differee(
|
|
||||||
etudiants=[sco_etud.get_etud_info(etudid=etud.etudid, filled=True)[0]],
|
|
||||||
moduleimpl_select=select,
|
|
||||||
),
|
),
|
||||||
|
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
|
||||||
|
assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"),
|
||||||
|
|
||||||
saisie_eval=saisie_eval,
|
saisie_eval=saisie_eval,
|
||||||
date_deb=date_deb,
|
date_deb=date_deb,
|
||||||
date_fin=date_fin,
|
date_fin=date_fin,
|
||||||
redirect_url=redirect_url,
|
redirect_url=redirect_url,
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
),
|
),
|
||||||
|
# render_template(
|
||||||
|
# "assiduites/pages/signal_assiduites_etud.j2",
|
||||||
|
# sco=ScoData(etud),
|
||||||
|
# date=_dateiso_to_datefr(date),
|
||||||
|
# morning=morning,
|
||||||
|
# lunch=lunch,
|
||||||
|
# timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
|
||||||
|
# afternoon=afternoon,
|
||||||
|
# nonworkdays=_non_work_days(),
|
||||||
|
# forcer_module=sco_preferences.get_preference(
|
||||||
|
# "forcer_module", dept_id=g.scodoc_dept_id
|
||||||
|
# ),
|
||||||
|
# diff=_differee(
|
||||||
|
# etudiants=[sco_etud.get_etud_info(etudid=etud.etudid, filled=True)[0]],
|
||||||
|
# moduleimpl_select=select,
|
||||||
|
# ),
|
||||||
|
# saisie_eval=saisie_eval,
|
||||||
|
# date_deb=date_deb,
|
||||||
|
# date_fin=date_fin,
|
||||||
|
# redirect_url=redirect_url,
|
||||||
|
# moduleimpl_id=moduleimpl_id,
|
||||||
|
# ),
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
|
|
||||||
|
@ -450,6 +450,17 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
|
|||||||
"readonly": edit_only_roles,
|
"readonly": edit_only_roles,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"edt_id",
|
||||||
|
{
|
||||||
|
"title": "Identifiant sur l'emploi du temps",
|
||||||
|
"input_type": "text",
|
||||||
|
"explanation": """id du compte utilisateur sur l'emploi du temps
|
||||||
|
ou l'annuaire de l'établissement (par défaut, l'e-mail institutionnel )""",
|
||||||
|
"size": 36,
|
||||||
|
"allow_null": True,
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
if not edit: # options création utilisateur
|
if not edit: # options création utilisateur
|
||||||
descr += [
|
descr += [
|
||||||
@ -690,10 +701,12 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
|
|||||||
log(f"sco_users: editing {user_name} by {current_user.user_name}")
|
log(f"sco_users: editing {user_name} by {current_user.user_name}")
|
||||||
log(f"sco_users: previous_values={initvalues}")
|
log(f"sco_users: previous_values={initvalues}")
|
||||||
log(f"sco_users: new_values={vals}")
|
log(f"sco_users: new_values={vals}")
|
||||||
sco_users.user_edit(user_name, vals)
|
|
||||||
flash(f"Utilisateur {user_name} modifié")
|
|
||||||
else:
|
else:
|
||||||
sco_users.user_edit(user_name, {"roles_string": vals["roles_string"]})
|
vals = {"roles_string": vals["roles_string"]}
|
||||||
|
the_user.from_dict(vals)
|
||||||
|
db.session.add(the_user)
|
||||||
|
db.session.commit()
|
||||||
|
flash(f"Utilisateur {user_name} modifié")
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"users.user_info_page",
|
"users.user_info_page",
|
||||||
@ -749,7 +762,7 @@ def create_user_form(user_name=None, edit=0, all_roles=True):
|
|||||||
log(
|
log(
|
||||||
f"""sco_users: new_user {vals["user_name"]} by {current_user.user_name}"""
|
f"""sco_users: new_user {vals["user_name"]} by {current_user.user_name}"""
|
||||||
)
|
)
|
||||||
the_user = User()
|
the_user = User(user_name=user_name)
|
||||||
the_user.from_dict(vals, new_user=True)
|
the_user.from_dict(vals, new_user=True)
|
||||||
db.session.add(the_user)
|
db.session.add(the_user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -916,11 +929,12 @@ def user_info_page(user_name=None):
|
|||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"auth/user_info_page.j2",
|
"auth/user_info_page.j2",
|
||||||
user=user,
|
|
||||||
title=f"Utilisateur {user.user_name}",
|
|
||||||
Permission=Permission,
|
|
||||||
dept=dept,
|
dept=dept,
|
||||||
|
Permission=Permission,
|
||||||
|
ScoDocSiteConfig=ScoDocSiteConfig,
|
||||||
session_info=session_info,
|
session_info=session_info,
|
||||||
|
title=f"Utilisateur {user.user_name}",
|
||||||
|
user=user,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
[pytest]
|
[pytest]
|
||||||
|
norecursedirs = .git app/static
|
||||||
markers =
|
markers =
|
||||||
slow: marks tests as slow (deselect with '-m "not slow"')
|
slow: marks tests as slow (deselect with '-m "not slow"')
|
||||||
apo
|
apo
|
||||||
|
@ -88,7 +88,7 @@ python-editor==1.0.4
|
|||||||
pytz==2023.3.post1
|
pytz==2023.3.post1
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
redis==5.0.1
|
redis==5.0.1
|
||||||
reportlab==4.0.5
|
reportlab==4.0.7
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
rq==1.15.1
|
rq==1.15.1
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
|
@ -28,10 +28,11 @@ from tests.api.setup_test_api import (
|
|||||||
API_URL,
|
API_URL,
|
||||||
API_USER_ADMIN,
|
API_USER_ADMIN,
|
||||||
CHECK_CERTIFICATE,
|
CHECK_CERTIFICATE,
|
||||||
|
DEPT_ACRONYM,
|
||||||
POST_JSON,
|
POST_JSON,
|
||||||
api_headers,
|
|
||||||
get_auth_headers,
|
get_auth_headers,
|
||||||
)
|
)
|
||||||
|
from tests.api.setup_test_api import api_headers # pylint: disable=unused-import
|
||||||
from tests.api.tools_test_api import (
|
from tests.api.tools_test_api import (
|
||||||
BULLETIN_ETUDIANT_FIELDS,
|
BULLETIN_ETUDIANT_FIELDS,
|
||||||
BULLETIN_FIELDS,
|
BULLETIN_FIELDS,
|
||||||
@ -923,3 +924,20 @@ def test_etudiant_groups(api_headers):
|
|||||||
group = groups[0]
|
group = groups[0]
|
||||||
fields_ok = verify_fields(group, fields)
|
fields_ok = verify_fields(group, fields)
|
||||||
assert fields_ok is True
|
assert fields_ok is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_etudiant_create(api_headers):
|
||||||
|
"""/etudiant/create"""
|
||||||
|
admin_header = get_auth_headers(API_USER_ADMIN, API_PASSWORD_ADMIN)
|
||||||
|
args = {
|
||||||
|
"prenom": "Carl Philipp Emanuel",
|
||||||
|
"nom": "Bach",
|
||||||
|
"dept": DEPT_ACRONYM,
|
||||||
|
"civilite": "M",
|
||||||
|
}
|
||||||
|
etud = POST_JSON(
|
||||||
|
"/etudiant/create",
|
||||||
|
args,
|
||||||
|
headers=admin_header,
|
||||||
|
)
|
||||||
|
assert etud["nom"] == args["nom"].upper()
|
||||||
|
@ -90,6 +90,7 @@ def test_formsemestre_partition(api_headers):
|
|||||||
)
|
)
|
||||||
assert isinstance(group_r, dict)
|
assert isinstance(group_r, dict)
|
||||||
assert group_r["group_name"] == group_d["group_name"]
|
assert group_r["group_name"] == group_d["group_name"]
|
||||||
|
assert group_r["edt_id"] is None
|
||||||
# --- Liste groupes de la partition
|
# --- Liste groupes de la partition
|
||||||
partition = GET(f"/partition/{partition_r['id']}", headers=headers)
|
partition = GET(f"/partition/{partition_r['id']}", headers=headers)
|
||||||
assert isinstance(partition, dict)
|
assert isinstance(partition, dict)
|
||||||
@ -99,6 +100,26 @@ def test_formsemestre_partition(api_headers):
|
|||||||
group = partition["groups"][str(group_r["id"])] # nb: str car clés json en string
|
group = partition["groups"][str(group_r["id"])] # nb: str car clés json en string
|
||||||
assert group["group_name"] == group_d["group_name"]
|
assert group["group_name"] == group_d["group_name"]
|
||||||
|
|
||||||
|
# --- Ajout d'un groupe avec edt_id
|
||||||
|
group_d = {"group_name": "extra", "edt_id": "GEDT"}
|
||||||
|
group_r = POST_JSON(
|
||||||
|
f"/partition/{partition_r['id']}/group/create",
|
||||||
|
group_d,
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
assert group_r["edt_id"] == "GEDT"
|
||||||
|
# Edit edt_id
|
||||||
|
group_r = POST_JSON(
|
||||||
|
f"/group/{group_r['id']}/edit",
|
||||||
|
{"edt_id": "GEDT2"},
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
assert group_r["edt_id"] == "GEDT2"
|
||||||
|
partition = GET(f"/partition/{partition_r['id']}", headers=headers)
|
||||||
|
group = partition["groups"][str(group_r["id"])] # nb: str car clés json en string
|
||||||
|
assert group["group_name"] == group_d["group_name"]
|
||||||
|
assert group["edt_id"] == "GEDT2"
|
||||||
|
|
||||||
# Place un étudiant dans le groupe
|
# Place un étudiant dans le groupe
|
||||||
etud = GET(f"/formsemestre/{formsemestre_id}/etudiants", headers=headers)[0]
|
etud = GET(f"/formsemestre/{formsemestre_id}/etudiants", headers=headers)[0]
|
||||||
repl = POST_JSON(f"/group/{group['id']}/set_etudiant/{etud['id']}", headers=headers)
|
repl = POST_JSON(f"/group/{group['id']}/set_etudiant/{etud['id']}", headers=headers)
|
||||||
|
@ -88,15 +88,17 @@ def test_edit_users(api_admin_headers):
|
|||||||
# Change le dept et rend inactif
|
# Change le dept et rend inactif
|
||||||
user = POST_JSON(
|
user = POST_JSON(
|
||||||
f"/user/{user['id']}/edit",
|
f"/user/{user['id']}/edit",
|
||||||
{"active": False, "dept": "TAPI"},
|
{"active": False, "dept": "TAPI", "edt_id": "GGG"},
|
||||||
headers=admin_h,
|
headers=admin_h,
|
||||||
)
|
)
|
||||||
assert user["dept"] == "TAPI"
|
assert user["dept"] == "TAPI"
|
||||||
assert user["active"] is False
|
assert user["active"] is False
|
||||||
|
assert user["edt_id"] == "GGG"
|
||||||
user = GET(f"/user/{user['id']}", headers=admin_h)
|
user = GET(f"/user/{user['id']}", headers=admin_h)
|
||||||
assert user["nom"] == "Toto"
|
assert user["nom"] == "Toto"
|
||||||
assert user["dept"] == "TAPI"
|
assert user["dept"] == "TAPI"
|
||||||
assert user["active"] is False
|
assert user["active"] is False
|
||||||
|
assert user["edt_id"] == "GGG"
|
||||||
|
|
||||||
|
|
||||||
def test_roles(api_admin_headers):
|
def test_roles(api_admin_headers):
|
||||||
|
@ -123,3 +123,30 @@ def test_create_delete(test_client):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
ul = User.query.filter_by(prenom="Pierre").all()
|
ul = User.query.filter_by(prenom="Pierre").all()
|
||||||
assert len(ul) == 1
|
assert len(ul) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_edit(test_client):
|
||||||
|
"test edition object utlisateur"
|
||||||
|
args = {
|
||||||
|
"prenom": "No Totoro",
|
||||||
|
"edt_id": "totorito",
|
||||||
|
"cas_allow_login": 1, # boolean
|
||||||
|
"irrelevant": "..", # intentionnellement en dehors des attributs
|
||||||
|
}
|
||||||
|
u = User(user_name="Tonari")
|
||||||
|
u.from_dict(args)
|
||||||
|
db.session.add(u)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.refresh(u)
|
||||||
|
assert u.edt_id == "totorito"
|
||||||
|
assert u.nom == ""
|
||||||
|
assert u.cas_allow_login is True
|
||||||
|
d = u.to_dict()
|
||||||
|
assert d["nom"] == ""
|
||||||
|
args["cas_allow_login"] = 0
|
||||||
|
u.from_dict(args)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.refresh(u)
|
||||||
|
assert u.cas_allow_login is False
|
||||||
|
db.session.delete(u)
|
||||||
|
db.session.commit()
|
||||||
|
Loading…
Reference in New Issue
Block a user