ScoDoc/app/scodoc/sco_formsemestre.py

597 lines
19 KiB
Python
Raw Normal View History

2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2021-01-01 17:51:08 +01:00
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
2020-09-26 16:19:37 +02:00
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""Operations de base sur les formsemestres
"""
2021-02-04 20:02:44 +01:00
import time
2021-07-09 23:19:30 +02:00
from operator import itemgetter
2020-09-26 16:19:37 +02:00
2021-06-21 10:17:16 +02:00
from scodoc_manager import sco_mgr
from app.scodoc import sco_codes_parcours
2021-07-19 19:53:01 +02:00
from app.scodoc import sco_cache
2021-07-15 17:14:44 +02:00
from app.scodoc import sco_formations
from app.scodoc import sco_preferences
from app.scodoc import sco_users
2021-06-21 10:17:16 +02:00
from app.scodoc.gen_tables import GenTable
from app.scodoc.notes_log import log
from app.scodoc.sco_codes_parcours import NO_SEMESTRE_ID
2021-07-10 13:55:35 +02:00
from app.scodoc.sco_vdi import ApoEtapeVDI
2021-06-21 10:17:16 +02:00
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
2020-09-26 16:19:37 +02:00
2021-02-03 22:00:41 +01:00
_formsemestreEditor = ndb.EditableTable(
2020-09-26 16:19:37 +02:00
"notes_formsemestre",
"formsemestre_id",
(
"formsemestre_id",
"semestre_id",
"formation_id",
"titre",
"date_debut",
"date_fin",
"gestion_compensation",
"gestion_semestrielle",
"etat",
"bul_hide_xml",
"bul_bgcolor",
"modalite",
"resp_can_edit",
"resp_can_change_ens",
"ens_can_edit_eval",
"elt_sem_apo",
"elt_annee_apo",
),
sortkey="date_debut",
output_formators={
2021-02-03 22:00:41 +01:00
"date_debut": ndb.DateISOtoDMY,
"date_fin": ndb.DateISOtoDMY,
2021-08-10 12:57:38 +02:00
"gestion_compensation": bool,
"gestion_semestrielle": bool,
2021-08-10 09:10:36 +02:00
"etat": bool,
2021-08-10 12:57:38 +02:00
"bul_hide_xml": bool,
2020-09-26 16:19:37 +02:00
},
input_formators={
2021-02-03 22:00:41 +01:00
"date_debut": ndb.DateDMYtoISO,
"date_fin": ndb.DateDMYtoISO,
2021-08-10 12:57:38 +02:00
"gestion_compensation": bool,
"gestion_semestrielle": bool,
2021-08-10 09:10:36 +02:00
"etat": bool,
2021-08-10 12:57:38 +02:00
"bul_hide_xml": bool,
2020-09-26 16:19:37 +02:00
},
)
def get_formsemestre(context, formsemestre_id):
"list ONE formsemestre"
try:
sem = do_formsemestre_list(context, args={"formsemestre_id": formsemestre_id})[
0
]
return sem
except:
log("get_formsemestre: invalid formsemestre_id (%s)" % formsemestre_id)
raise
def do_formsemestre_list(context, *a, **kw):
"list formsemestres"
# log('do_formsemestre_list: a=%s kw=%s' % (str(a),str(kw)))
2021-06-15 13:59:56 +02:00
cnx = ndb.GetDBConnexion()
2020-09-26 16:19:37 +02:00
sems = _formsemestreEditor.list(cnx, *a, **kw)
# Ajoute les étapes Apogee et les responsables:
for sem in sems:
sem["etapes"] = read_formsemestre_etapes(context, sem["formsemestre_id"])
sem["responsables"] = read_formsemestre_responsables(
context, sem["formsemestre_id"]
)
# Filtre sur code etape si indiqué:
if "args" in kw:
etape = kw["args"].get("etape_apo", None)
if etape:
sems = [sem for sem in sems if etape in sem["etapes"]]
for sem in sems:
formsemestre_enrich(context, sem)
2021-07-09 23:19:30 +02:00
# tri par date, le plus récent d'abord
sems.sort(key=itemgetter("dateord", "semestre_id"), reverse=True)
2020-09-26 16:19:37 +02:00
return sems
def formsemestre_enrich(context, sem):
"""Ajoute champs souvent utiles: titre + annee et dateord (pour tris)"""
2020-09-26 16:19:37 +02:00
# imports ici pour eviter refs circulaires
2021-07-12 00:25:23 +02:00
from app.scodoc import sco_formsemestre_edit
from app.scodoc import sco_etud
2020-09-26 16:19:37 +02:00
2021-07-15 17:14:44 +02:00
F = sco_formations.formation_list(
context, args={"formation_id": sem["formation_id"]}
)[0]
2020-09-26 16:19:37 +02:00
parcours = sco_codes_parcours.get_parcours_from_code(F["type_parcours"])
# 'S1', 'S2', ... ou '' pour les monosemestres
if sem["semestre_id"] != NO_SEMESTRE_ID:
sem["sem_id_txt"] = "S%s" % sem["semestre_id"]
else:
sem["sem_id_txt"] = ""
# Nom avec numero semestre:
sem["titre_num"] = sem["titre"] # eg "DUT Informatique"
if sem["semestre_id"] != NO_SEMESTRE_ID:
sem["titre_num"] += " %s %s" % (
parcours.SESSION_NAME,
sem["semestre_id"],
) # eg "DUT Informatique semestre 2"
2021-02-03 22:00:41 +01:00
sem["dateord"] = ndb.DateDMYtoISO(sem["date_debut"])
sem["date_debut_iso"] = ndb.DateDMYtoISO(sem["date_debut"])
sem["date_fin_iso"] = ndb.DateDMYtoISO(sem["date_fin"])
2020-09-26 16:19:37 +02:00
try:
mois_debut, annee_debut = sem["date_debut"].split("/")[1:]
except:
mois_debut, annee_debut = "", ""
try:
mois_fin, annee_fin = sem["date_fin"].split("/")[1:]
except:
mois_fin, annee_fin = "", ""
sem["annee_debut"] = annee_debut
sem["annee_fin"] = annee_fin
sem["mois_debut_ord"] = int(mois_debut)
sem["mois_fin_ord"] = int(mois_fin)
sem["annee"] = annee_debut
# 2007 ou 2007-2008:
2021-02-04 20:02:44 +01:00
sem["anneescolaire"] = scu.annee_scolaire_repr(
int(annee_debut), sem["mois_debut_ord"]
)
2020-09-26 16:19:37 +02:00
# La période: considère comme "S1" (ou S3) les débuts en aout-sept-octobre
# devrait sans doute pouvoir etre changé...
if sem["mois_debut_ord"] >= 8 and sem["mois_debut_ord"] <= 10:
sem["periode"] = 1 # typiquement, début en septembre: S1, S3...
else:
sem["periode"] = 2 # typiquement, début en février: S2, S4...
sem["titreannee"] = "%s %s %s" % (
sem["titre_num"],
sem.get("modalite", ""),
annee_debut,
)
if annee_fin != annee_debut:
sem["titreannee"] += "-" + annee_fin
sem["annee"] += "-" + annee_fin
# et les dates sous la forme "oct 2007 - fev 2008"
months = sco_etud.MONTH_NAMES_ABBREV
2020-09-26 16:19:37 +02:00
if mois_debut:
mois_debut = months[int(mois_debut) - 1]
if mois_fin:
mois_fin = months[int(mois_fin) - 1]
sem["mois_debut"] = mois_debut + " " + annee_debut
sem["mois_fin"] = mois_fin + " " + annee_fin
sem["titremois"] = "%s %s (%s - %s)" % (
sem["titre_num"],
sem.get("modalite", ""),
sem["mois_debut"],
sem["mois_fin"],
)
sem["session_id"] = sco_formsemestre_edit.get_formsemestre_session_id(
context, sem, F, parcours
)
sem["etapes"] = read_formsemestre_etapes(context, sem["formsemestre_id"])
sem["etapes_apo_str"] = formsemestre_etape_apo_str(sem)
sem["responsables"] = read_formsemestre_responsables(
context, sem["formsemestre_id"]
)
def formsemestre_etape_apo_str(sem):
"chaine décrivant le(s) codes étapes Apogée"
return etapes_apo_str(sem["etapes"])
def etapes_apo_str(etapes):
"Chaine decrivant une liste d'instance de ApoEtapeVDI"
return ", ".join([str(x) for x in etapes])
def do_formsemestre_create(args, silent=False):
2021-06-21 10:17:16 +02:00
"create a formsemestre"
context = None # XXX #context
2021-06-21 11:22:55 +02:00
from app.scodoc import sco_groups
from app.scodoc import sco_news
2021-06-21 10:17:16 +02:00
cnx = ndb.GetDBConnexion()
formsemestre_id = _formsemestreEditor.create(cnx, args)
if args["etapes"]:
args["formsemestre_id"] = formsemestre_id
write_formsemestre_etapes(context, args)
if args["responsables"]:
args["formsemestre_id"] = formsemestre_id
write_formsemestre_responsables(context, args)
# create default partition
partition_id = sco_groups.partition_create(
context,
formsemestre_id,
default=True,
redirect=0,
2021-06-21 10:17:16 +02:00
)
_group_id = sco_groups.createGroup(context, partition_id, default=True)
2021-06-21 10:17:16 +02:00
# news
2021-07-09 17:47:06 +02:00
if "titre" not in args:
2021-06-21 10:17:16 +02:00
args["titre"] = "sans titre"
args["formsemestre_id"] = formsemestre_id
args["url"] = "Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s" % args
if not silent:
sco_news.add(
typ=sco_news.NEWS_SEM,
text='Création du semestre <a href="%(url)s">%(titre)s</a>' % args,
url=args["url"],
)
return formsemestre_id
2020-09-26 16:19:37 +02:00
def do_formsemestre_edit(context, sem, cnx=None, **kw):
2021-01-16 11:49:02 +01:00
"""Apply modifications to formsemestre.
Update etapes and resps. Invalidate cache."""
2020-09-26 16:19:37 +02:00
if not cnx:
2021-06-15 13:59:56 +02:00
cnx = ndb.GetDBConnexion()
2020-09-26 16:19:37 +02:00
_formsemestreEditor.edit(cnx, sem, **kw)
write_formsemestre_etapes(context, sem)
write_formsemestre_responsables(context, sem)
2021-07-19 19:53:01 +02:00
sco_cache.invalidate_formsemestre(
formsemestre_id=sem["formsemestre_id"]
2021-06-13 23:37:14 +02:00
) # > modif formsemestre
2020-09-26 16:19:37 +02:00
def read_formsemestre_responsables(context, formsemestre_id):
"""recupere liste des responsables de ce semestre
:returns: liste de chaines
"""
2021-02-03 22:00:41 +01:00
r = ndb.SimpleDictFetch(
"""SELECT responsable_id
FROM notes_formsemestre_responsables
WHERE formsemestre_id = %(formsemestre_id)s
""",
2020-09-26 16:19:37 +02:00
{"formsemestre_id": formsemestre_id},
)
return [x["responsable_id"] for x in r]
def write_formsemestre_responsables(context, sem):
return _write_formsemestre_aux(context, sem, "responsables", "responsable_id")
# ---------------------- Coefs des UE
2021-02-03 22:00:41 +01:00
_formsemestre_uecoef_editor = ndb.EditableTable(
"notes_formsemestre_uecoef",
"formsemestre_uecoef_id",
("formsemestre_uecoef_id", "formsemestre_id", "ue_id", "coefficient"),
)
formsemestre_uecoef_create = _formsemestre_uecoef_editor.create
formsemestre_uecoef_edit = _formsemestre_uecoef_editor.edit
formsemestre_uecoef_list = _formsemestre_uecoef_editor.list
formsemestre_uecoef_delete = _formsemestre_uecoef_editor.delete
def do_formsemestre_uecoef_edit_or_create(context, cnx, formsemestre_id, ue_id, coef):
"modify or create the coef"
coefs = formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue_id}
)
if coefs:
formsemestre_uecoef_edit(
cnx,
args={
"formsemestre_uecoef_id": coefs[0]["formsemestre_uecoef_id"],
"coefficient": coef,
},
)
else:
formsemestre_uecoef_create(
cnx,
args={
"formsemestre_id": formsemestre_id,
"ue_id": ue_id,
"coefficient": coef,
},
)
def do_formsemestre_uecoef_delete(context, cnx, formsemestre_id, ue_id):
"delete coef for this (ue,sem)"
coefs = formsemestre_uecoef_list(
cnx, args={"formsemestre_id": formsemestre_id, "ue_id": ue_id}
)
if coefs:
formsemestre_uecoef_delete(cnx, coefs[0]["formsemestre_uecoef_id"])
2020-09-26 16:19:37 +02:00
def read_formsemestre_etapes(context, formsemestre_id):
"""recupere liste des codes etapes associés à ce semestre
:returns: liste d'instance de ApoEtapeVDI
"""
2021-02-03 22:00:41 +01:00
r = ndb.SimpleDictFetch(
"""SELECT etape_apo
FROM notes_formsemestre_etapes
WHERE formsemestre_id = %(formsemestre_id)s
""",
2020-09-26 16:19:37 +02:00
{"formsemestre_id": formsemestre_id},
)
return [ApoEtapeVDI(x["etape_apo"]) for x in r if x["etape_apo"]]
def write_formsemestre_etapes(context, sem):
return _write_formsemestre_aux(context, sem, "etapes", "etape_apo")
def _write_formsemestre_aux(context, sem, fieldname, valuename):
"""fieldname: 'etapes' ou 'responsables'
valuename: 'etape_apo' ou 'responsable_id'
"""
if not fieldname in sem:
2020-09-26 16:19:37 +02:00
return
# uniquify
values = set([str(x) for x in sem[fieldname]])
2021-06-21 12:13:25 +02:00
cnx = ndb.GetDBConnexion(autocommit=False)
2021-02-03 22:00:41 +01:00
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
2020-09-26 16:19:37 +02:00
tablename = "notes_formsemestre_" + fieldname
try:
cursor.execute(
"DELETE from " + tablename + " where formsemestre_id = %(formsemestre_id)s",
{"formsemestre_id": sem["formsemestre_id"]},
)
for item in values:
2020-09-26 16:19:37 +02:00
if item:
cursor.execute(
"INSERT INTO "
+ tablename
+ " (formsemestre_id, "
+ valuename
+ ") VALUES (%(formsemestre_id)s, %("
+ valuename
+ ")s)",
{"formsemestre_id": sem["formsemestre_id"], valuename: item},
2020-09-26 16:19:37 +02:00
)
except:
log("Warning: exception in write_formsemestre_aux !")
cnx.rollback()
raise
cnx.commit()
def sem_set_responsable_name(context, sem):
"ajoute champs responsable_name"
sem["responsable_name"] = ", ".join(
[
sco_users.user_info(responsable_id)["nomprenom"]
2020-09-26 16:19:37 +02:00
for responsable_id in sem["responsables"]
]
)
def sem_in_semestre_scolaire(context, sem, year=False, saison=0, REQUEST=None):
"""n'utilise que la date de debut, pivot au 1er aout
si annee non specifiée, année scolaire courante
Patch Jmp: ajout du parametre optionnel saison
2020-09-26 16:19:37 +02:00
1 = sept, 0 = janvier, None = année complète
si saison non spécifiée: année complète
pivot de saison au 1er décembre
XXX TODO: la période (ici appelée "saison" devrait être éditable
manuellement dans le formsemestre_edit afin de couvrir les cas particulier
comme un semestre S2 qui commencerait en décembre... voire novembre.
2020-09-26 16:19:37 +02:00
)
"""
if not year:
2021-08-01 16:13:47 +02:00
year = scu.AnneeScolaire()
2020-09-26 16:19:37 +02:00
# est-on dans la même année universitaire ?
if sem["mois_debut_ord"] > 7:
if sem["annee_debut"] != str(year):
return False
else:
if sem["annee_debut"] != str(year + 1):
return False
# rafinement éventuel sur le semestre
# saison is None => pas de rafinement => True
if saison == 0:
return True
elif saison == 1: # calcul en fonction de la saison
return sem["mois_debut_ord"] > 7 and sem["mois_debut_ord"] < 12
else: # saison == 0
return sem["mois_debut_ord"] <= 7 or sem["mois_debut_ord"] == 12
def sem_in_annee_scolaire(context, sem, year=False, REQUEST=None):
"""Test si sem appartient à l'année scolaire year (int).
N'utilise que la date de debut, pivot au 1er août.
Si annee non specifiée, année scolaire courante
"""
if not year:
2021-08-01 16:13:47 +02:00
year = scu.AnneeScolaire()
2020-09-26 16:19:37 +02:00
return ((sem["annee_debut"] == str(year)) and (sem["mois_debut_ord"] > 7)) or (
(sem["annee_debut"] == str(year + 1)) and (sem["mois_debut_ord"] <= 7)
)
def sem_une_annee(context, sem):
"""Test si sem est entièrement sur la même année scolaire.
(ce n'est pas obligatoire mais si ce n'est pas le cas les exports Apogée ne vont pas fonctionner)
pivot au 1er août.
"""
if sem["date_debut_iso"] > sem["date_fin_iso"]:
log("Warning: semestre %(formsemestre_id)s begins after ending !" % sem)
2020-09-26 16:19:37 +02:00
return False
2020-09-26 16:19:37 +02:00
debut = int(sem["annee_debut"])
if sem["mois_debut_ord"] < 8: # considere que debut sur l'anne scolaire precedente
2020-09-26 16:19:37 +02:00
debut -= 1
fin = int(sem["annee_fin"])
if (
sem["mois_fin_ord"] < 9
): # 9 (sept) pour autoriser un début en sept et une fin en aout
2020-09-26 16:19:37 +02:00
fin -= 1
return debut == fin
def sem_est_courant(context, sem):
"""Vrai si la date actuelle (now) est dans le semestre (les dates de début et fin sont incluses)"""
now = time.strftime("%Y-%m-%d")
2021-02-03 22:00:41 +01:00
debut = ndb.DateDMYtoISO(sem["date_debut"])
fin = ndb.DateDMYtoISO(sem["date_fin"])
return (debut <= now) and (now <= fin)
2020-09-26 16:19:37 +02:00
def scodoc_get_all_unlocked_sems(context):
"""Liste de tous les semestres non verrouillés de tous les départements"""
2021-06-21 10:17:16 +02:00
depts = sco_mgr.get_dept_ids()
2020-09-26 16:19:37 +02:00
semdepts = []
for dept in depts:
semdepts += [
(sem, dept.Scolarite.Notes)
for sem in do_formsemestre_list(dept.Scolarite.Notes)
2021-08-10 12:57:38 +02:00
if sem["etat"]
2020-09-26 16:19:37 +02:00
]
return semdepts
def table_formsemestres(
context,
sems,
columns_ids=(),
sup_columns_ids=(),
html_title="<h2>Semestres</h2>",
html_next_section="",
REQUEST=None,
):
"""Une table presentant des semestres"""
2020-09-26 16:19:37 +02:00
for sem in sems:
sem_set_responsable_name(context, sem)
sem["_titre_num_target"] = (
"formsemestre_status?formsemestre_id=%s" % sem["formsemestre_id"]
)
if not columns_ids:
columns_ids = (
"etat",
"modalite",
"mois_debut",
"mois_fin",
"titre_num",
"responsable_name",
"etapes_apo_str",
)
columns_ids += sup_columns_ids
titles = {
"modalite": "",
"mois_debut": "Début",
"mois_fin": "Fin",
"titre_num": "Semestre",
"responsable_name": "Resp.",
"etapes_apo_str": "Apo.",
}
if sems:
preferences = sco_preferences.SemPreferences(sems[0]["formsemestre_id"])
2020-09-26 16:19:37 +02:00
else:
preferences = sco_preferences.SemPreferences()
2020-09-26 16:19:37 +02:00
tab = GenTable(
columns_ids=columns_ids,
rows=sems,
titles=titles,
html_class="table_leftalign",
html_sortable=True,
html_title=html_title,
html_next_section=html_next_section,
html_empty_element="<p><em>aucun résultat</em></p>",
page_title="Semestres",
preferences=preferences,
)
return tab
def list_formsemestre_by_etape(
context, etape_apo=False, annee_scolaire=False, REQUEST=None
2020-09-26 16:19:37 +02:00
):
"""Liste des semestres de cette etape, pour l'annee scolaire indiquée (sinon, pour toutes)"""
2020-09-26 16:19:37 +02:00
ds = {} # formsemestre_id : sem
if etape_apo:
sems = do_formsemestre_list(context, args={"etape_apo": etape_apo})
for sem in sems:
if annee_scolaire: # restriction annee scolaire
if sem_in_annee_scolaire(
context, sem, year=int(annee_scolaire), REQUEST=REQUEST
2020-09-26 16:19:37 +02:00
):
ds[sem["formsemestre_id"]] = sem
2021-07-09 17:47:06 +02:00
sems = list(ds.values())
2020-09-26 16:19:37 +02:00
else:
sems = do_formsemestre_list(context)
if annee_scolaire:
sems = [
sem
for sem in sems
if sem_in_annee_scolaire(
context, sem, year=int(annee_scolaire), REQUEST=REQUEST
2020-09-26 16:19:37 +02:00
)
]
sems.sort(key=lambda s: (s["modalite"], s["dateord"]))
return sems
def view_formsemestre_by_etape(context, etape_apo=None, format="html", REQUEST=None):
"""Affiche table des semestres correspondants à l'étape"""
2020-09-26 16:19:37 +02:00
if etape_apo:
html_title = (
"""<h2>Semestres courants de l'étape <tt>%s</tt></h2>""" % etape_apo
)
else:
html_title = """<h2>Semestres courants</h2>"""
tab = table_formsemestres(
context,
list_formsemestre_by_etape(
2021-08-01 16:13:47 +02:00
context, etape_apo=etape_apo, annee_scolaire=scu.AnneeScolaire()
2020-09-26 16:19:37 +02:00
),
html_title=html_title,
html_next_section="""<form action="view_formsemestre_by_etape">
Etape: <input name="etape_apo" type="text" size="8"></input>
</form>""",
REQUEST=REQUEST,
)
tab.base_url = "%s?etape_apo=%s" % (REQUEST.URL0, etape_apo or "")
return tab.make_page(context, format=format, REQUEST=REQUEST)
def sem_has_etape(sem, code_etape):
return code_etape in sem["etapes"]