forked from ScoDoc/ScoDoc
Clonage semestre: améliore code + qq modifs cosmétiques
This commit is contained in:
parent
07c2f00277
commit
8a49d99292
@ -36,6 +36,7 @@ from app.models.config import ScoDocSiteConfig
|
||||
from app.models.departements import Departement
|
||||
from app.models.etudiants import Identite
|
||||
from app.models.evaluations import Evaluation
|
||||
from app.models.events import ScolarNews
|
||||
from app.models.formations import Formation
|
||||
from app.models.groups import GroupDescr, Partition
|
||||
from app.models.moduleimpls import (
|
||||
@ -207,6 +208,70 @@ class FormSemestre(models.ScoDocModel):
|
||||
).first_or_404()
|
||||
return cls.query.filter_by(id=formsemestre_id).first_or_404()
|
||||
|
||||
@classmethod
|
||||
def create_formsemestre(cls, args: dict, silent=False) -> "FormSemestre":
|
||||
"""Création d'un formsemestre, avec toutes les valeurs par défaut
|
||||
et notification (sauf si silent).
|
||||
Crée la partition par défaut.
|
||||
"""
|
||||
# was sco_formsemestre.do_formsemestre_create
|
||||
if "dept_id" not in args:
|
||||
args["dept_id"] = g.scodoc_dept_id
|
||||
formsemestre: "FormSemestre" = cls.create_from_dict(args)
|
||||
db.session.flush()
|
||||
for etape in args["etapes"]:
|
||||
formsemestre.add_etape(etape)
|
||||
db.session.commit()
|
||||
for u in args["responsables"]:
|
||||
formsemestre.responsables.append(u)
|
||||
# create default partition
|
||||
partition = Partition(
|
||||
formsemestre=formsemestre, partition_name=None, numero=1000000
|
||||
)
|
||||
db.session.add(partition)
|
||||
partition.create_group(default=True)
|
||||
db.session.commit()
|
||||
|
||||
if not silent:
|
||||
url = url_for(
|
||||
"notes.formsemestre_status",
|
||||
scodoc_dept=formsemestre.departement.acronym,
|
||||
formsemestre_id=formsemestre.id,
|
||||
)
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_SEM,
|
||||
text=f"""Création du semestre <a href="{url}">{formsemestre.titre}</a>""",
|
||||
url=url,
|
||||
max_frequency=0,
|
||||
)
|
||||
|
||||
return formsemestre
|
||||
|
||||
@classmethod
|
||||
def convert_dict_fields(cls, args: dict) -> dict:
|
||||
"""Convert fields in the given dict.
|
||||
args: dict with args in application.
|
||||
returns: dict to store in model's db.
|
||||
"""
|
||||
if "date_debut" in args:
|
||||
args["date_debut"] = scu.convert_fr_date(args["date_debut"])
|
||||
if "date_fin" in args:
|
||||
args["date_fin"] = scu.convert_fr_date(args["date_debut"])
|
||||
if "etat" in args:
|
||||
args["etat"] = bool(args["etat"])
|
||||
if "bul_bgcolor" in args:
|
||||
args["bul_bgcolor"] = args.get("bul_bgcolor") or "white"
|
||||
if "titre" in args:
|
||||
args["titre"] = args.get("titre") or "sans titre"
|
||||
return args
|
||||
|
||||
@classmethod
|
||||
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.
|
||||
Add 'etapes' to excluded."""
|
||||
# on ne peut pas affecter directement etapes
|
||||
return super().filter_model_attributes(data, (excluded or set()) | {"etapes"})
|
||||
|
||||
def sort_key(self) -> tuple:
|
||||
"""clé pour tris par ordre de date_debut, le plus ancien en tête
|
||||
(pour avoir le plus récent d'abord, sort avec reverse=True)"""
|
||||
@ -729,7 +794,7 @@ class FormSemestre(models.ScoDocModel):
|
||||
FormSemestre.titre,
|
||||
)
|
||||
|
||||
def etapes_apo_vdi(self) -> list[ApoEtapeVDI]:
|
||||
def etapes_apo_vdi(self) -> list["ApoEtapeVDI"]:
|
||||
"Liste des vdis"
|
||||
# was read_formsemestre_etapes
|
||||
return [e.as_apovdi() for e in self.etapes if e.etape_apo]
|
||||
@ -742,9 +807,9 @@ class FormSemestre(models.ScoDocModel):
|
||||
return ""
|
||||
return ", ".join(sorted([etape.etape_apo for etape in self.etapes if etape]))
|
||||
|
||||
def add_etape(self, etape_apo: str):
|
||||
def add_etape(self, etape_apo: str | ApoEtapeVDI):
|
||||
"Ajoute une étape"
|
||||
etape = FormSemestreEtape(formsemestre_id=self.id, etape_apo=etape_apo)
|
||||
etape = FormSemestreEtape(formsemestre_id=self.id, etape_apo=str(etape_apo))
|
||||
db.session.add(etape)
|
||||
|
||||
def regroupements_coherents_etud(self) -> list[tuple[UniteEns, UniteEns]]:
|
||||
@ -1271,7 +1336,7 @@ class FormSemestreEtape(db.Model):
|
||||
def __str__(self):
|
||||
return self.etape_apo or ""
|
||||
|
||||
def as_apovdi(self) -> ApoEtapeVDI:
|
||||
def as_apovdi(self) -> "ApoEtapeVDI":
|
||||
return ApoEtapeVDI(self.etape_apo)
|
||||
|
||||
|
||||
|
@ -93,6 +93,10 @@ class Partition(ScoDocModel):
|
||||
):
|
||||
group.remove_etud(etud)
|
||||
|
||||
def is_default(self) -> bool:
|
||||
"vrai si partition par défault (tous les étudiants)"
|
||||
return not self.partition_name
|
||||
|
||||
def is_parcours(self) -> bool:
|
||||
"Vrai s'il s'agit de la partition de parcours"
|
||||
return self.partition_name == scu.PARTITION_PARCOURS
|
||||
|
@ -1056,10 +1056,10 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
|
||||
if current_user.has_permission(Permission.EditFormSemestre):
|
||||
H.append(
|
||||
f"""<ul>
|
||||
<li><a class="stdlink" href="{
|
||||
<li><b><a class="stdlink" href="{
|
||||
url_for('notes.formsemestre_createwithmodules', scodoc_dept=g.scodoc_dept,
|
||||
formation_id=formation_id, semestre_id=1)
|
||||
}">Mettre en place un nouveau semestre de formation {formation.acronyme}</a>
|
||||
}">Mettre en place un nouveau semestre de formation {formation.acronyme}</a></b>
|
||||
</li>
|
||||
</ul>"""
|
||||
)
|
||||
|
@ -229,11 +229,14 @@ def etapes_apo_str(etapes):
|
||||
return ", ".join([str(x) for x in etapes])
|
||||
|
||||
|
||||
def do_formsemestre_create(args, silent=False):
|
||||
def do_formsemestre_create( # DEPRECATED, use FormSemestre.create_formsemestre()
|
||||
args, silent=False
|
||||
):
|
||||
"create a formsemestre"
|
||||
from app.models import ScolarNews
|
||||
from app.scodoc import sco_groups
|
||||
|
||||
log("Warning: do_formsemestre_create is deprecated")
|
||||
cnx = ndb.GetDBConnexion()
|
||||
formsemestre_id = _formsemestreEditor.create(cnx, args)
|
||||
if args["etapes"]:
|
||||
|
@ -37,16 +37,17 @@ from app import db
|
||||
from app.auth.models import User
|
||||
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
|
||||
from app.models import (
|
||||
Module,
|
||||
ModuleImpl,
|
||||
Evaluation,
|
||||
UniteEns,
|
||||
ScoDocSiteConfig,
|
||||
ScolarFormSemestreValidation,
|
||||
ScolarAutorisationInscription,
|
||||
ApcValidationAnnee,
|
||||
ApcValidationRCUE,
|
||||
Evaluation,
|
||||
FormSemestreUECoef,
|
||||
Module,
|
||||
ModuleImpl,
|
||||
ScoDocSiteConfig,
|
||||
ScolarAutorisationInscription,
|
||||
ScolarFormSemestreValidation,
|
||||
ScolarNews,
|
||||
UniteEns,
|
||||
)
|
||||
from app.models.formations import Formation
|
||||
from app.models.formsemestre import FormSemestre
|
||||
@ -439,12 +440,13 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
||||
{
|
||||
"size": 32,
|
||||
"title": "Element(s) Apogée sem.:",
|
||||
"explanation": "associé(s) au résultat du semestre (ex: VRTW1). Inutile en BUT. Séparés par des virgules.",
|
||||
"allow_null": not sco_preferences.get_preference(
|
||||
"always_require_apo_sem_codes"
|
||||
)
|
||||
"explanation": """associé(s) au résultat du semestre (ex: VRTW1).
|
||||
Inutile en BUT. Séparés par des virgules.""",
|
||||
"allow_null": (
|
||||
not sco_preferences.get_preference("always_require_apo_sem_codes")
|
||||
or (formsemestre and formsemestre.modalite == "EXT")
|
||||
or (formsemestre.formation.is_apc()),
|
||||
or (formsemestre and formsemestre.formation.is_apc())
|
||||
),
|
||||
},
|
||||
)
|
||||
)
|
||||
@ -1250,7 +1252,7 @@ def formsemestre_clone(formsemestre_id):
|
||||
raise ScoValueError("id responsable invalide")
|
||||
new_formsemestre_id = do_formsemestre_clone(
|
||||
formsemestre_id,
|
||||
resp.id,
|
||||
resp,
|
||||
tf[2]["date_debut"],
|
||||
tf[2]["date_fin"],
|
||||
clone_evaluations=tf[2]["clone_evaluations"],
|
||||
@ -1268,7 +1270,7 @@ def formsemestre_clone(formsemestre_id):
|
||||
|
||||
def do_formsemestre_clone(
|
||||
orig_formsemestre_id,
|
||||
responsable_id, # new resp.
|
||||
responsable: User, # new resp.
|
||||
date_debut,
|
||||
date_fin, # 'dd/mm/yyyy'
|
||||
clone_evaluations=False,
|
||||
@ -1281,49 +1283,63 @@ def do_formsemestre_clone(
|
||||
formsemestre_orig: FormSemestre = FormSemestre.query.get_or_404(
|
||||
orig_formsemestre_id
|
||||
)
|
||||
orig_sem = sco_formsemestre.get_formsemestre(orig_formsemestre_id)
|
||||
cnx = ndb.GetDBConnexion()
|
||||
# 1- create sem
|
||||
args = orig_sem.copy()
|
||||
args = formsemestre_orig.to_dict()
|
||||
del args["formsemestre_id"]
|
||||
args["responsables"] = [responsable_id]
|
||||
del args["id"]
|
||||
del args["parcours"] # copiés ensuite
|
||||
args["responsables"] = [responsable]
|
||||
args["date_debut"] = date_debut
|
||||
args["date_fin"] = date_fin
|
||||
args["etat"] = 1 # non verrouillé
|
||||
formsemestre_id = sco_formsemestre.do_formsemestre_create(args)
|
||||
log(f"created formsemestre {formsemestre_id}")
|
||||
formsemestre: FormSemestre = db.session.get(FormSemestre, formsemestre_id)
|
||||
|
||||
formsemestre = FormSemestre.create_formsemestre(args)
|
||||
log(f"created formsemestre {formsemestre}")
|
||||
# 2- create moduleimpls
|
||||
modimpl_orig: ModuleImpl
|
||||
for modimpl_orig in formsemestre_orig.modimpls:
|
||||
assert isinstance(modimpl_orig, ModuleImpl)
|
||||
assert isinstance(modimpl_orig.id, int)
|
||||
log(f"cloning {modimpl_orig}")
|
||||
args = modimpl_orig.to_dict(with_module=False)
|
||||
args["formsemestre_id"] = formsemestre_id
|
||||
args["formsemestre_id"] = formsemestre.id
|
||||
modimpl_new = ModuleImpl.create_from_dict(args)
|
||||
log(f"created ModuleImpl from {args}")
|
||||
db.session.flush()
|
||||
# copy enseignants
|
||||
for ens in modimpl_orig.enseignants:
|
||||
modimpl_new.enseignants.append(ens)
|
||||
db.session.add(modimpl_new)
|
||||
db.session.flush()
|
||||
log(f"new moduleimpl.id = {modimpl_new.id}")
|
||||
# optionally, copy evaluations
|
||||
if clone_evaluations:
|
||||
e: Evaluation
|
||||
for e in Evaluation.query.filter_by(moduleimpl_id=modimpl_orig.id):
|
||||
log(f"cloning evaluation {e.id}")
|
||||
# copie en enlevant la date
|
||||
new_eval = e.clone(
|
||||
not_copying=("date_debut", "date_fin", "moduleimpl_id")
|
||||
)
|
||||
new_eval.moduleimpl_id = modimpl_new.id
|
||||
args = dict(e.__dict__)
|
||||
args.pop("_sa_instance_state")
|
||||
args.pop("id")
|
||||
args["moduleimpl_id"] = modimpl_new.id
|
||||
new_eval = Evaluation(**args)
|
||||
db.session.add(new_eval)
|
||||
db.session.commit()
|
||||
# Copie les poids APC de l'évaluation
|
||||
new_eval.set_ue_poids_dict(e.get_ue_poids_dict())
|
||||
db.session.commit()
|
||||
|
||||
# 3- copy uecoefs
|
||||
objs = sco_formsemestre.formsemestre_uecoef_list(
|
||||
cnx, args={"formsemestre_id": orig_formsemestre_id}
|
||||
for ue_coef in FormSemestreUECoef.query.filter_by(
|
||||
formsemestre_id=formsemestre_orig.id
|
||||
):
|
||||
new_ue_coef = FormSemestreUECoef(
|
||||
formsemestre_id=formsemestre.id,
|
||||
ue_id=ue_coef.ue_id,
|
||||
coefficient=ue_coef.coefficient,
|
||||
)
|
||||
for obj in objs:
|
||||
args = obj.copy()
|
||||
args["formsemestre_id"] = formsemestre_id
|
||||
_ = sco_formsemestre.formsemestre_uecoef_create(cnx, args)
|
||||
db.session.add(new_ue_coef)
|
||||
db.session.flush()
|
||||
|
||||
# NB: don't copy notes_formsemestre_custommenu (usually specific)
|
||||
|
||||
@ -1335,11 +1351,11 @@ def do_formsemestre_clone(
|
||||
if not prefs.is_global(pname):
|
||||
pvalue = prefs[pname]
|
||||
try:
|
||||
prefs.base_prefs.set(formsemestre_id, pname, pvalue)
|
||||
prefs.base_prefs.set(formsemestre.id, pname, pvalue)
|
||||
except ValueError:
|
||||
log(
|
||||
"do_formsemestre_clone: ignoring old preference %s=%s for %s"
|
||||
% (pname, pvalue, formsemestre_id)
|
||||
f"""do_formsemestre_clone: ignoring old preference {
|
||||
pname}={pvalue} for {formsemestre}"""
|
||||
)
|
||||
|
||||
# 5- Copie les parcours
|
||||
@ -1350,10 +1366,10 @@ def do_formsemestre_clone(
|
||||
# 6- Copy partitions and groups
|
||||
if clone_partitions:
|
||||
sco_groups_copy.clone_partitions_and_groups(
|
||||
orig_formsemestre_id, formsemestre_id
|
||||
orig_formsemestre_id, formsemestre.id
|
||||
)
|
||||
|
||||
return formsemestre_id
|
||||
return formsemestre.id
|
||||
|
||||
|
||||
def formsemestre_delete(formsemestre_id: int) -> str | flask.Response:
|
||||
|
@ -794,7 +794,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
<div class="sem-groups-partition-titre">{
|
||||
'Groupes de ' + partition.partition_name
|
||||
if partition.partition_name else
|
||||
'Tous les étudiants'}
|
||||
('aucun étudiant inscrit' if partition_is_empty else 'Tous les étudiants')}
|
||||
</div>
|
||||
<div class="sem-groups-partition-titre">{
|
||||
"Assiduité" if not partition_is_empty else ""
|
||||
@ -885,7 +885,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
)
|
||||
|
||||
H.append("</div>") # /sem-groups-assi
|
||||
if partition_is_empty:
|
||||
if partition_is_empty and not partition.is_default():
|
||||
H.append(
|
||||
'<div class="help sem-groups-none">Aucun groupe peuplé dans cette partition'
|
||||
)
|
||||
|
@ -109,13 +109,17 @@ ETATS_INSCRIPTION = {
|
||||
}
|
||||
|
||||
|
||||
def convert_fr_date(date_str: str, allow_iso=True) -> datetime.datetime:
|
||||
def convert_fr_date(
|
||||
date_str: str | datetime.datetime, allow_iso=True
|
||||
) -> datetime.datetime:
|
||||
"""Converti une date saisie par un humain français avant 2070
|
||||
en un objet datetime.
|
||||
12/2/1972 => 1972-02-12, 12/2/72 => 1972-02-12, mais 12/2/24 => 2024-02-12
|
||||
Le pivot est 70.
|
||||
ScoValueError si date invalide.
|
||||
"""
|
||||
if isinstance(date_str, datetime.datetime):
|
||||
return date_str
|
||||
try:
|
||||
return datetime.datetime.strptime(date_str, DATE_FMT)
|
||||
except ValueError:
|
||||
|
@ -30,7 +30,7 @@
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
|
||||
|
||||
class ApoEtapeVDI(object):
|
||||
class ApoEtapeVDI:
|
||||
"""Classe stockant le VDI avec le code étape (noms de fichiers maquettes et code semestres)"""
|
||||
|
||||
_ETAPE_VDI_SEP = "!"
|
||||
@ -118,7 +118,7 @@ class ApoEtapeVDI(object):
|
||||
else:
|
||||
return etape_vdi, ""
|
||||
|
||||
def concat_etape_vdi(self, etape, vdi=""):
|
||||
def concat_etape_vdi(self, etape: str, vdi: str = "") -> str:
|
||||
if vdi:
|
||||
return self._ETAPE_VDI_SEP.join([etape, vdi])
|
||||
else:
|
||||
|
Loading…
Reference in New Issue
Block a user