forked from ScoDoc/ScoDoc
Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into edit_roles
This commit is contained in:
commit
db062d5b4a
@ -353,8 +353,8 @@ class User(UserMixin, db.Model):
|
|||||||
return mails
|
return mails
|
||||||
|
|
||||||
# Permissions management:
|
# Permissions management:
|
||||||
def has_permission(self, perm: int, dept=False):
|
def has_permission(self, perm: int, dept: str = False):
|
||||||
"""Check if user has permission `perm` in given `dept`.
|
"""Check if user has permission `perm` in given `dept` (acronym).
|
||||||
Similar to Zope ScoDoc7 `has_permission``
|
Similar to Zope ScoDoc7 `has_permission``
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -70,16 +70,11 @@ class Assiduite(db.Model):
|
|||||||
def to_dict(self, format_api=True) -> dict:
|
def to_dict(self, format_api=True) -> dict:
|
||||||
"""Retourne la représentation json de l'assiduité"""
|
"""Retourne la représentation json de l'assiduité"""
|
||||||
etat = self.etat
|
etat = self.etat
|
||||||
username = self.user_id
|
user: User = None
|
||||||
if format_api:
|
if format_api:
|
||||||
etat = EtatAssiduite.inverse().get(self.etat).name
|
etat = EtatAssiduite.inverse().get(self.etat).name
|
||||||
if self.user_id is not None:
|
if self.user_id is not None:
|
||||||
user: User = db.session.get(User, self.user_id)
|
user = db.session.get(User, self.user_id)
|
||||||
|
|
||||||
if user is None:
|
|
||||||
username = "Non renseigné"
|
|
||||||
else:
|
|
||||||
username = user.get_prenomnom()
|
|
||||||
data = {
|
data = {
|
||||||
"assiduite_id": self.id,
|
"assiduite_id": self.id,
|
||||||
"etudid": self.etudid,
|
"etudid": self.etudid,
|
||||||
@ -90,7 +85,8 @@ class Assiduite(db.Model):
|
|||||||
"etat": etat,
|
"etat": etat,
|
||||||
"desc": self.description,
|
"desc": self.description,
|
||||||
"entry_date": self.entry_date,
|
"entry_date": self.entry_date,
|
||||||
"user_id": username,
|
"user_id": None if user is None else user.id, # l'uid
|
||||||
|
"user_name": None if user is None else user.user_name, # le login
|
||||||
"est_just": self.est_just,
|
"est_just": self.est_just,
|
||||||
"external_data": self.external_data,
|
"external_data": self.external_data,
|
||||||
}
|
}
|
||||||
|
@ -80,8 +80,6 @@ class Departement(db.Model):
|
|||||||
|
|
||||||
def create_dept(acronym: str, visible=True) -> Departement:
|
def create_dept(acronym: str, visible=True) -> Departement:
|
||||||
"Create new departement"
|
"Create new departement"
|
||||||
from app.models import ScoPreference
|
|
||||||
|
|
||||||
if Departement.invalid_dept_acronym(acronym):
|
if Departement.invalid_dept_acronym(acronym):
|
||||||
raise ScoValueError("acronyme departement invalide")
|
raise ScoValueError("acronyme departement invalide")
|
||||||
existing = Departement.query.filter_by(acronym=acronym).count()
|
existing = Departement.query.filter_by(acronym=acronym).count()
|
||||||
|
@ -86,6 +86,50 @@ class Identite(db.Model):
|
|||||||
f"<Etud {self.id}/{self.departement.acronym} {self.nom!r} {self.prenom!r}>"
|
f"<Etud {self.id}/{self.departement.acronym} {self.nom!r} {self.prenom!r}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def clone(self, not_copying=(), new_dept_id: int = None):
|
||||||
|
"""Clone, not copying the given attrs
|
||||||
|
Clone aussi les adresses.
|
||||||
|
Si new_dept_id est None, le nouvel étudiant n'a pas de département.
|
||||||
|
Attention: la copie n'a pas d'id avant le prochain flush ou commit.
|
||||||
|
"""
|
||||||
|
if new_dept_id == self.dept_id:
|
||||||
|
raise ScoValueError(
|
||||||
|
"clonage étudiant: le département destination est identique à celui de départ"
|
||||||
|
)
|
||||||
|
# Vérifie les contraintes d'unicité
|
||||||
|
# ("dept_id", "code_nip") et ("dept_id", "code_ine")
|
||||||
|
if (
|
||||||
|
self.code_nip is not None
|
||||||
|
and Identite.query.filter_by(
|
||||||
|
dept_id=new_dept_id, code_nip=self.code_nip
|
||||||
|
).count()
|
||||||
|
> 0
|
||||||
|
) or (
|
||||||
|
self.code_ine is not None
|
||||||
|
and Identite.query.filter_by(
|
||||||
|
dept_id=new_dept_id, code_ine=self.code_ine
|
||||||
|
).count()
|
||||||
|
> 0
|
||||||
|
):
|
||||||
|
raise ScoValueError(
|
||||||
|
"""clonage étudiant: un étudiant de même code existe déjà
|
||||||
|
dans le département destination"""
|
||||||
|
)
|
||||||
|
d = dict(self.__dict__)
|
||||||
|
d.pop("id", None) # get rid of id
|
||||||
|
d.pop("_sa_instance_state", None) # get rid of SQLAlchemy special attr
|
||||||
|
d.pop("departement", None) # relationship
|
||||||
|
d["dept_id"] = new_dept_id
|
||||||
|
for k in not_copying:
|
||||||
|
d.pop(k, None)
|
||||||
|
copy = self.__class__(**d)
|
||||||
|
copy.adresses = [adr.clone() for adr in self.adresses]
|
||||||
|
db.session.add(copy)
|
||||||
|
log(
|
||||||
|
f"cloning etud <{self.id} {self.nom!r} {self.prenom!r}> in dept_id={new_dept_id}"
|
||||||
|
)
|
||||||
|
return copy
|
||||||
|
|
||||||
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="{
|
||||||
@ -660,6 +704,19 @@ class Adresse(db.Model):
|
|||||||
)
|
)
|
||||||
description = db.Column(db.Text)
|
description = db.Column(db.Text)
|
||||||
|
|
||||||
|
def clone(self, not_copying=()):
|
||||||
|
"""Clone, not copying the given attrs
|
||||||
|
Attention: la copie n'a pas d'id avant le prochain flush ou commit.
|
||||||
|
"""
|
||||||
|
d = dict(self.__dict__)
|
||||||
|
d.pop("id", None) # get rid of id
|
||||||
|
d.pop("_sa_instance_state", None) # get rid of SQLAlchemy special attr
|
||||||
|
for k in not_copying:
|
||||||
|
d.pop(k, None)
|
||||||
|
copy = self.__class__(**d)
|
||||||
|
db.session.add(copy)
|
||||||
|
return copy
|
||||||
|
|
||||||
def to_dict(self, convert_nulls_to_str=False):
|
def to_dict(self, convert_nulls_to_str=False):
|
||||||
"""Représentation dictionnaire,"""
|
"""Représentation dictionnaire,"""
|
||||||
e = dict(self.__dict__)
|
e = dict(self.__dict__)
|
||||||
|
@ -177,11 +177,15 @@ class FormSemestre(db.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_formsemestre(cls, formsemestre_id: int) -> "FormSemestre":
|
def get_formsemestre(
|
||||||
""" "FormSemestre ou 404, cherche uniquement dans le département courant"""
|
cls, formsemestre_id: int, dept_id: int = None
|
||||||
|
) -> "FormSemestre":
|
||||||
|
""" "FormSemestre ou 404, cherche uniquement dans le département spécifié ou le courant"""
|
||||||
if g.scodoc_dept:
|
if g.scodoc_dept:
|
||||||
|
dept_id = dept_id if dept_id is not None else g.scodoc_dept_id
|
||||||
|
if dept_id is not None:
|
||||||
return cls.query.filter_by(
|
return cls.query.filter_by(
|
||||||
id=formsemestre_id, dept_id=g.scodoc_dept_id
|
id=formsemestre_id, dept_id=dept_id
|
||||||
).first_or_404()
|
).first_or_404()
|
||||||
return cls.query.filter_by(id=formsemestre_id).first_or_404()
|
return cls.query.filter_by(id=formsemestre_id).first_or_404()
|
||||||
|
|
||||||
|
@ -237,7 +237,7 @@ class TF(object):
|
|||||||
|
|
||||||
def setdefaultvalues(self):
|
def setdefaultvalues(self):
|
||||||
"set default values and convert numbers to strings"
|
"set default values and convert numbers to strings"
|
||||||
for (field, descr) in self.formdescription:
|
for field, descr in self.formdescription:
|
||||||
# special case for boolcheckbox
|
# special case for boolcheckbox
|
||||||
if descr.get("input_type", None) == "boolcheckbox" and self.submitted():
|
if descr.get("input_type", None) == "boolcheckbox" and self.submitted():
|
||||||
if field not in self.values:
|
if field not in self.values:
|
||||||
@ -278,7 +278,7 @@ class TF(object):
|
|||||||
"check values. Store .result and returns msg"
|
"check values. Store .result and returns msg"
|
||||||
ok = 1
|
ok = 1
|
||||||
msg = []
|
msg = []
|
||||||
for (field, descr) in self.formdescription:
|
for field, descr in self.formdescription:
|
||||||
val = self.values[field]
|
val = self.values[field]
|
||||||
# do not check "unckecked" items
|
# do not check "unckecked" items
|
||||||
if descr.get("withcheckbox", False):
|
if descr.get("withcheckbox", False):
|
||||||
@ -287,7 +287,7 @@ class TF(object):
|
|||||||
# null values
|
# null values
|
||||||
allow_null = descr.get("allow_null", True)
|
allow_null = descr.get("allow_null", True)
|
||||||
if not allow_null:
|
if not allow_null:
|
||||||
if val == "" or val == None:
|
if val is None or (isinstance(val, str) and not val.strip()):
|
||||||
msg.append(
|
msg.append(
|
||||||
"Le champ '%s' doit être renseigné" % descr.get("title", field)
|
"Le champ '%s' doit être renseigné" % descr.get("title", field)
|
||||||
)
|
)
|
||||||
@ -871,7 +871,7 @@ var {field}_as = new bsn.AutoSuggest('{field}', {field}_opts);
|
|||||||
def _ReadOnlyVersion(self, formdescription):
|
def _ReadOnlyVersion(self, formdescription):
|
||||||
"Generate HTML for read-only view of the form"
|
"Generate HTML for read-only view of the form"
|
||||||
R = ['<table class="tf-ro">']
|
R = ['<table class="tf-ro">']
|
||||||
for (field, descr) in formdescription:
|
for field, descr in formdescription:
|
||||||
R.append(self._ReadOnlyElement(field, descr))
|
R.append(self._ReadOnlyElement(field, descr))
|
||||||
R.append("</table>")
|
R.append("</table>")
|
||||||
return R
|
return R
|
||||||
|
@ -19,26 +19,35 @@ class Trace:
|
|||||||
"""gestionnaire de la trace des fichiers justificatifs"""
|
"""gestionnaire de la trace des fichiers justificatifs"""
|
||||||
|
|
||||||
def __init__(self, path: str) -> None:
|
def __init__(self, path: str) -> None:
|
||||||
log(f"init Trace {path}")
|
|
||||||
self.path: str = path + "/_trace.csv"
|
self.path: str = path + "/_trace.csv"
|
||||||
self.content: dict[str, list[datetime, datetime, str]] = {}
|
self.content: dict[str, list[datetime, datetime, str]] = {}
|
||||||
self.import_from_file()
|
self.import_from_file()
|
||||||
|
|
||||||
def import_from_file(self):
|
def import_from_file(self):
|
||||||
"""import trace from file"""
|
"""import trace from file"""
|
||||||
if os.path.isfile(self.path):
|
|
||||||
with open(self.path, "r", encoding="utf-8") as file:
|
def import_from_csv(path):
|
||||||
|
with open(path, "r", encoding="utf-8") as file:
|
||||||
for line in file.readlines():
|
for line in file.readlines():
|
||||||
csv = line.split(",")
|
csv = line.split(",")
|
||||||
if len(csv) < 4:
|
if len(csv) < 4:
|
||||||
continue
|
continue
|
||||||
fname: str = csv[0]
|
fname: str = csv[0]
|
||||||
|
if fname not in os.listdir(self.path.replace("/_trace.csv", "")):
|
||||||
|
continue
|
||||||
entry_date: datetime = is_iso_formated(csv[1], True)
|
entry_date: datetime = is_iso_formated(csv[1], True)
|
||||||
delete_date: datetime = is_iso_formated(csv[2], True)
|
delete_date: datetime = is_iso_formated(csv[2], True)
|
||||||
user_id = csv[3]
|
user_id = csv[3]
|
||||||
|
|
||||||
self.content[fname] = [entry_date, delete_date, user_id]
|
self.content[fname] = [entry_date, delete_date, user_id]
|
||||||
|
|
||||||
|
if os.path.isfile(self.path):
|
||||||
|
import_from_csv(self.path)
|
||||||
|
else:
|
||||||
|
parent_dir: str = self.path[: self.path.rfind("/", 0, self.path.rfind("/"))]
|
||||||
|
if os.path.isfile(parent_dir + "/_trace.csv"):
|
||||||
|
import_from_csv(parent_dir + "/_trace.csv")
|
||||||
|
self.save_trace()
|
||||||
|
|
||||||
def set_trace(self, *fnames: str, mode: str = "entry", current_user: str = None):
|
def set_trace(self, *fnames: str, mode: str = "entry", current_user: str = None):
|
||||||
"""Ajoute une trace du fichier donné
|
"""Ajoute une trace du fichier donné
|
||||||
mode : entry / delete
|
mode : entry / delete
|
||||||
@ -57,9 +66,11 @@ class Trace:
|
|||||||
)
|
)
|
||||||
self.save_trace()
|
self.save_trace()
|
||||||
|
|
||||||
def save_trace(self):
|
def save_trace(self, new_path: str = None):
|
||||||
"""Enregistre la trace dans le fichier _trace.csv"""
|
"""Enregistre la trace dans le fichier _trace.csv"""
|
||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
|
if new_path is not None:
|
||||||
|
self.path = new_path
|
||||||
for fname, traced in self.content.items():
|
for fname, traced in self.content.items():
|
||||||
date_fin: datetime or None = traced[1].isoformat() if traced[1] else "None"
|
date_fin: datetime or None = traced[1].isoformat() if traced[1] else "None"
|
||||||
if traced[0] is not None:
|
if traced[0] is not None:
|
||||||
@ -126,7 +137,6 @@ class JustificatifArchiver(BaseArchiver):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fname: str = self.store(archive_id, filename, data, dept_id=etud.dept_id)
|
fname: str = self.store(archive_id, filename, data, dept_id=etud.dept_id)
|
||||||
log(f"obj_dir {self.get_obj_dir(etud.id, dept_id=etud.dept_id)} | {archive_id}")
|
|
||||||
trace = Trace(archive_id)
|
trace = Trace(archive_id)
|
||||||
trace.set_trace(fname, mode="entry")
|
trace.set_trace(fname, mode="entry")
|
||||||
if user_id is not None:
|
if user_id is not None:
|
||||||
|
@ -136,7 +136,7 @@ class WrapDict(object):
|
|||||||
try:
|
try:
|
||||||
value = self.dict[key]
|
value = self.dict[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise
|
return f"XXX {key} invalide XXX"
|
||||||
if value is None:
|
if value is None:
|
||||||
return self.none_value
|
return self.none_value
|
||||||
return value
|
return value
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
import flask
|
import flask
|
||||||
from flask import url_for, g
|
from flask import url_for, g
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ _evaluationEditor = ndb.EditableTable(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_evaluation_dict(args: dict) -> list[dict]:
|
def get_evaluations_dict(args: dict) -> list[dict]:
|
||||||
"""Liste evaluations, triées numero (or most recent date first).
|
"""Liste evaluations, triées numero (or most recent date first).
|
||||||
Fonction de transition pour ancien code ScoDoc7.
|
Fonction de transition pour ancien code ScoDoc7.
|
||||||
|
|
||||||
@ -83,7 +84,12 @@ def get_evaluation_dict(args: dict) -> list[dict]:
|
|||||||
'descrheure' : ' de 15h00 à 16h30'
|
'descrheure' : ' de 15h00 à 16h30'
|
||||||
"""
|
"""
|
||||||
# calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi
|
# calcule duree (chaine de car.) de chaque evaluation et ajoute jour_iso, matin, apresmidi
|
||||||
return [e.to_dict() for e in Evaluation.query.filter_by(**args)]
|
return [
|
||||||
|
e.to_dict()
|
||||||
|
for e in Evaluation.query.filter_by(**args).order_by(
|
||||||
|
sa.desc(Evaluation.numero), sa.desc(Evaluation.date_debut)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def do_evaluation_list_in_formsemestre(formsemestre_id):
|
def do_evaluation_list_in_formsemestre(formsemestre_id):
|
||||||
@ -91,7 +97,7 @@ def do_evaluation_list_in_formsemestre(formsemestre_id):
|
|||||||
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
evals = []
|
evals = []
|
||||||
for modimpl in mods:
|
for modimpl in mods:
|
||||||
evals += get_evaluation_dict(args={"moduleimpl_id": modimpl["moduleimpl_id"]})
|
evals += get_evaluations_dict(args={"moduleimpl_id": modimpl["moduleimpl_id"]})
|
||||||
return evals
|
return evals
|
||||||
|
|
||||||
|
|
||||||
@ -161,7 +167,6 @@ def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
|
|||||||
(published)
|
(published)
|
||||||
"""
|
"""
|
||||||
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
evaluation: Evaluation = Evaluation.query.get_or_404(evaluation_id)
|
||||||
moduleimpl_id = evaluation.moduleimpl_id
|
|
||||||
redirect = int(redirect)
|
redirect = int(redirect)
|
||||||
# access: can change eval ?
|
# access: can change eval ?
|
||||||
if not evaluation.moduleimpl.can_edit_evaluation(current_user):
|
if not evaluation.moduleimpl.can_edit_evaluation(current_user):
|
||||||
@ -171,12 +176,12 @@ def moduleimpl_evaluation_move(evaluation_id: int, after=0, redirect=1):
|
|||||||
Evaluation.moduleimpl_evaluation_renumber(
|
Evaluation.moduleimpl_evaluation_renumber(
|
||||||
evaluation.moduleimpl, only_if_unumbered=True
|
evaluation.moduleimpl, only_if_unumbered=True
|
||||||
)
|
)
|
||||||
e = get_evaluation_dict(args={"evaluation_id": evaluation_id})[0]
|
e = get_evaluations_dict(args={"evaluation_id": evaluation_id})[0]
|
||||||
|
|
||||||
after = int(after) # 0: deplace avant, 1 deplace apres
|
after = int(after) # 0: deplace avant, 1 deplace apres
|
||||||
if after not in (0, 1):
|
if after not in (0, 1):
|
||||||
raise ValueError('invalid value for "after"')
|
raise ValueError('invalid value for "after"')
|
||||||
mod_evals = get_evaluation_dict({"moduleimpl_id": e["moduleimpl_id"]})
|
mod_evals = get_evaluations_dict({"moduleimpl_id": e["moduleimpl_id"]})
|
||||||
if len(mod_evals) > 1:
|
if len(mod_evals) > 1:
|
||||||
idx = [p["evaluation_id"] for p in mod_evals].index(evaluation_id)
|
idx = [p["evaluation_id"] for p in mod_evals].index(evaluation_id)
|
||||||
neigh = None # object to swap with
|
neigh = None # object to swap with
|
||||||
|
@ -133,7 +133,7 @@ def do_evaluation_etat(
|
|||||||
) # { etudid : note }
|
) # { etudid : note }
|
||||||
|
|
||||||
# ---- Liste des groupes complets et incomplets
|
# ---- Liste des groupes complets et incomplets
|
||||||
E = sco_evaluation_db.get_evaluation_dict(args={"evaluation_id": evaluation_id})[0]
|
E = sco_evaluation_db.get_evaluations_dict(args={"evaluation_id": evaluation_id})[0]
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
Mod = sco_edit_module.module_list(args={"module_id": M["module_id"]})[0]
|
||||||
is_malus = Mod["module_type"] == ModuleType.MALUS # True si module de malus
|
is_malus = Mod["module_type"] == ModuleType.MALUS # True si module de malus
|
||||||
|
@ -1445,7 +1445,7 @@ def do_formsemestre_delete(formsemestre_id):
|
|||||||
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
mods = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
# evaluations
|
# evaluations
|
||||||
evals = sco_evaluation_db.get_evaluation_dict(
|
evals = sco_evaluation_db.get_evaluations_dict(
|
||||||
args={"moduleimpl_id": mod["moduleimpl_id"]}
|
args={"moduleimpl_id": mod["moduleimpl_id"]}
|
||||||
)
|
)
|
||||||
for e in evals:
|
for e in evals:
|
||||||
|
@ -275,14 +275,16 @@ def do_formsemestre_inscription_with_modules(
|
|||||||
etat=scu.INSCRIT,
|
etat=scu.INSCRIT,
|
||||||
etape=None,
|
etape=None,
|
||||||
method="inscription_with_modules",
|
method="inscription_with_modules",
|
||||||
|
dept_id: int = None,
|
||||||
):
|
):
|
||||||
"""Inscrit cet etudiant à ce semestre et TOUS ses modules STANDARDS
|
"""Inscrit cet etudiant à ce semestre et TOUS ses modules STANDARDS
|
||||||
(donc sauf le sport)
|
(donc sauf le sport)
|
||||||
|
Si dept_id est spécifié, utilise ce département au lieu du courant.
|
||||||
"""
|
"""
|
||||||
group_ids = group_ids or []
|
group_ids = group_ids or []
|
||||||
if isinstance(group_ids, int):
|
if isinstance(group_ids, int):
|
||||||
group_ids = [group_ids]
|
group_ids = [group_ids]
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id, dept_id=dept_id)
|
||||||
# inscription au semestre
|
# inscription au semestre
|
||||||
args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
|
args = {"formsemestre_id": formsemestre_id, "etudid": etudid}
|
||||||
if etat is not None:
|
if etat is not None:
|
||||||
|
@ -490,7 +490,7 @@ def retreive_formsemestre_from_request() -> int:
|
|||||||
modimpl = modimpl[0]
|
modimpl = modimpl[0]
|
||||||
formsemestre_id = modimpl["formsemestre_id"]
|
formsemestre_id = modimpl["formsemestre_id"]
|
||||||
elif "evaluation_id" in args:
|
elif "evaluation_id" in args:
|
||||||
E = sco_evaluation_db.get_evaluation_dict(
|
E = sco_evaluation_db.get_evaluations_dict(
|
||||||
{"evaluation_id": args["evaluation_id"]}
|
{"evaluation_id": args["evaluation_id"]}
|
||||||
)
|
)
|
||||||
if not E:
|
if not E:
|
||||||
@ -884,7 +884,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
|||||||
jour = datetime.date.today().isoformat(),
|
jour = datetime.date.today().isoformat(),
|
||||||
group_ids=group.id,
|
group_ids=group.id,
|
||||||
)}">
|
)}">
|
||||||
<button>Visualiser l'assiduité</button></a>
|
<button>Visualiser</button></a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn" href="{
|
<a class="btn" href="{
|
||||||
|
@ -69,10 +69,10 @@ def do_evaluation_listenotes(
|
|||||||
mode = None
|
mode = None
|
||||||
if moduleimpl_id:
|
if moduleimpl_id:
|
||||||
mode = "module"
|
mode = "module"
|
||||||
evals = sco_evaluation_db.get_evaluation_dict({"moduleimpl_id": moduleimpl_id})
|
evals = sco_evaluation_db.get_evaluations_dict({"moduleimpl_id": moduleimpl_id})
|
||||||
elif evaluation_id:
|
elif evaluation_id:
|
||||||
mode = "eval"
|
mode = "eval"
|
||||||
evals = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})
|
evals = sco_evaluation_db.get_evaluations_dict({"evaluation_id": evaluation_id})
|
||||||
else:
|
else:
|
||||||
raise ValueError("missing argument: evaluation or module")
|
raise ValueError("missing argument: evaluation or module")
|
||||||
if not evals:
|
if not evals:
|
||||||
|
@ -642,6 +642,12 @@ def menus_etud(etudid):
|
|||||||
"args": {"etudid": etud["etudid"]},
|
"args": {"etudid": etud["etudid"]},
|
||||||
"enabled": authuser.has_permission(Permission.ScoEtudInscrit),
|
"enabled": authuser.has_permission(Permission.ScoEtudInscrit),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Copier dans un autre département...",
|
||||||
|
"endpoint": "scolar.etud_copy_in_other_dept",
|
||||||
|
"args": {"etudid": etud["etudid"]},
|
||||||
|
"enabled": authuser.has_permission(Permission.ScoEtudInscrit),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Supprimer cet étudiant...",
|
"title": "Supprimer cet étudiant...",
|
||||||
"endpoint": "scolar.etudident_delete",
|
"endpoint": "scolar.etudident_delete",
|
||||||
@ -656,7 +662,9 @@ def menus_etud(etudid):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return htmlutils.make_menu("Étudiant", menuEtud, alone=True)
|
return htmlutils.make_menu(
|
||||||
|
"Étudiant", menuEtud, alone=True, css_class="menu-etudiant"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def etud_info_html(etudid, with_photo="1", debug=False):
|
def etud_info_html(etudid, with_photo="1", debug=False):
|
||||||
|
@ -138,7 +138,7 @@ class PlacementForm(FlaskForm):
|
|||||||
|
|
||||||
def set_evaluation_infos(self, evaluation_id):
|
def set_evaluation_infos(self, evaluation_id):
|
||||||
"""Initialise les données du formulaire avec les données de l'évaluation."""
|
"""Initialise les données du formulaire avec les données de l'évaluation."""
|
||||||
eval_data = sco_evaluation_db.get_evaluation_dict(
|
eval_data = sco_evaluation_db.get_evaluations_dict(
|
||||||
{"evaluation_id": evaluation_id}
|
{"evaluation_id": evaluation_id}
|
||||||
)
|
)
|
||||||
if not eval_data:
|
if not eval_data:
|
||||||
@ -239,7 +239,7 @@ class PlacementRunner:
|
|||||||
self.groups_ids = [
|
self.groups_ids = [
|
||||||
gid if gid != TOUS else form.tous_id for gid in form["groups"].data
|
gid if gid != TOUS else form.tous_id for gid in form["groups"].data
|
||||||
]
|
]
|
||||||
self.eval_data = sco_evaluation_db.get_evaluation_dict(
|
self.eval_data = sco_evaluation_db.get_evaluations_dict(
|
||||||
{"evaluation_id": self.evaluation_id}
|
{"evaluation_id": self.evaluation_id}
|
||||||
)[0]
|
)[0]
|
||||||
self.groups = sco_groups.listgroups(self.groups_ids)
|
self.groups = sco_groups.listgroups(self.groups_ids)
|
||||||
|
@ -524,11 +524,11 @@ def table_suivi_cohorte(
|
|||||||
# 3-- Regroupe les semestres par date de debut
|
# 3-- Regroupe les semestres par date de debut
|
||||||
P = [] # liste de periodsem
|
P = [] # liste de periodsem
|
||||||
|
|
||||||
class periodsem(object):
|
class PeriodSem:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# semestre de depart:
|
# semestre de depart:
|
||||||
porigin = periodsem()
|
porigin = PeriodSem()
|
||||||
d, m, y = [int(x) for x in sem["date_debut"].split("/")]
|
d, m, y = [int(x) for x in sem["date_debut"].split("/")]
|
||||||
porigin.datedebut = datetime.datetime(y, m, d)
|
porigin.datedebut = datetime.datetime(y, m, d)
|
||||||
porigin.sems = [sem]
|
porigin.sems = [sem]
|
||||||
@ -543,7 +543,7 @@ def table_suivi_cohorte(
|
|||||||
merged = True
|
merged = True
|
||||||
break
|
break
|
||||||
if not merged:
|
if not merged:
|
||||||
p = periodsem()
|
p = PeriodSem()
|
||||||
p.datedebut = s["date_debut_dt"]
|
p.datedebut = s["date_debut_dt"]
|
||||||
p.sems = [s]
|
p.sems = [s]
|
||||||
P.append(p)
|
P.append(p)
|
||||||
@ -743,7 +743,7 @@ def formsemestre_suivi_cohorte(
|
|||||||
civilite=None,
|
civilite=None,
|
||||||
statut="",
|
statut="",
|
||||||
only_primo=False,
|
only_primo=False,
|
||||||
):
|
) -> str:
|
||||||
"""Affiche suivi cohortes par numero de semestre"""
|
"""Affiche suivi cohortes par numero de semestre"""
|
||||||
annee_bac = str(annee_bac or "")
|
annee_bac = str(annee_bac or "")
|
||||||
annee_admission = str(annee_admission or "")
|
annee_admission = str(annee_admission or "")
|
||||||
@ -794,14 +794,6 @@ def formsemestre_suivi_cohorte(
|
|||||||
'<p><a href="%s&percent=1">Afficher les résultats en pourcentages</a></p>'
|
'<p><a href="%s&percent=1">Afficher les résultats en pourcentages</a></p>'
|
||||||
% burl
|
% burl
|
||||||
)
|
)
|
||||||
help = (
|
|
||||||
pplink
|
|
||||||
+ """
|
|
||||||
<p class="help">Nombre d'étudiants dans chaque semestre. Les dates indiquées sont les dates approximatives de <b>début</b> des semestres (les semestres commençant à des dates proches sont groupés). Le nombre de diplômés est celui à la <b>fin</b> du semestre correspondant. Lorsqu'il y a moins de %s étudiants dans une case, vous pouvez afficher leurs noms en passant le curseur sur le chiffre.</p>
|
|
||||||
<p class="help">Les menus permettent de n'étudier que certaines catégories d'étudiants (titulaires d'un type de bac, garçons ou filles). La case "restreindre aux primo-entrants" permet de ne considérer que les étudiants qui n'ont jamais été inscrits dans ScoDoc avant le semestre considéré.</p>
|
|
||||||
"""
|
|
||||||
% (MAX_ETUD_IN_DESCR,)
|
|
||||||
)
|
|
||||||
|
|
||||||
H = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title=tab.page_title),
|
html_sco_header.sco_header(page_title=tab.page_title),
|
||||||
@ -824,7 +816,20 @@ def formsemestre_suivi_cohorte(
|
|||||||
percent=percent,
|
percent=percent,
|
||||||
),
|
),
|
||||||
t,
|
t,
|
||||||
help,
|
f"""{pplink}
|
||||||
|
<p class="help">Nombre d'étudiants dans chaque semestre.
|
||||||
|
Les dates indiquées sont les dates approximatives de <b>début</b> des semestres
|
||||||
|
(les semestres commençant à des dates proches sont groupés). Le nombre de diplômés
|
||||||
|
est celui à la <b>fin</b> du semestre correspondant.
|
||||||
|
Lorsqu'il y a moins de {MAX_ETUD_IN_DESCR} étudiants dans une case, vous pouvez
|
||||||
|
afficher leurs noms en passant le curseur sur le chiffre.
|
||||||
|
</p>
|
||||||
|
<p class="help">Les menus permettent de n'étudier que certaines catégories
|
||||||
|
d'étudiants (titulaires d'un type de bac, garçons ou filles).
|
||||||
|
La case "restreindre aux primo-entrants" permet de ne considérer que les étudiants
|
||||||
|
qui n'ont jamais été inscrits dans ScoDoc avant le semestre considéré.
|
||||||
|
</p>
|
||||||
|
""",
|
||||||
expl,
|
expl,
|
||||||
html_sco_header.sco_footer(),
|
html_sco_header.sco_footer(),
|
||||||
]
|
]
|
||||||
@ -870,35 +875,33 @@ def _gen_form_selectetuds(
|
|||||||
else:
|
else:
|
||||||
selected = 'selected="selected"'
|
selected = 'selected="selected"'
|
||||||
F = [
|
F = [
|
||||||
"""<form id="f" method="get" action="%s">
|
f"""<form id="f" method="get" action="{request.base_url}">
|
||||||
<p>Bac: <select name="bac" onchange="javascript: submit(this);">
|
<p>Bac: <select name="bac" onchange="javascript: submit(this);">
|
||||||
<option value="" %s>tous</option>
|
<option value="" {selected}>tous</option>
|
||||||
"""
|
"""
|
||||||
% (request.base_url, selected)
|
|
||||||
]
|
]
|
||||||
for b in bacs:
|
for b in bacs:
|
||||||
if bac == b:
|
if bac == b:
|
||||||
selected = 'selected="selected"'
|
selected = 'selected="selected"'
|
||||||
else:
|
else:
|
||||||
selected = ""
|
selected = ""
|
||||||
F.append('<option value="%s" %s>%s</option>' % (b, selected, b))
|
F.append(f'<option value="{b}" {selected}>{b}</option>')
|
||||||
F.append("</select>")
|
F.append("</select>")
|
||||||
if bacspecialite:
|
if bacspecialite:
|
||||||
selected = ""
|
selected = ""
|
||||||
else:
|
else:
|
||||||
selected = 'selected="selected"'
|
selected = 'selected="selected"'
|
||||||
F.append(
|
F.append(
|
||||||
""" Bac/Specialité: <select name="bacspecialite" onchange="javascript: submit(this);">
|
f""" Bac/Specialité: <select name="bacspecialite" onchange="javascript: submit(this);">
|
||||||
<option value="" %s>tous</option>
|
<option value="" {selected}>tous</option>
|
||||||
"""
|
"""
|
||||||
% selected
|
|
||||||
)
|
)
|
||||||
for b in bacspecialites:
|
for b in bacspecialites:
|
||||||
if bacspecialite == b:
|
if bacspecialite == b:
|
||||||
selected = 'selected="selected"'
|
selected = 'selected="selected"'
|
||||||
else:
|
else:
|
||||||
selected = ""
|
selected = ""
|
||||||
F.append('<option value="%s" %s>%s</option>' % (b, selected, b))
|
F.append(f'<option value="{b}" {selected}>{b}</option>')
|
||||||
F.append("</select>")
|
F.append("</select>")
|
||||||
#
|
#
|
||||||
F.append(
|
F.append(
|
||||||
@ -910,46 +913,44 @@ def _gen_form_selectetuds(
|
|||||||
)
|
)
|
||||||
#
|
#
|
||||||
F.append(
|
F.append(
|
||||||
""" Genre: <select name="civilite" onchange="javascript: submit(this);">
|
f""" Genre: <select name="civilite" onchange="javascript: submit(this);">
|
||||||
<option value="" %s>tous</option>
|
<option value="" {selected}>tous</option>
|
||||||
"""
|
"""
|
||||||
% selected
|
|
||||||
)
|
)
|
||||||
for b in civilites:
|
for b in civilites:
|
||||||
if civilite == b:
|
if civilite == b:
|
||||||
selected = 'selected="selected"'
|
selected = 'selected="selected"'
|
||||||
else:
|
else:
|
||||||
selected = ""
|
selected = ""
|
||||||
F.append('<option value="%s" %s>%s</option>' % (b, selected, b))
|
F.append(f'<option value="{b}" {selected}>{b}</option>')
|
||||||
F.append("</select>")
|
F.append("</select>")
|
||||||
|
|
||||||
F.append(
|
F.append(
|
||||||
""" Statut: <select name="statut" onchange="javascript: submit(this);">
|
f""" Statut: <select name="statut" onchange="javascript: submit(this);">
|
||||||
<option value="" %s>tous</option>
|
<option value="" {selected}>tous</option>
|
||||||
"""
|
"""
|
||||||
% selected
|
|
||||||
)
|
)
|
||||||
for b in statuts:
|
for b in statuts:
|
||||||
if statut == b:
|
if statut == b:
|
||||||
selected = 'selected="selected"'
|
selected = 'selected="selected"'
|
||||||
else:
|
else:
|
||||||
selected = ""
|
selected = ""
|
||||||
F.append('<option value="%s" %s>%s</option>' % (b, selected, b))
|
F.append(f'<option value="{b}" {selected}>{b}</option>')
|
||||||
F.append("</select>")
|
F.append("</select>")
|
||||||
|
|
||||||
if only_primo:
|
|
||||||
checked = 'checked="1"'
|
|
||||||
else:
|
|
||||||
checked = ""
|
|
||||||
F.append(
|
F.append(
|
||||||
'<br><input type="checkbox" name="only_primo" onchange="javascript: submit(this);" %s/>Restreindre aux primo-entrants'
|
f"""<br>
|
||||||
% checked
|
<input type="checkbox" name="only_primo"
|
||||||
|
onchange="javascript: submit(this);"
|
||||||
|
{'checked="1"' if only_primo else ""}/>Restreindre aux primo-entrants
|
||||||
|
<input type="hidden" name="formsemestre_id" value="{formsemestre_id}"/>
|
||||||
|
<input type="hidden" name="percent" value="{percent}"/>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
F.append(
|
|
||||||
'<input type="hidden" name="formsemestre_id" value="%s"/>' % formsemestre_id
|
|
||||||
)
|
|
||||||
F.append('<input type="hidden" name="percent" value="%s"/>' % percent)
|
|
||||||
F.append("</p></form>")
|
|
||||||
return "\n".join(F)
|
return "\n".join(F)
|
||||||
|
|
||||||
|
|
||||||
@ -964,7 +965,7 @@ def _gen_select_annee(field, values, value) -> str:
|
|||||||
return menu_html + "</select>"
|
return menu_html + "</select>"
|
||||||
|
|
||||||
|
|
||||||
def _descr_etud_set(etudids):
|
def _descr_etud_set(etudids) -> str:
|
||||||
"textual html description of a set of etudids"
|
"textual html description of a set of etudids"
|
||||||
etuds = []
|
etuds = []
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
@ -980,12 +981,19 @@ def _count_dem_reo(formsemestre_id, etudids):
|
|||||||
"count nb of demissions and reorientation in this etud set"
|
"count nb of demissions and reorientation in this etud set"
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
|
validations_annuelles = nt.get_validations_annee() if nt.is_apc else {}
|
||||||
dems = set()
|
dems = set()
|
||||||
reos = set()
|
reos = set()
|
||||||
for etudid in etudids:
|
for etudid in etudids:
|
||||||
if nt.get_etud_etat(etudid) == "D":
|
if nt.get_etud_etat(etudid) == "D":
|
||||||
dems.add(etudid)
|
dems.add(etudid)
|
||||||
|
if nt.is_apc:
|
||||||
|
# BUT: utilise les validations annuelles
|
||||||
|
validation = validations_annuelles.get(etudid)
|
||||||
|
if validation and validation.code in codes_cursus.CODES_SEM_REO:
|
||||||
|
reos.add(etudid)
|
||||||
|
else:
|
||||||
|
# Autres formations: validations de semestres
|
||||||
dec = nt.get_etud_decision_sem(etudid)
|
dec = nt.get_etud_decision_sem(etudid)
|
||||||
if dec and dec["code"] in codes_cursus.CODES_SEM_REO:
|
if dec and dec["code"] in codes_cursus.CODES_SEM_REO:
|
||||||
reos.add(etudid)
|
reos.add(etudid)
|
||||||
|
@ -149,7 +149,7 @@ def list_operations(evaluation_id):
|
|||||||
|
|
||||||
def evaluation_list_operations(evaluation_id):
|
def evaluation_list_operations(evaluation_id):
|
||||||
"""Page listing operations on evaluation"""
|
"""Page listing operations on evaluation"""
|
||||||
E = sco_evaluation_db.get_evaluation_dict({"evaluation_id": evaluation_id})[0]
|
E = sco_evaluation_db.get_evaluations_dict({"evaluation_id": evaluation_id})[0]
|
||||||
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
M = sco_moduleimpl.moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
|
||||||
|
|
||||||
Ops = list_operations(evaluation_id)
|
Ops = list_operations(evaluation_id)
|
||||||
|
@ -10,12 +10,12 @@
|
|||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
|
||||||
background-color: var(--sco-color-background);
|
background-color: var(--sco-color-background);
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
@ -24,6 +24,10 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.container {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
h3 {
|
h3 {
|
||||||
@ -1672,6 +1676,10 @@ formsemestre_page_title .lock img {
|
|||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-etudiant>li {
|
||||||
|
width: 200px !important;
|
||||||
|
}
|
||||||
|
|
||||||
span.inscr_addremove_menu {
|
span.inscr_addremove_menu {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
@ -219,20 +219,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dayOnly() {
|
function dayOnly() {
|
||||||
|
const { deb, fin } = getDates();
|
||||||
|
|
||||||
if (document.getElementById('justi_journee').checked) {
|
if (document.getElementById('justi_journee').checked) {
|
||||||
document.getElementById("justi_date_debut").type = "date"
|
document.getElementById("justi_date_debut").type = "date"
|
||||||
|
document.getElementById("justi_date_debut").value = deb.slice(0, deb.indexOf('T'))
|
||||||
|
|
||||||
document.getElementById("justi_date_fin").type = "date"
|
document.getElementById("justi_date_fin").type = "date"
|
||||||
|
document.getElementById("justi_date_fin").value = fin.slice(0, fin.indexOf('T'))
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("justi_date_debut").type = "datetime-local"
|
document.getElementById("justi_date_debut").type = "datetime-local"
|
||||||
|
document.getElementById("justi_date_debut").value = `${deb}T${assi_morning}`
|
||||||
|
|
||||||
document.getElementById("justi_date_fin").type = "datetime-local"
|
document.getElementById("justi_date_fin").type = "datetime-local"
|
||||||
|
document.getElementById("justi_date_fin").value = `${fin}T${assi_evening}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDates() {
|
function getDates() {
|
||||||
if (document.querySelector('.page #justi_journee').checked) {
|
if (document.querySelector('.page #justi_journee').checked) {
|
||||||
const date_str_deb = document.querySelector(".page #justi_date_debut").value
|
const date_str_deb = document.querySelector(".page #justi_date_debut").value
|
||||||
const date_str_fin = document.querySelector(".page #justi_date_debut").value
|
const date_str_fin = document.querySelector(".page #justi_date_fin").value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
99
app/templates/scolar/etud_copy_in_other_dept.j2
Normal file
99
app/templates/scolar/etud_copy_in_other_dept.j2
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
{# -*- mode: jinja-html -*- #}
|
||||||
|
{% extends 'base.j2' %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{super()}}
|
||||||
|
<style>
|
||||||
|
.dept-name {
|
||||||
|
font-size: 120%;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.dept {
|
||||||
|
background-color: bisque;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.dept label {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
button[name="action"] {
|
||||||
|
margin-right: 32px;
|
||||||
|
}
|
||||||
|
#submit-button:disabled {
|
||||||
|
background-color: #CCCCCC;
|
||||||
|
color: #888888;
|
||||||
|
cursor: not-allowed;
|
||||||
|
border: 1px solid #AAAAAA;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
|
||||||
|
<h2>Création d'une copie de {{ etud.html_link_fiche() | safe }}</h2>
|
||||||
|
|
||||||
|
<div class="help">
|
||||||
|
|
||||||
|
<p>Utiliser cette page lorsqu'un étudinat change de département. ScoDoc gère
|
||||||
|
séparéement les étudiants des départements. Il faut donc dans ce cas
|
||||||
|
exceptionnel créer une copie de l'étudiant et l'inscrire dans un semestre de son
|
||||||
|
nouveau département. Seules les donénes sur l'identité de l'étudiant (état
|
||||||
|
civil, adresse, ...) sont dupliquées. Dans le noveau département, les résultats
|
||||||
|
obtenus dans le département d'origine ne seront pas visibles.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Si des UEs ou compétences de l'ancien département doivent être validées dans
|
||||||
|
le nouveau, il faudra utiliser ensuite une "validation d'UE antérieure".
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Attention: seuls les départements dans lesquels vous avez la permission
|
||||||
|
d'inscrire des étudiants sont présentés ici. Il faudra peut-être solliciter
|
||||||
|
l'administrateur de ce ScoDoc.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Dans chaque département autorisés, seuls les semestres non verrouillés sont
|
||||||
|
montrés. Choisir le semestre destination et valider le formulaire.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Ensuite, ne pas oublier d'inscrire l'étudiant à ses groupes, notamment son
|
||||||
|
parcours si besoin.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{% for dept in departements.values() %}
|
||||||
|
<div class="dept">
|
||||||
|
<div class="dept-name">Département {{ dept.acronym }}</div>
|
||||||
|
{% for sem in formsemestres_by_dept[dept.id]%}
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input type="radio" class="formsemestre" name="formsemestre_id" value="{{ sem.id }}">
|
||||||
|
{{ sem.html_link_status() | safe }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<button type="submit" name="action" value="submit" disabled id="submit-button">Créer une copie de l'étudiant et l'inscrire au semestre choisi</button>
|
||||||
|
<button type="submit" name="action" value="cancel">Annuler</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const radioButtons = document.querySelectorAll('input.formsemestre');
|
||||||
|
const submitButton = document.getElementById('submit-button');
|
||||||
|
|
||||||
|
radioButtons.forEach(radioButton => {
|
||||||
|
radioButton.addEventListener('change', () => {
|
||||||
|
const isAnyRadioButtonChecked = [...radioButtons].some(radioButton => radioButton.checked);
|
||||||
|
if (isAnyRadioButtonChecked) {
|
||||||
|
submitButton.removeAttribute('disabled');
|
||||||
|
} else {
|
||||||
|
submitButton.setAttribute('disabled', 'disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -517,7 +517,7 @@ def ajout_justificatif_etud():
|
|||||||
dept_id=g.scodoc_dept_id,
|
dept_id=g.scodoc_dept_id,
|
||||||
),
|
),
|
||||||
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
|
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
|
||||||
assi_evening=ScoDocSiteConfig.get("assi_evening_time", "18:00"),
|
assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"),
|
||||||
),
|
),
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
@ -1129,7 +1129,7 @@ def signal_assiduites_diff():
|
|||||||
defdem=_get_etuds_dem_def(formsemestre),
|
defdem=_get_etuds_dem_def(formsemestre),
|
||||||
timeMorning=ScoDocSiteConfig.get("assi_morning_time", "08:00:00"),
|
timeMorning=ScoDocSiteConfig.get("assi_morning_time", "08:00:00"),
|
||||||
timeNoon=ScoDocSiteConfig.get("assi_lunch_time", "13:00:00"),
|
timeNoon=ScoDocSiteConfig.get("assi_lunch_time", "13:00:00"),
|
||||||
timeEvening=ScoDocSiteConfig.get("assi_evening_time", "18:00:00"),
|
timeEvening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00:00"),
|
||||||
defaultDates=_get_days_between_dates(date_deb, date_fin),
|
defaultDates=_get_days_between_dates(date_deb, date_fin),
|
||||||
nonworkdays=_non_work_days(),
|
nonworkdays=_non_work_days(),
|
||||||
),
|
),
|
||||||
|
@ -407,7 +407,6 @@ def moduleimpl_evaluation_renumber(moduleimpl_id):
|
|||||||
)
|
)
|
||||||
Evaluation.moduleimpl_evaluation_renumber(modimpl)
|
Evaluation.moduleimpl_evaluation_renumber(modimpl)
|
||||||
# redirect to moduleimpl page:
|
# redirect to moduleimpl page:
|
||||||
if redirect:
|
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"notes.moduleimpl_status",
|
"notes.moduleimpl_status",
|
||||||
|
@ -31,11 +31,12 @@ issu de ScoDoc7 / ZScolar.py
|
|||||||
Emmanuel Viennet, 2021
|
Emmanuel Viennet, 2021
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import requests
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import url_for, flash, render_template, make_response
|
from flask import abort, flash, make_response, render_template, url_for
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask_json import as_json
|
from flask_json import as_json
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@ -43,6 +44,7 @@ from flask_wtf import FlaskForm
|
|||||||
from flask_wtf.file import FileField, FileAllowed
|
from flask_wtf.file import FileField, FileAllowed
|
||||||
from wtforms import SubmitField
|
from wtforms import SubmitField
|
||||||
|
|
||||||
|
import app
|
||||||
from app import db
|
from app import db
|
||||||
from app import log
|
from app import log
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
@ -52,6 +54,7 @@ from app.decorators import (
|
|||||||
permission_required_compat_scodoc7,
|
permission_required_compat_scodoc7,
|
||||||
)
|
)
|
||||||
from app.models import (
|
from app.models import (
|
||||||
|
Departement,
|
||||||
FormSemestre,
|
FormSemestre,
|
||||||
Identite,
|
Identite,
|
||||||
Partition,
|
Partition,
|
||||||
@ -69,6 +72,7 @@ from app.scodoc.scolog import logdb
|
|||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc.sco_exceptions import (
|
from app.scodoc.sco_exceptions import (
|
||||||
AccessDenied,
|
AccessDenied,
|
||||||
|
ScoPermissionDenied,
|
||||||
ScoValueError,
|
ScoValueError,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1770,6 +1774,77 @@ def _etudident_create_or_edit_form(edit):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/etud_copy_in_other_dept/<int:etudid>", methods=["GET", "POST"])
|
||||||
|
@scodoc
|
||||||
|
@permission_required(
|
||||||
|
Permission.ScoView
|
||||||
|
) # il faut aussi ScoEtudInscrit dans le nouveau dept
|
||||||
|
def etud_copy_in_other_dept(etudid: int):
|
||||||
|
"""Crée une copie de l'étudiant (avec ses adresses et codes) dans un autre département
|
||||||
|
et l'inscrit à un formsemestre
|
||||||
|
"""
|
||||||
|
etud = Identite.get_etud(etudid)
|
||||||
|
if request.method == "POST":
|
||||||
|
action = request.form.get("action")
|
||||||
|
if action == "cancel":
|
||||||
|
return flask.redirect(
|
||||||
|
url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
formsemestre_id = int(request.form.get("formsemestre_id"))
|
||||||
|
except ValueError:
|
||||||
|
log("etud_copy_in_other_dept: invalid formsemestre_id")
|
||||||
|
abort(404, description="formsemestre_id invalide")
|
||||||
|
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
|
||||||
|
if not current_user.has_permission(
|
||||||
|
Permission.ScoEtudInscrit, formsemestre.departement.acronym
|
||||||
|
):
|
||||||
|
raise ScoPermissionDenied("non autorisé")
|
||||||
|
new_etud = etud.clone(new_dept_id=formsemestre.dept_id)
|
||||||
|
db.session.commit()
|
||||||
|
# Attention: change le département pour opérer dans le nouveau
|
||||||
|
# avec les anciennes fonctions ScoDoc7
|
||||||
|
orig_dept = g.scodoc_dept
|
||||||
|
try:
|
||||||
|
app.set_sco_dept(formsemestre.departement.acronym, open_cnx=False)
|
||||||
|
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
|
||||||
|
formsemestre.id,
|
||||||
|
new_etud.id,
|
||||||
|
method="etud_copy_in_other_dept",
|
||||||
|
dept_id=formsemestre.dept_id,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
app.set_sco_dept(orig_dept, open_cnx=False)
|
||||||
|
flash(f"Etudiant dupliqué et inscrit en {formsemestre.departement.acronym}")
|
||||||
|
# Attention, ce redirect change de département !
|
||||||
|
return flask.redirect(
|
||||||
|
url_for(
|
||||||
|
"scolar.ficheEtud",
|
||||||
|
scodoc_dept=formsemestre.departement.acronym,
|
||||||
|
etudid=new_etud.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
departements = {
|
||||||
|
dept.id: dept
|
||||||
|
for dept in Departement.query.order_by(Departement.acronym)
|
||||||
|
if current_user.has_permission(Permission.ScoEtudInscrit, dept.acronym)
|
||||||
|
and dept.id != etud.dept_id
|
||||||
|
}
|
||||||
|
formsemestres_by_dept = {
|
||||||
|
dept.id: dept.formsemestres.filter_by(etat=True)
|
||||||
|
.filter(FormSemestre.modalite != "EXT")
|
||||||
|
.order_by(FormSemestre.date_debut, FormSemestre.semestre_id)
|
||||||
|
.all()
|
||||||
|
for dept in departements.values()
|
||||||
|
}
|
||||||
|
return render_template(
|
||||||
|
"scolar/etud_copy_in_other_dept.j2",
|
||||||
|
departements=departements,
|
||||||
|
etud=etud,
|
||||||
|
formsemestres_by_dept=formsemestres_by_dept,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/etudident_delete", methods=["GET", "POST"])
|
@bp.route("/etudident_delete", methods=["GET", "POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoEtudInscrit)
|
@permission_required(Permission.ScoEtudInscrit)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.6.35"
|
SCOVERSION = "9.6.38"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ def check_fields(data: dict, fields: dict = None):
|
|||||||
"""
|
"""
|
||||||
assert set(data.keys()) == set(fields.keys())
|
assert set(data.keys()) == set(fields.keys())
|
||||||
for key in data:
|
for key in data:
|
||||||
if key in ("moduleimpl_id", "desc", "user_id", "external_data"):
|
if key in ("moduleimpl_id", "desc", "external_data"):
|
||||||
assert (
|
assert (
|
||||||
isinstance(data[key], fields[key]) or data[key] is None
|
isinstance(data[key], fields[key]) or data[key] is None
|
||||||
), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"
|
), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"
|
||||||
|
@ -6,6 +6,7 @@ Ecrit par HARTMANN Matthias
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from random import randint
|
from random import randint
|
||||||
|
from types import NoneType
|
||||||
|
|
||||||
from tests.api.setup_test_api import (
|
from tests.api.setup_test_api import (
|
||||||
GET,
|
GET,
|
||||||
@ -34,7 +35,8 @@ ASSIDUITES_FIELDS = {
|
|||||||
"etat": str,
|
"etat": str,
|
||||||
"desc": str,
|
"desc": str,
|
||||||
"entry_date": str,
|
"entry_date": str,
|
||||||
"user_id": str,
|
"user_id": (int, NoneType),
|
||||||
|
"user_name": (str, NoneType),
|
||||||
"est_just": bool,
|
"est_just": bool,
|
||||||
"external_data": dict,
|
"external_data": dict,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user