ScoDoc/app/scodoc/sco_debouche.py

414 lines
14 KiB
Python
Raw Permalink Normal View History

2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2023-12-31 23:04:06 +01:00
# Copyright (c) 1999 - 2024 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@gmail.com
#
##############################################################################
"""
Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudiant
"""
2021-08-10 17:12:10 +02:00
import http
from flask import url_for, g, request
from app import log
from app.comp import res_sem
from app.comp.res_compat import NotesTableCompat
from app.models import FormSemestre
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.scolog import logdb
from app.scodoc.gen_tables import GenTable
from app.scodoc import safehtml
from app.scodoc import html_sco_header
from app.scodoc import sco_permissions_check
from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module
from app.scodoc import sco_etud
2021-08-21 17:07:44 +02:00
import sco_version
2020-09-26 16:19:37 +02:00
def report_debouche_date(start_year=None, fmt="html"):
2021-09-30 09:37:18 +02:00
"""Rapport (table) pour les débouchés des étudiants sortis
à partir de l'année indiquée.
"""
2020-09-26 16:19:37 +02:00
if not start_year:
2021-09-30 09:37:18 +02:00
return report_debouche_ask_date("Année de début de la recherche")
else:
try:
start_year = int(start_year)
except ValueError:
return report_debouche_ask_date(
"Année invalide. Année de début de la recherche"
)
if fmt == "xls":
2020-09-26 16:19:37 +02:00
keep_numeric = True # pas de conversion des notes en strings
else:
keep_numeric = False
etudids = get_etudids_with_debouche(start_year)
tab = table_debouche_etudids(etudids, keep_numeric=keep_numeric)
2020-09-26 16:19:37 +02:00
2024-03-24 11:23:40 +01:00
tab.filename = scu.make_filename(f"debouche_scodoc_{start_year}")
tab.origin = f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}"
tab.caption = f"Récapitulatif débouchés à partir du 1/1/{start_year}."
tab.base_url = f"{request.base_url}?start_year={start_year}"
2020-09-26 16:19:37 +02:00
return tab.make_page(
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
init_qtip=True,
javascripts=["js/etud_info.js"],
fmt=fmt,
2020-09-26 16:19:37 +02:00
with_html_headers=True,
)
def get_etudids_with_debouche(start_year):
2020-09-26 16:19:37 +02:00
"""Liste des etudids de tous les semestres terminant
à partir du 1er janvier de start_year
et ayant un 'debouche' renseigné.
"""
start_date = str(start_year) + "-01-01"
# Recupere tous les etudid avec un debouché renseigné et une inscription dans un semestre
# posterieur à la date de depart:
# r = ndb.SimpleDictFetch(
2020-09-26 16:19:37 +02:00
# """SELECT DISTINCT i.etudid
# FROM notes_formsemestre_inscription i, admissions adm, notes_formsemestre s
# WHERE adm.debouche is not NULL
# AND i.etudid = adm.etudid AND i.formsemestre_id = s.formsemestre_id
# AND s.date_fin >= %(start_date)s
# """,
# {'start_date' : start_date })
2021-06-15 13:59:56 +02:00
r = ndb.SimpleDictFetch(
2020-09-26 16:19:37 +02:00
"""SELECT DISTINCT i.etudid
FROM notes_formsemestre_inscription i, notes_formsemestre s, itemsuivi it
WHERE i.etudid = it.etudid
AND i.formsemestre_id = s.id AND s.date_fin >= %(start_date)s
2021-09-30 09:37:18 +02:00
AND s.dept_id = %(dept_id)s
""",
2021-09-30 09:37:18 +02:00
{"start_date": start_date, "dept_id": g.scodoc_dept_id},
2020-09-26 16:19:37 +02:00
)
return [x["etudid"] for x in r]
def table_debouche_etudids(etudids, keep_numeric=True):
"""Rapport pour ces étudiants"""
2024-03-24 11:23:40 +01:00
rows = []
# Recherche les débouchés:
itemsuivi_etuds = {etudid: itemsuivi_list_etud(etudid) for etudid in etudids}
all_tags = set()
for debouche in itemsuivi_etuds.values():
if debouche:
for it in debouche:
all_tags.update(tag.strip() for tag in it["tags"].split(","))
all_tags = tuple(sorted(all_tags))
2020-09-26 16:19:37 +02:00
for etudid in etudids:
2021-08-22 13:24:36 +02:00
etud = sco_etud.get_etud_info(filled=True, etudid=etudid)[0]
2020-09-26 16:19:37 +02:00
# retrouve le "dernier" semestre (au sens de la date de fin)
sems = etud["sems"]
2021-07-13 16:39:41 +02:00
es = [(s["date_fin_iso"], i) for i, s in enumerate(sems)]
2020-09-26 16:19:37 +02:00
imax = max(es)[1]
last_sem = sems[imax]
formsemestre = FormSemestre.query.get_or_404(last_sem["formsemestre_id"])
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
2020-09-26 16:19:37 +02:00
row = {
"etudid": etudid,
"civilite": etud["civilite"],
2020-09-26 16:19:37 +02:00
"nom": etud["nom"],
"prenom": etud["prenom"],
"_nom_target": url_for(
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
),
"_prenom_target": url_for(
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid
),
2020-09-26 16:19:37 +02:00
"_nom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]),
# 'debouche' : etud['debouche'],
2020-12-24 00:07:17 +01:00
"moy": scu.fmt_note(nt.get_etud_moy_gen(etudid), keep_numeric=keep_numeric),
2020-09-26 16:19:37 +02:00
"rang": nt.get_etud_rang(etudid),
"effectif": len(nt.T),
"semestre_id": last_sem["semestre_id"],
"semestre": last_sem["titre"],
"date_debut": last_sem["date_debut"],
"date_fin": last_sem["date_fin"],
"periode": "%s - %s" % (last_sem["mois_debut"], last_sem["mois_fin"]),
"sem_ident": "%s %s"
% (last_sem["date_debut_iso"], last_sem["titre"]), # utile pour tris
}
# recherche des débouchés
2024-03-24 11:23:40 +01:00
debouche = itemsuivi_etuds[etudid] # liste de plusieurs items
2020-09-26 16:19:37 +02:00
if debouche:
2024-03-24 11:23:40 +01:00
if keep_numeric: # pour excel:
row["debouche"] = "\n".join(
f"""{it["item_date"]}: {it["situation"]}""" for it in debouche
)
else:
row["debouche"] = "<br>".join(
[
str(it["item_date"])
+ " : "
+ it["situation"]
+ " <i>"
+ it["tags"]
+ "</i>"
for it in debouche
]
)
for it in debouche:
for tag in it["tags"].split(","):
tag = tag.strip()
row[f"tag_{tag}"] = tag
2020-09-26 16:19:37 +02:00
else:
row["debouche"] = "non renseigné"
2024-03-24 11:23:40 +01:00
rows.append(row)
rows.sort(key=lambda x: x["sem_ident"])
2020-09-26 16:19:37 +02:00
titles = {
"civilite": "",
2020-09-26 16:19:37 +02:00
"nom": "Nom",
"prenom": "Prénom",
"semestre": "Dernier semestre",
"semestre_id": "S",
"periode": "Dates",
"moy": "Moyenne",
"rang": "Rang",
"effectif": "Eff.",
"debouche": "Débouché",
}
2024-03-24 11:23:40 +01:00
columns_ids = [
"semestre",
"semestre_id",
"periode",
"civilite",
"nom",
"prenom",
"moy",
"rang",
"effectif",
"debouche",
]
for tag in all_tags:
titles[f"tag_{tag}"] = tag
columns_ids.append(f"tag_{tag}")
2020-09-26 16:19:37 +02:00
tab = GenTable(
2024-03-24 11:23:40 +01:00
columns_ids=columns_ids,
2020-09-26 16:19:37 +02:00
titles=titles,
2024-03-24 11:23:40 +01:00
rows=rows,
2020-09-26 16:19:37 +02:00
# html_col_width='4em',
html_sortable=True,
html_class="table_leftalign table_listegroupe",
preferences=sco_preferences.SemPreferences(),
table_id="table_debouche_etudids",
2020-09-26 16:19:37 +02:00
)
return tab
2021-09-30 09:37:18 +02:00
def report_debouche_ask_date(msg: str) -> str:
2020-12-24 00:07:17 +01:00
"""Formulaire demande date départ"""
2021-09-30 09:37:18 +02:00
return f"""{html_sco_header.sco_header()}
<h2>Table des débouchés des étudiants</h2>
<form method="GET">
2023-12-31 23:04:06 +01:00
{msg}
2021-09-30 09:37:18 +02:00
<input type="text" name="start_year" value="" size=10/>
</form>
{html_sco_header.sco_footer()}
"""
2020-09-26 16:19:37 +02:00
# ----------------------------------------------------------------------------
#
# Nouveau suivi des etudiants (nov 2017)
#
# ----------------------------------------------------------------------------
2021-06-15 13:59:56 +02:00
_itemsuiviEditor = ndb.EditableTable(
2020-09-26 16:19:37 +02:00
"itemsuivi",
"itemsuivi_id",
("itemsuivi_id", "etudid", "item_date", "situation"),
sortkey="item_date desc",
convert_null_outputs_to_empty=True,
2020-12-24 00:07:17 +01:00
output_formators={
"situation": safehtml.html_to_safe_html,
2021-06-15 13:59:56 +02:00
"item_date": ndb.DateISOtoDMY,
2020-12-24 00:07:17 +01:00
},
2021-06-15 13:59:56 +02:00
input_formators={"item_date": ndb.DateDMYtoISO},
2020-09-26 16:19:37 +02:00
)
_itemsuivi_create = _itemsuiviEditor.create
_itemsuivi_delete = _itemsuiviEditor.delete
_itemsuivi_list = _itemsuiviEditor.list
_itemsuivi_edit = _itemsuiviEditor.edit
class ItemSuiviTag(sco_tag_module.ScoTag):
2020-12-24 00:07:17 +01:00
"""Les tags sur les items"""
2020-09-26 16:19:37 +02:00
tag_table = "itemsuivi_tags" # table (tag_id, title)
assoc_table = "itemsuivi_tags_assoc" # table (tag_id, object_id)
obj_colname = "itemsuivi_id" # column name for object_id in assoc_table
def itemsuivi_get(cnx, itemsuivi_id, ignore_errors=False):
"""get an item"""
items = _itemsuivi_list(cnx, {"itemsuivi_id": itemsuivi_id})
if items:
return items[0]
elif not ignore_errors:
raise ScoValueError(f"Débouché: item inexistant ({itemsuivi_id})")
2020-09-26 16:19:37 +02:00
return None
def itemsuivi_suppress(itemsuivi_id):
2020-12-24 00:07:17 +01:00
"""Suppression d'un item"""
if not sco_permissions_check.can_edit_suivi():
2020-09-26 16:19:37 +02:00
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
2021-06-15 13:59:56 +02:00
cnx = ndb.GetDBConnexion()
2020-09-26 16:19:37 +02:00
item = itemsuivi_get(cnx, itemsuivi_id, ignore_errors=True)
if item:
_itemsuivi_delete(cnx, itemsuivi_id)
2021-07-31 18:01:10 +02:00
logdb(cnx, method="itemsuivi_suppress", etudid=item["etudid"])
2020-09-26 16:19:37 +02:00
log("suppressed itemsuivi %s" % (itemsuivi_id,))
return ("", 204)
2020-09-26 16:19:37 +02:00
def itemsuivi_create(etudid, item_date=None, situation="", fmt=None):
2020-09-26 16:19:37 +02:00
"""Creation d'un item"""
if not sco_permissions_check.can_edit_suivi():
2020-09-26 16:19:37 +02:00
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
2021-06-15 13:59:56 +02:00
cnx = ndb.GetDBConnexion()
2020-09-26 16:19:37 +02:00
itemsuivi_id = _itemsuivi_create(
cnx, args={"etudid": etudid, "item_date": item_date, "situation": situation}
)
2021-07-31 18:01:10 +02:00
logdb(cnx, method="itemsuivi_create", etudid=etudid)
2020-09-26 16:19:37 +02:00
log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
item = itemsuivi_get(cnx, itemsuivi_id)
if fmt == "json":
return scu.sendJSON(item)
2020-09-26 16:19:37 +02:00
return item
def itemsuivi_set_date(itemsuivi_id, item_date):
2020-09-26 16:19:37 +02:00
"""set item date
item_date is a string dd/mm/yyyy
"""
if not sco_permissions_check.can_edit_suivi():
2020-09-26 16:19:37 +02:00
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
# log('itemsuivi_set_date %s : %s' % (itemsuivi_id, item_date))
2021-06-15 13:59:56 +02:00
cnx = ndb.GetDBConnexion()
2020-09-26 16:19:37 +02:00
item = itemsuivi_get(cnx, itemsuivi_id)
item["item_date"] = item_date
_itemsuivi_edit(cnx, item)
return ("", 204)
2020-09-26 16:19:37 +02:00
def itemsuivi_set_situation(object, value):
2020-09-26 16:19:37 +02:00
"""set situation"""
if not sco_permissions_check.can_edit_suivi():
2020-09-26 16:19:37 +02:00
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
itemsuivi_id = object
situation = value.strip("-_ \t")
# log('itemsuivi_set_situation %s : %s' % (itemsuivi_id, situation))
2021-06-15 13:59:56 +02:00
cnx = ndb.GetDBConnexion()
2020-09-26 16:19:37 +02:00
item = itemsuivi_get(cnx, itemsuivi_id)
item["situation"] = situation
_itemsuivi_edit(cnx, item)
2020-12-24 00:07:17 +01:00
return situation or scu.IT_SITUATION_MISSING_STR
2020-09-26 16:19:37 +02:00
def itemsuivi_list_etud(etudid, fmt=None):
2020-09-26 16:19:37 +02:00
"""Liste des items pour cet étudiant, avec tags"""
2021-06-15 13:59:56 +02:00
cnx = ndb.GetDBConnexion()
2020-09-26 16:19:37 +02:00
items = _itemsuivi_list(cnx, {"etudid": etudid})
for it in items:
it["tags"] = ", ".join(itemsuivi_tag_list(it["itemsuivi_id"]))
if fmt == "json":
return scu.sendJSON(items)
2020-09-26 16:19:37 +02:00
return items
def itemsuivi_tag_list(itemsuivi_id):
2020-09-26 16:19:37 +02:00
"""les noms de tags associés à cet item"""
2021-06-15 13:59:56 +02:00
r = ndb.SimpleDictFetch(
2020-09-26 16:19:37 +02:00
"""SELECT t.title
FROM itemsuivi_tags_assoc a, itemsuivi_tags t
WHERE a.tag_id = t.id
2020-09-26 16:19:37 +02:00
AND a.itemsuivi_id = %(itemsuivi_id)s
""",
{"itemsuivi_id": itemsuivi_id},
)
return [x["title"] for x in r]
def itemsuivi_tag_search(term):
2020-09-26 16:19:37 +02:00
"""List all used tag names (for auto-completion)"""
# restrict charset to avoid injections
2021-07-12 15:13:10 +02:00
if not scu.ALPHANUM_EXP.match(term):
2020-09-26 16:19:37 +02:00
data = []
else:
2021-06-15 13:59:56 +02:00
r = ndb.SimpleDictFetch(
"SELECT title FROM itemsuivi_tags WHERE title LIKE %(term)s AND dept_id=%(dept_id)s",
{
"term": term + "%",
"dept_id": g.scodoc_dept_id,
},
2020-09-26 16:19:37 +02:00
)
data = [x["title"] for x in r]
return scu.sendJSON(data)
2020-09-26 16:19:37 +02:00
def itemsuivi_tag_set(itemsuivi_id="", taglist=None):
2020-09-26 16:19:37 +02:00
"""taglist may either be:
a string with tag names separated by commas ("un;deux")
or a list of strings (["un", "deux"])
"""
if not sco_permissions_check.can_edit_suivi():
2020-09-26 16:19:37 +02:00
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
if not taglist:
taglist = []
2021-07-11 18:18:44 +02:00
elif isinstance(taglist, str):
2020-09-26 16:19:37 +02:00
taglist = taglist.split(",")
taglist = [t.strip() for t in taglist]
# log('itemsuivi_tag_set: itemsuivi_id=%s taglist=%s' % (itemsuivi_id, taglist))
# Sanity check:
2021-06-15 13:59:56 +02:00
cnx = ndb.GetDBConnexion()
2020-12-24 00:07:17 +01:00
_ = itemsuivi_get(cnx, itemsuivi_id)
2020-09-26 16:19:37 +02:00
newtags = set(taglist)
oldtags = set(itemsuivi_tag_list(itemsuivi_id))
2020-09-26 16:19:37 +02:00
to_del = oldtags - newtags
to_add = newtags - oldtags
# should be atomic, but it's not.
for tagname in to_add:
t = ItemSuiviTag(tagname, object_id=itemsuivi_id)
2020-09-26 16:19:37 +02:00
for tagname in to_del:
t = ItemSuiviTag(tagname)
2020-09-26 16:19:37 +02:00
t.remove_tag_from_object(itemsuivi_id)
2021-08-10 17:12:10 +02:00
return "", http.HTTPStatus.NO_CONTENT