388 lines
13 KiB
Python
388 lines
13 KiB
Python
|
# -*- mode: python -*-
|
||
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
##############################################################################
|
||
|
#
|
||
|
# Gestion scolarite IUT
|
||
|
#
|
||
|
# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved.
|
||
|
#
|
||
|
# 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
|
||
|
"""
|
||
|
|
||
|
import safehtml
|
||
|
|
||
|
from notesdb import *
|
||
|
from sco_utils import *
|
||
|
from notes_log import log
|
||
|
from scolog import logdb
|
||
|
from gen_tables import GenTable
|
||
|
import sco_formsemestre
|
||
|
import sco_groups
|
||
|
import sco_tag_module
|
||
|
|
||
|
|
||
|
def report_debouche_date(context, start_year=None, format="html", REQUEST=None):
|
||
|
"""Rapport (table) pour les débouchés des étudiants sortis à partir de l'année indiquée.
|
||
|
"""
|
||
|
if not start_year:
|
||
|
return report_debouche_ask_date(context, REQUEST=REQUEST)
|
||
|
if format == "xls":
|
||
|
keep_numeric = True # pas de conversion des notes en strings
|
||
|
else:
|
||
|
keep_numeric = False
|
||
|
|
||
|
etudids = get_etudids_with_debouche(context, start_year)
|
||
|
tab = table_debouche_etudids(context, etudids, keep_numeric=keep_numeric)
|
||
|
|
||
|
tab.filename = make_filename("debouche_scodoc_%s" % start_year)
|
||
|
tab.origin = "Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + ""
|
||
|
tab.caption = "Récapitulatif débouchés à partir du 1/1/%s." % start_year
|
||
|
tab.base_url = "%s?start_year=%s" % (REQUEST.URL0, start_year)
|
||
|
return tab.make_page(
|
||
|
context,
|
||
|
title="""<h2 class="formsemestre">Débouchés étudiants </h2>""",
|
||
|
init_qtip=True,
|
||
|
javascripts=["js/etud_info.js"],
|
||
|
format=format,
|
||
|
REQUEST=REQUEST,
|
||
|
with_html_headers=True,
|
||
|
)
|
||
|
|
||
|
|
||
|
def get_etudids_with_debouche(context, start_year):
|
||
|
"""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 = SimpleDictFetch(context,
|
||
|
# """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 })
|
||
|
|
||
|
r = SimpleDictFetch(
|
||
|
context,
|
||
|
"""SELECT DISTINCT i.etudid
|
||
|
FROM notes_formsemestre_inscription i, notes_formsemestre s, itemsuivi it
|
||
|
WHERE i.etudid = it.etudid
|
||
|
AND i.formsemestre_id = s.formsemestre_id AND s.date_fin >= %(start_date)s
|
||
|
""",
|
||
|
{"start_date": start_date},
|
||
|
)
|
||
|
|
||
|
return [x["etudid"] for x in r]
|
||
|
|
||
|
|
||
|
def table_debouche_etudids(context, etudids, keep_numeric=True):
|
||
|
"""Rapport pour ces etudiants
|
||
|
"""
|
||
|
L = []
|
||
|
for etudid in etudids:
|
||
|
etud = context.getEtudInfo(filled=1, etudid=etudid)[0]
|
||
|
# retrouve le "dernier" semestre (au sens de la date de fin)
|
||
|
sems = etud["sems"]
|
||
|
es = [(sems[i]["date_fin_iso"], i) for i in range(len(sems))]
|
||
|
imax = max(es)[1]
|
||
|
last_sem = sems[imax]
|
||
|
nt = context._getNotesCache().get_NotesTable(
|
||
|
context, last_sem["formsemestre_id"]
|
||
|
)
|
||
|
row = {
|
||
|
"etudid": etudid,
|
||
|
"sexe": etud["sexe"],
|
||
|
"nom": etud["nom"],
|
||
|
"prenom": etud["prenom"],
|
||
|
"_nom_target": "ficheEtud?etudid=" + etud["etudid"],
|
||
|
"_prenom_target": "ficheEtud?etudid=" + etud["etudid"],
|
||
|
"_nom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]),
|
||
|
# 'debouche' : etud['debouche'],
|
||
|
"moy": fmt_note(nt.get_etud_moy_gen(etudid), keep_numeric=keep_numeric),
|
||
|
"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
|
||
|
debouche = itemsuivi_list_etud(context, etudid) # liste de plusieurs items
|
||
|
if debouche:
|
||
|
row["debouche"] = "<br>".join(
|
||
|
[
|
||
|
str(it["item_date"])
|
||
|
+ " : "
|
||
|
+ it["situation"]
|
||
|
+ " <i>"
|
||
|
+ it["tags"]
|
||
|
+ "</i>"
|
||
|
for it in debouche
|
||
|
]
|
||
|
) #
|
||
|
else:
|
||
|
row["debouche"] = "non renseigné"
|
||
|
L.append(row)
|
||
|
L.sort(key=lambda x: x["sem_ident"])
|
||
|
|
||
|
titles = {
|
||
|
"sexe": "",
|
||
|
"nom": "Nom",
|
||
|
"prenom": "Prénom",
|
||
|
"semestre": "Dernier semestre",
|
||
|
"semestre_id": "S",
|
||
|
"periode": "Dates",
|
||
|
"moy": "Moyenne",
|
||
|
"rang": "Rang",
|
||
|
"effectif": "Eff.",
|
||
|
"debouche": "Débouché",
|
||
|
}
|
||
|
tab = GenTable(
|
||
|
columns_ids=(
|
||
|
"semestre",
|
||
|
"semestre_id",
|
||
|
"periode",
|
||
|
"sexe",
|
||
|
"nom",
|
||
|
"prenom",
|
||
|
"moy",
|
||
|
"rang",
|
||
|
"effectif",
|
||
|
"debouche",
|
||
|
),
|
||
|
titles=titles,
|
||
|
rows=L,
|
||
|
# html_col_width='4em',
|
||
|
html_sortable=True,
|
||
|
html_class="table_leftalign table_listegroupe",
|
||
|
preferences=context.get_preferences(),
|
||
|
)
|
||
|
return tab
|
||
|
|
||
|
|
||
|
def report_debouche_ask_date(context, REQUEST=None):
|
||
|
"""Formulaire demande date départ
|
||
|
"""
|
||
|
return (
|
||
|
context.sco_header(REQUEST)
|
||
|
+ """<form method="GET">
|
||
|
Date de départ de la recherche: <input type="text" name="start_year" value="" size=10/>
|
||
|
</form>"""
|
||
|
+ context.sco_footer(REQUEST)
|
||
|
)
|
||
|
|
||
|
|
||
|
# ----------------------------------------------------------------------------
|
||
|
#
|
||
|
# Nouveau suivi des etudiants (nov 2017)
|
||
|
#
|
||
|
# ----------------------------------------------------------------------------
|
||
|
|
||
|
# OBSOLETE (this field has been copied to itemsuivi)
|
||
|
# def debouche_set(context, object, value, REQUEST=None):
|
||
|
# """Set debouche (field in admission table, may be deprecated ?)
|
||
|
# """
|
||
|
# if not context.can_edit_suivi(REQUEST):
|
||
|
# raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||
|
# adm_id = object
|
||
|
# debouche = value.strip('-_ \t')
|
||
|
# cnx = context.GetDBConnexion()
|
||
|
# adms = scolars.admission_list(cnx, {'etudid' : etudid})
|
||
|
# if not adms:
|
||
|
# raise ValueError('no admission info for %s !' % etudid)
|
||
|
# adm = adms[0]
|
||
|
# adm['debouche'] = debouche
|
||
|
# admission_edit(cnx, adm)
|
||
|
|
||
|
|
||
|
_itemsuiviEditor = EditableTable(
|
||
|
"itemsuivi",
|
||
|
"itemsuivi_id",
|
||
|
("itemsuivi_id", "etudid", "item_date", "situation"),
|
||
|
sortkey="item_date desc",
|
||
|
convert_null_outputs_to_empty=True,
|
||
|
output_formators={"situation": safehtml.HTML2SafeHTML, "item_date": DateISOtoDMY},
|
||
|
input_formators={"item_date": DateDMYtoISO},
|
||
|
)
|
||
|
|
||
|
_itemsuivi_create = _itemsuiviEditor.create
|
||
|
_itemsuivi_delete = _itemsuiviEditor.delete
|
||
|
_itemsuivi_list = _itemsuiviEditor.list
|
||
|
_itemsuivi_edit = _itemsuiviEditor.edit
|
||
|
|
||
|
|
||
|
class ItemSuiviTag(sco_tag_module.ScoTag):
|
||
|
"""Les tags sur les items
|
||
|
"""
|
||
|
|
||
|
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 ValueError("invalid itemsuivi_id")
|
||
|
return None
|
||
|
|
||
|
|
||
|
def itemsuivi_suppress(context, itemsuivi_id, REQUEST=None):
|
||
|
"""Suppression d'un item
|
||
|
"""
|
||
|
if not context.can_edit_suivi(REQUEST):
|
||
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||
|
cnx = context.GetDBConnexion()
|
||
|
item = itemsuivi_get(cnx, itemsuivi_id, ignore_errors=True)
|
||
|
if item:
|
||
|
_itemsuivi_delete(cnx, itemsuivi_id)
|
||
|
logdb(REQUEST, cnx, method="itemsuivi_suppress", etudid=item["etudid"])
|
||
|
log("suppressed itemsuivi %s" % (itemsuivi_id,))
|
||
|
|
||
|
|
||
|
def itemsuivi_create(
|
||
|
context, etudid, item_date=None, situation="", REQUEST=None, format=None
|
||
|
):
|
||
|
"""Creation d'un item"""
|
||
|
if not context.can_edit_suivi(REQUEST):
|
||
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||
|
cnx = context.GetDBConnexion()
|
||
|
itemsuivi_id = _itemsuivi_create(
|
||
|
cnx, args={"etudid": etudid, "item_date": item_date, "situation": situation}
|
||
|
)
|
||
|
logdb(REQUEST, cnx, method="itemsuivi_create", etudid=etudid)
|
||
|
log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
|
||
|
item = itemsuivi_get(cnx, itemsuivi_id)
|
||
|
if format == "json":
|
||
|
return sendJSON(REQUEST, item)
|
||
|
return item
|
||
|
|
||
|
|
||
|
def itemsuivi_set_date(context, itemsuivi_id, item_date, REQUEST=None):
|
||
|
"""set item date
|
||
|
item_date is a string dd/mm/yyyy
|
||
|
"""
|
||
|
if not context.can_edit_suivi(REQUEST):
|
||
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||
|
# log('itemsuivi_set_date %s : %s' % (itemsuivi_id, item_date))
|
||
|
cnx = context.GetDBConnexion()
|
||
|
item = itemsuivi_get(cnx, itemsuivi_id)
|
||
|
item["item_date"] = item_date
|
||
|
_itemsuivi_edit(cnx, item)
|
||
|
|
||
|
|
||
|
def itemsuivi_set_situation(context, object, value, REQUEST=None):
|
||
|
"""set situation"""
|
||
|
if not context.can_edit_suivi(REQUEST):
|
||
|
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))
|
||
|
cnx = context.GetDBConnexion()
|
||
|
item = itemsuivi_get(cnx, itemsuivi_id)
|
||
|
item["situation"] = situation
|
||
|
_itemsuivi_edit(cnx, item)
|
||
|
return situation or IT_SITUATION_MISSING_STR
|
||
|
|
||
|
|
||
|
def itemsuivi_list_etud(context, etudid, format=None, REQUEST=None):
|
||
|
"""Liste des items pour cet étudiant, avec tags"""
|
||
|
cnx = context.GetDBConnexion()
|
||
|
items = _itemsuivi_list(cnx, {"etudid": etudid})
|
||
|
for it in items:
|
||
|
it["tags"] = ", ".join(itemsuivi_tag_list(context, it["itemsuivi_id"]))
|
||
|
if format == "json":
|
||
|
return sendJSON(REQUEST, items)
|
||
|
return items
|
||
|
|
||
|
|
||
|
def itemsuivi_tag_list(context, itemsuivi_id):
|
||
|
"""les noms de tags associés à cet item"""
|
||
|
r = SimpleDictFetch(
|
||
|
context,
|
||
|
"""SELECT t.title
|
||
|
FROM itemsuivi_tags_assoc a, itemsuivi_tags t
|
||
|
WHERE a.tag_id = t.tag_id
|
||
|
AND a.itemsuivi_id = %(itemsuivi_id)s
|
||
|
""",
|
||
|
{"itemsuivi_id": itemsuivi_id},
|
||
|
)
|
||
|
return [x["title"] for x in r]
|
||
|
|
||
|
|
||
|
def itemsuivi_tag_search(context, term, REQUEST=None):
|
||
|
"""List all used tag names (for auto-completion)"""
|
||
|
# restrict charset to avoid injections
|
||
|
if not ALPHANUM_EXP.match(term.decode(SCO_ENCODING)):
|
||
|
data = []
|
||
|
else:
|
||
|
r = SimpleDictFetch(
|
||
|
context,
|
||
|
"SELECT title FROM itemsuivi_tags WHERE title LIKE %(term)s",
|
||
|
{"term": term + "%"},
|
||
|
)
|
||
|
data = [x["title"] for x in r]
|
||
|
|
||
|
return sendJSON(REQUEST, data)
|
||
|
|
||
|
|
||
|
def itemsuivi_tag_set(context, itemsuivi_id="", taglist=[], REQUEST=None):
|
||
|
"""taglist may either be:
|
||
|
a string with tag names separated by commas ("un;deux")
|
||
|
or a list of strings (["un", "deux"])
|
||
|
"""
|
||
|
if not context.can_edit_suivi(REQUEST):
|
||
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||
|
if not taglist:
|
||
|
taglist = []
|
||
|
elif type(taglist) == StringType:
|
||
|
taglist = taglist.split(",")
|
||
|
taglist = [t.strip() for t in taglist]
|
||
|
# log('itemsuivi_tag_set: itemsuivi_id=%s taglist=%s' % (itemsuivi_id, taglist))
|
||
|
# Sanity check:
|
||
|
cnx = context.GetDBConnexion()
|
||
|
item = itemsuivi_get(cnx, itemsuivi_id)
|
||
|
|
||
|
newtags = set(taglist)
|
||
|
oldtags = set(itemsuivi_tag_list(context, itemsuivi_id))
|
||
|
to_del = oldtags - newtags
|
||
|
to_add = newtags - oldtags
|
||
|
|
||
|
# should be atomic, but it's not.
|
||
|
for tagname in to_add:
|
||
|
t = ItemSuiviTag(context, tagname, object_id=itemsuivi_id)
|
||
|
for tagname in to_del:
|
||
|
t = ItemSuiviTag(context, tagname)
|
||
|
t.remove_tag_from_object(itemsuivi_id)
|