ScoDoc/app/scodoc/sco_abs_views.py

1122 lines
38 KiB
Python

# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2021 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@viennet.net
#
##############################################################################
"""Pages HTML gestion absences
(la plupart portées du DTML)
"""
import datetime
from stripogram import html2text, html2safehtml
import app.scodoc.sco_utils as scu
from app.scodoc import notesdb as ndb
from app.scodoc.scolog import logdb
from app.scodoc.gen_tables import GenTable
from app.scodoc import html_sco_header
from app.scodoc import sco_abs
from app.scodoc import sco_etud
from app.scodoc import sco_find_etud
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_photos
from app.scodoc import sco_preferences
from app.scodoc.notes_log import log
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission
def doSignaleAbsence(
context,
datedebut,
datefin,
moduleimpl_id=None,
demijournee=2,
estjust=False,
description=None,
etudid=False,
REQUEST=None,
): # etudid implied
"""Signalement d'une absence.
Args:
datedebut: dd/mm/yyyy
datefin: dd/mm/yyyy (non incluse)
moduleimpl_id: module auquel imputer les absences
demijournee: 2 si journée complète, 1 matin, 0 après-midi
estjust: absence justifiée
description: str
etudid: etudiant concerné. Si non spécifié, cherche dans REQUEST.form
"""
etud = sco_etud.get_etud_info(filled=1, etudid=etudid, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
description_abs = description
dates = sco_abs.DateRangeISO(context, datedebut, datefin)
nbadded = 0
demijournee = int(demijournee)
for jour in dates:
if demijournee == 2:
sco_abs.add_absence(
context,
etudid,
jour,
False,
estjust,
REQUEST,
description_abs,
moduleimpl_id,
)
sco_abs.add_absence(
context,
etudid,
jour,
True,
estjust,
REQUEST,
description_abs,
moduleimpl_id,
)
nbadded += 2
else:
sco_abs.add_absence(
context,
etudid,
jour,
demijournee,
estjust,
REQUEST,
description_abs,
moduleimpl_id,
)
nbadded += 1
#
if estjust:
J = ""
else:
J = "NON "
M = ""
if moduleimpl_id and moduleimpl_id != "NULL":
mod = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id)[0]
formsemestre_id = mod["formsemestre_id"]
nt = context.Notes._getNotesCache().get_NotesTable(
context.Notes, formsemestre_id
)
ues = nt.get_ues(etudid=etudid)
for ue in ues:
modimpls = nt.get_modimpls(ue_id=ue["ue_id"])
for modimpl in modimpls:
if modimpl["moduleimpl_id"] == moduleimpl_id:
M = "dans le module %s" % modimpl["module"]["code"]
H = [
html_sco_header.sco_header(
context,
REQUEST,
page_title="Signalement d'une absence pour %(nomprenom)s" % etud,
),
"""<h2>Signalement d'absences</h2>""",
]
if dates:
H.append(
"""<p>Ajout de %d absences <b>%sjustifiées</b> du %s au %s %s</p>"""
% (nbadded, J, datedebut, datefin, M)
)
else:
H.append(
"""<p class="warning">Aucune date ouvrable entre le %s et le %s !</p>"""
% (datedebut, datefin)
)
H.append(
"""<ul><li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Autre absence pour <b>%(nomprenom)s</b></a></li>
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses absences</a></li>
</ul>
<hr>"""
% etud
)
H.append(sco_find_etud.form_search_etud(context, REQUEST))
H.append(html_sco_header.sco_footer(context, REQUEST))
return "\n".join(H)
def SignaleAbsenceEtud(context, REQUEST=None): # etudid implied
"""Formulaire individuel simple de signalement d'une absence"""
# brute-force portage from very old dtml code ...
etud = sco_etud.get_etud_info(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
disabled = False
if not etud["cursem"]:
require_module = sco_preferences.get_preference(
context, "abs_require_module"
) # on utilise la pref globale car pas de sem courant
if require_module:
menu_module = """<div class="ue_warning">Pas inscrit dans un semestre courant,
et l'indication du module est requise. Donc pas de saisie d'absence possible !</div>"""
disabled = True
else:
menu_module = ""
else:
formsemestre_id = etud["cursem"]["formsemestre_id"]
require_module = sco_preferences.get_preference(
context, "abs_require_module", formsemestre_id
)
nt = context.Notes._getNotesCache().get_NotesTable(
context.Notes, formsemestre_id
)
ues = nt.get_ues(etudid=etudid)
if require_module:
menu_module = """
<script type="text/javascript">
function form_enable_disable() {
if ( $("select#sel_moduleimpl_id").val() == "NULL" ) {
$("#butsubmit").prop("disabled", true);
} else {
$("#butsubmit").prop("disabled", false);
};
}
$(document).ready(function() {
form_enable_disable();
});
</script>
<p>Module:
<select id="sel_moduleimpl_id" name="moduleimpl_id"
onChange="form_enable_disable();">"""
else:
menu_module = (
"""<p>Module: <select id="sel_moduleimpl_id" name="moduleimpl_id">"""
)
menu_module += """<option value="NULL" selected>(Module)</option>"""
for ue in ues:
modimpls = nt.get_modimpls(ue_id=ue["ue_id"])
for modimpl in modimpls:
menu_module += (
"""<option value="%(modimpl_id)s">%(modname)s</option>\n"""
% {
"modimpl_id": modimpl["moduleimpl_id"],
"modname": modimpl["module"]["code"],
}
)
menu_module += """</select></p>"""
H = [
html_sco_header.sco_header(
context,
REQUEST,
page_title="Signalement d'une absence pour %(nomprenom)s" % etud,
),
"""<table><tr><td>
<h2>Signalement d'une absence pour %(nomprenom)s</h2>
</td><td>
"""
% etud,
"""<a href="%s/ficheEtud?etudid=%s">""" % (scu.ScoURL(), etud["etudid"]),
sco_photos.etud_photo_html(
context,
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
"""</a></td></tr></table>""",
"""
<form action="doSignaleAbsence" method="get">
<input type="hidden" name="etudid" value="%(etudid)s">
<p>
<table><tr>
<td>Date début : </td>
<td><input type="text" name="datedebut" size="10" class="datepicker"/> <em>j/m/a</em></td>
<td>&nbsp;&nbsp;&nbsp;Date fin (optionnelle):</td>
<td><input type="text" name="datefin" size="10" class="datepicker"/> <em>j/m/a</em></td>
</tr>
</table>
<br/>
<input type="radio" name="demijournee" value="2" checked>Journée(s)
&nbsp;<input type="radio" name="demijournee" value="1">Matin(s)
&nbsp;<input type="radio" name="demijournee" value="0">Après-midi
%(menu_module)s
<p>
<input type="checkbox" name="estjust"/>Absence justifiée.
<br/>
Raison: <input type="text" name="description" size="42"/> (optionnel)
</p>
<p>
<input id="butsubmit" type="submit" value="Envoyer" disable="%(disabled)s"/>
<em>
<p>Seuls les modules du semestre en cours apparaissent.</p>
<p>Évitez de saisir une absence pour un module qui n'est pas en place à cette date.</p>
<p>Toutes les dates sont au format jour/mois/annee.</p>
</em>
</form>
"""
% {
"etudid": etud["etudid"],
"menu_module": menu_module,
"disabled": "disabled" if disabled else "",
},
html_sco_header.sco_footer(context, REQUEST),
]
return "\n".join(H)
def doJustifAbsence(
context,
datedebut,
datefin,
demijournee,
description=None,
etudid=False,
REQUEST=None,
): # etudid implied
"""Justification d'une absence
Args:
datedebut: dd/mm/yyyy
datefin: dd/mm/yyyy (non incluse)
demijournee: 2 si journée complète, 1 matin, 0 après-midi
estjust: absence justifiée
description: str
etudid: etudiant concerné. Si non spécifié, cherche dans REQUEST.form
"""
etud = sco_etud.get_etud_info(filled=1, etudid=etudid, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
description_abs = description
dates = sco_abs.DateRangeISO(context, datedebut, datefin)
nbadded = 0
demijournee = int(demijournee)
for jour in dates:
if demijournee == 2:
sco_abs.add_justif(
context,
etudid=etudid,
jour=jour,
matin=False,
REQUEST=REQUEST,
description=description_abs,
)
sco_abs.add_justif(
context,
etudid=etudid,
jour=jour,
matin=True,
REQUEST=REQUEST,
description=description_abs,
)
nbadded += 2
else:
sco_abs.add_justif(
context,
etudid=etudid,
jour=jour,
matin=demijournee,
REQUEST=REQUEST,
description=description_abs,
)
nbadded += 1
#
H = [
html_sco_header.sco_header(
context,
REQUEST,
page_title="Justification d'une absence pour %(nomprenom)s" % etud,
),
"""<h2>Justification d'absences</h2>""",
]
if dates:
H.append(
"""<p>Ajout de %d <b>justifications</b> du %s au %s</p>"""
% (nbadded, datedebut, datefin)
)
else:
H.append(
"""<p class="warning">Aucune date ouvrable entre le %s et le %s !</p>"""
% (datedebut, datefin)
)
H.append(
"""<ul><li><a href="JustifAbsenceEtud?etudid=%(etudid)s">Autre justification pour <b>%(nomprenom)s</b></a></li>
<li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Signaler une absence</a></li>
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses absences</a></li>
<li><a href="ListeAbsEtud?etudid=%(etudid)s">Liste de ses absences</a></li>
</ul>
<hr>"""
% etud
)
H.append(sco_find_etud.form_search_etud(context, REQUEST))
H.append(html_sco_header.sco_footer(context, REQUEST))
return "\n".join(H)
def JustifAbsenceEtud(context, REQUEST=None): # etudid implied
"""Formulaire individuel simple de justification d'une absence"""
# brute-force portage from very old dtml code ...
etud = sco_etud.get_etud_info(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
H = [
html_sco_header.sco_header(
context,
REQUEST,
page_title="Justification d'une absence pour %(nomprenom)s" % etud,
),
"""<table><tr><td>
<h2>Justification d'une absence pour %(nomprenom)s</h2>
</td><td>
"""
% etud,
"""<a href="%s/ficheEtud?etudid=%s">""" % (scu.ScoURL(), etud["etudid"]),
sco_photos.etud_photo_html(
context,
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
"""</a></td></tr></table>""",
"""
<form action="doJustifAbsence" method="get">
<input type="hidden" name="etudid" value="%(etudid)s">
<p>
<table><tr>
<td>Date d&eacute;but : </td>
<td>
<input type="text" name="datedebut" size="10" class="datepicker"/>
</td>
<td>&nbsp;&nbsp;&nbsp;Date Fin (optionnel):</td>
<td><input type="text" name="datefin" size="10" class="datepicker"/></td>
</tr>
</table>
<br/>
<input type="radio" name="demijournee" value="2" checked>Journée(s)
&nbsp;<input type="radio" name="demijournee" value="1">Matin(s)
&nbsp;<input type="radio" name="demijournee" value="0">Apr&egrave;s midi
<br/><br/>
Raison: <input type="text" name="description" size="42"/> (optionnel)
<p>
<input type="submit" value="Envoyer">
</form> """
% etud,
html_sco_header.sco_footer(context, REQUEST),
]
return "\n".join(H)
def doAnnuleAbsence(
context, datedebut, datefin, demijournee, etudid=False, REQUEST=None
): # etudid implied
"""Annulation des absences pour une demi journée"""
etud = sco_etud.get_etud_info(filled=1, etudid=etudid, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
dates = sco_abs.DateRangeISO(context, datedebut, datefin)
nbadded = 0
demijournee = int(demijournee)
for jour in dates:
if demijournee == 2:
sco_abs.annule_absence(context, etudid, jour, False, REQUEST=REQUEST)
sco_abs.annule_absence(context, etudid, jour, True, REQUEST=REQUEST)
nbadded += 2
else:
sco_abs.annule_absence(context, etudid, jour, demijournee, REQUEST=REQUEST)
nbadded += 1
#
H = [
html_sco_header.sco_header(
context,
REQUEST,
page_title="Annulation d'une absence pour %(nomprenom)s" % etud,
),
"""<h2>Annulation d'absences pour %(nomprenom)s</h2>""" % etud,
]
if dates:
H.append(
"<p>Annulation sur %d demi-journées du %s au %s"
% (nbadded, datedebut, datefin)
)
else:
H.append(
"""<p class="warning">Aucune date ouvrable entre le %s et le %s !</p>"""
% (datedebut, datefin)
)
H.append(
"""<ul><li><a href="AnnuleAbsenceEtud?etudid=%(etudid)s">Annulation d'une
autre absence pour <b>%(nomprenom)s</b></a></li>
<li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Ajout d'une absence</a></li>
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses absences</a></li>
</ul>
<hr>"""
% etud
)
H.append(sco_find_etud.form_search_etud(context, REQUEST))
H.append(html_sco_header.sco_footer(context, REQUEST))
return "\n".join(H)
def AnnuleAbsenceEtud(context, REQUEST=None): # etudid implied
"""Formulaire individuel simple d'annulation d'une absence"""
# brute-force portage from very old dtml code ...
etud = sco_etud.get_etud_info(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
H = [
html_sco_header.sco_header(
context,
REQUEST,
page_title="Annulation d'une absence pour %(nomprenom)s" % etud,
),
"""<table><tr><td>
<h2><font color="#FF0000">Annulation</font> d'une absence pour %(nomprenom)s</h2>
</td><td>
"""
% etud, # "
"""<a href="%s/ficheEtud?etudid=%s">""" % (scu.ScoURL(), etud["etudid"]),
sco_photos.etud_photo_html(
context,
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
"""</a></td></tr></table>""",
"""<p>A n'utiliser que suite à une erreur de saisie ou lorsqu'il s'avère que l'étudiant était en fait présent. </p>
<p><font color="#FF0000">Si plusieurs modules sont affectés, les absences seront toutes effacées. </font></p>
"""
% etud,
"""<table frame="border" border="1"><tr><td>
<form action="doAnnuleAbsence" method="get">
<input type="hidden" name="etudid" value="%(etudid)s">
<p>
<table><tr>
<td>Date d&eacute;but : </td>
<td>
<input type="text" name="datedebut" size="10" class="datepicker"/> <em>j/m/a</em>
</td>
<td>&nbsp;&nbsp;&nbsp;Date Fin (optionnel):</td>
<td>
<input type="text" name="datefin" size="10" class="datepicker"/> <em>j/m/a</em>
</td>
</tr>
</table>
<input type="radio" name="demijournee" value="2" checked>journ&eacute;e(s)
&nbsp;<input type="radio" name="demijournee" value="1">Matin(s)
&nbsp;<input type="radio" name="demijournee" value="0">Apr&egrave;s midi
<p>
<input type="submit" value="Supprimer les absences">
</form>
</td></tr>
<tr><td>
<form action="doAnnuleJustif" method="get">
<input type="hidden" name="etudid" value="%(etudid)s">
<p>
<table><tr>
<td>Date d&eacute;but : </td>
<td>
<input type="text" name="datedebut0" size="10" class="datepicker"/> <em>j/m/a</em>
</td>
<td>&nbsp;&nbsp;&nbsp;Date Fin (optionnel):</td>
<td>
<input type="text" name="datefin0" size="10" class="datepicker"/> <em>j/m/a</em>
</td>
</tr>
</table>
<p>
<input type="radio" name="demijournee" value="2" checked>journ&eacute;e(s)
&nbsp;<input type="radio" name="demijournee" value="1">Matin(s)
&nbsp;<input type="radio" name="demijournee" value="0">Apr&egrave;s midi
<p>
<input type="submit" value="Supprimer les justificatifs">
<i>(utiliser ceci en cas de justificatif erron&eacute; saisi ind&eacute;pendemment d'une absence)</i>
</form>
</td></tr></table>"""
% etud,
html_sco_header.sco_footer(context, REQUEST),
]
return "\n".join(H)
def doAnnuleJustif(
context, datedebut0, datefin0, demijournee, REQUEST=None
): # etudid implied
"""Annulation d'une justification"""
etud = sco_etud.get_etud_info(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
dates = sco_abs.DateRangeISO(context, datedebut0, datefin0)
nbadded = 0
demijournee = int(demijournee)
for jour in dates:
# Attention: supprime matin et après-midi
if demijournee == 2:
sco_abs.annule_justif(context, etudid, jour, False, REQUEST=REQUEST)
sco_abs.annule_justif(context, etudid, jour, True, REQUEST=REQUEST)
nbadded += 2
else:
sco_abs.annule_justif(context, etudid, jour, demijournee, REQUEST=REQUEST)
nbadded += 1
#
H = [
html_sco_header.sco_header(
context,
REQUEST,
page_title="Annulation d'une justification pour %(nomprenom)s" % etud,
),
"""<h2>Annulation de justifications pour %(nomprenom)s</h2>""" % etud,
]
if dates:
H.append(
"<p>Annulation sur %d demi-journées du %s au %s"
% (nbadded, datedebut0, datefin0)
)
else:
H.append(
"""<p class="warning">Aucune date ouvrable entre le %s et le %s !</p>"""
% (datedebut0, datefin0)
)
H.append(
"""<ul><li><a href="AnnuleAbsenceEtud?etudid=%(etudid)s">Annulation d'une
autre absence pour <b>%(nomprenom)s</b></a></li>
<li><a href="SignaleAbsenceEtud?etudid=%(etudid)s">Ajout d'une absence</a></li>
<li><a href="CalAbs?etudid=%(etudid)s">Calendrier de ses absences</a></li>
</ul>
<hr>"""
% etud
)
H.append(sco_find_etud.form_search_etud(context, REQUEST))
H.append(html_sco_header.sco_footer(context, REQUEST))
return "\n".join(H)
def AnnuleAbsencesDatesNoJust(context, etudid, dates, moduleimpl_id=None, REQUEST=None):
"""Supprime les absences aux dates indiquées
mais ne supprime pas les justificatifs.
"""
# log('AnnuleAbsencesDatesNoJust: moduleimpl_id=%s' % moduleimpl_id)
if not dates:
return
date0 = dates[0]
if len(date0.split(":")) == 2:
# am/pm is present
for date in dates:
jour, ampm = date.split(":")
if ampm == "am":
matin = 1
elif ampm == "pm":
matin = 0
else:
raise ValueError("invalid ampm !")
sco_abs.annule_absence(context, etudid, jour, matin, moduleimpl_id, REQUEST)
return
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
# supr les absences non justifiees
for date in dates:
cursor.execute(
"delete from absences where etudid=%(etudid)s and (not estjust) and jour=%(date)s and moduleimpl_id=%(moduleimpl_id)s",
vars(),
)
sco_abs.invalidateAbsEtudDate(context, etudid, date)
# s'assure que les justificatifs ne sont pas "absents"
for date in dates:
cursor.execute(
"update absences set estabs=FALSE where etudid=%(etudid)s and jour=%(date)s and moduleimpl_id=%(moduleimpl_id)s",
vars(),
)
if dates:
date0 = dates[0]
else:
date0 = None
if len(dates) > 1:
date1 = dates[1]
else:
date1 = None
logdb(
REQUEST,
cnx,
"AnnuleAbsencesDatesNoJust",
etudid=etudid,
msg="%s - %s - %s" % (date0, date1, moduleimpl_id),
)
cnx.commit()
def EtatAbsences(context, REQUEST=None):
"""Etat des absences: choix du groupe"""
# crude portage from 1999 DTML
H = [
html_sco_header.sco_header(context, REQUEST, page_title="Etat des absences"),
"""<h2>Etat des absences pour un groupe</h2>
<form action="EtatAbsencesGr" method="GET">""",
formChoixSemestreGroupe(context),
"""<input type="submit" name="" value=" OK " width=100>
<table><tr><td>Date de début (j/m/a) : </td><td>
<input type="text" name="debut" size="10" value="01/09/%s" class="datepicker"/>
</td></tr><tr><td>Date de fin : </td><td>
<input type="text" name="fin" size="10" value="%s" class="datepicker"/>
</td></tr></table>
</form>"""
% (scu.AnneeScolaire(REQUEST), datetime.datetime.now().strftime("%d/%m/%Y")),
html_sco_header.sco_footer(context, REQUEST),
]
return "\n".join(H)
def formChoixSemestreGroupe(context, all=False):
"""partie de formulaire pour le choix d'un semestre et d'un groupe.
Si all, donne tous les semestres (même ceux verrouillés).
"""
# XXX assez primitif, à ameliorer TOTALEMENT OBSOLETE !
if all:
sems = sco_formsemestre.do_formsemestre_list(context)
else:
sems = sco_formsemestre.do_formsemestre_list(context, args={"etat": "1"})
if not sems:
raise ScoValueError("aucun semestre !")
H = ['<select name="group_ids">']
for sem in sems:
for p in sco_groups.get_partitions_list(context, sem["formsemestre_id"]):
for group in sco_groups.get_partition_groups(context, p):
if group["group_name"]:
group_tit = "%s %s" % (p["partition_name"], group["group_name"])
else:
group_tit = "tous"
H.append(
'<option value="%s">%s: %s</option>'
% (group["group_id"], sem["titremois"], group_tit)
)
H.append("</select>")
return "\n".join(H)
def CalAbs(context, REQUEST=None): # etud implied
"""Calendrier des absences d'un etudiant"""
# crude portage from 1999 DTML
etud = sco_etud.get_etud_info(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"]
anneescolaire = int(scu.AnneeScolaire(REQUEST))
datedebut = str(anneescolaire) + "-08-31"
datefin = str(anneescolaire + 1) + "-07-31"
nbabs = sco_abs.CountAbs(context, etudid=etudid, debut=datedebut, fin=datefin)
nbabsjust = context.CountAbsJust(etudid=etudid, debut=datedebut, fin=datefin)
events = []
for a in sco_abs.ListeAbsJust(context, etudid=etudid, datedebut=datedebut):
events.append(
(str(a["jour"]), "a", "#F8B7B0", "", a["matin"], a["description"])
)
for a in sco_abs.ListeAbsNonJust(context, etudid=etudid, datedebut=datedebut):
events.append(
(str(a["jour"]), "A", "#EE0000", "", a["matin"], a["description"])
)
justifs_noabs = sco_abs.ListeJustifs(
context, etudid=etudid, datedebut=datedebut, only_no_abs=True
)
for a in justifs_noabs:
events.append(
(str(a["jour"]), "X", "#8EA2C6", "", a["matin"], a["description"])
)
CalHTML = sco_abs.YearTable(context, anneescolaire, events=events, halfday=1)
#
H = [
html_sco_header.sco_header(
context,
REQUEST,
page_title="Calendrier des absences de %(nomprenom)s" % etud,
cssstyles=["css/calabs.css"],
),
"""<table><tr><td><h2>Absences de %(nomprenom)s (%(inscription)s)</h2><p>"""
% etud,
"""<b><font color="#EE0000">A : absence NON justifiée</font><br/>
<font color="#F8B7B0">a : absence justifiée</font><br/>
<font color="#8EA2C6">X : justification sans absence</font><br/>
%d absences sur l'année, dont %d justifiées (soit %d non justifiées)</b> <em>(%d justificatifs inutilisés)</em>
</p>
"""
% (nbabs, nbabsjust, nbabs - nbabsjust, len(justifs_noabs)),
"""</td>
<td><a href="%s/ficheEtud?etudid=%s">%s</a></td>
</tr>
</table>"""
% (
scu.ScoURL(),
etudid,
sco_photos.etud_photo_html(
context,
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
),
),
CalHTML,
"""<form method="GET" action="CalAbs" name="f">""",
"""<input type="hidden" name="etudid" value="%s"/>""" % etudid,
"""Année scolaire %s-%s""" % (anneescolaire, anneescolaire + 1),
"""&nbsp;&nbsp;Changer année: <select name="sco_year" onchange="document.f.submit()">""",
]
for y in range(anneescolaire, anneescolaire - 10, -1):
H.append("""<option value="%s" """ % y)
if y == anneescolaire:
H.append("selected")
H.append(""">%s</option>""" % y)
H.append("""</select></form>""")
H.append(html_sco_header.sco_footer(context, REQUEST))
return "\n".join(H)
def ListeAbsEtud(
context,
etudid,
with_evals=True, # indique les evaluations aux dates d'absences
format="html",
absjust_only=0, # si vrai, renvoie table absences justifiées
REQUEST=None,
):
"""Liste des absences d'un étudiant sur l'année en cours
En format 'html': page avec deux tableaux (non justifiées et justifiées).
En format json, xml, xls ou pdf: l'un ou l'autre des table, suivant absjust_only.
En format 'text': texte avec liste d'absences (pour mails).
"""
absjust_only = int(absjust_only) # si vrai, table absjust seule (export xls ou pdf)
datedebut = "%s-08-31" % scu.AnneeScolaire(REQUEST)
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
# Liste des absences et titres colonnes tables:
titles, columns_ids, absnonjust, absjust = _TablesAbsEtud(
context, etudid, datedebut, with_evals=with_evals, format=format
)
if REQUEST:
base_url_nj = "%s?etudid=%s&absjust_only=0" % (REQUEST.URL0, etudid)
base_url_j = "%s?etudid=%s&absjust_only=1" % (REQUEST.URL0, etudid)
else:
base_url_nj = base_url_j = ""
tab_absnonjust = GenTable(
titles=titles,
columns_ids=columns_ids,
rows=absnonjust,
html_class="table_leftalign",
table_id="tab_absnonjust",
base_url=base_url_nj,
filename="abs_" + scu.make_filename(etud["nomprenom"]),
caption="Absences non justifiées de %(nomprenom)s" % etud,
preferences=sco_preferences.SemPreferences(
context,
),
)
tab_absjust = GenTable(
titles=titles,
columns_ids=columns_ids,
rows=absjust,
html_class="table_leftalign",
table_id="tab_absjust",
base_url=base_url_j,
filename="absjust_" + scu.make_filename(etud["nomprenom"]),
caption="Absences justifiées de %(nomprenom)s" % etud,
preferences=sco_preferences.SemPreferences(
context,
),
)
# Formats non HTML et demande d'une seule table:
if format != "html" and format != "text":
if absjust_only == 1:
return tab_absjust.make_page(context, format=format, REQUEST=REQUEST)
else:
return tab_absnonjust.make_page(context, format=format, REQUEST=REQUEST)
if format == "html":
# Mise en forme HTML:
H = []
H.append(
html_sco_header.sco_header(
context, REQUEST, page_title="Absences de %s" % etud["nomprenom"]
)
)
H.append(
"""<h2>Absences de %s (à partir du %s)</h2>"""
% (etud["nomprenom"], ndb.DateISOtoDMY(datedebut))
)
if len(absnonjust):
H.append("<h3>%d absences non justifiées:</h3>" % len(absnonjust))
H.append(tab_absnonjust.html())
else:
H.append("""<h3>Pas d'absences non justifiées</h3>""")
if len(absjust):
H.append("""<h3>%d absences justifiées:</h3>""" % len(absjust))
H.append(tab_absjust.html())
else:
H.append("""<h3>Pas d'absences justifiées</h3>""")
return "\n".join(H) + html_sco_header.sco_footer(context, REQUEST)
elif format == "text":
T = []
if not len(absnonjust) and not len(absjust):
T.append(
"""--- Pas d'absences enregistrées depuis le %s"""
% ndb.DateISOtoDMY(datedebut)
)
else:
T.append(
"""--- Absences enregistrées à partir du %s:"""
% ndb.DateISOtoDMY(datedebut)
)
T.append("\n")
if len(absnonjust):
T.append("* %d absences non justifiées:" % len(absnonjust))
T.append(tab_absnonjust.text())
if len(absjust):
T.append("* %d absences justifiées:" % len(absjust))
T.append(tab_absjust.text())
return "\n".join(T)
else:
raise ValueError("Invalid format !")
def _TablesAbsEtud(
context,
etudid,
datedebut,
with_evals=True,
format="html",
absjust_only=0,
REQUEST=None,
):
"""Tables des absences justifiees et non justifiees d'un étudiant sur l'année en cours"""
absjust = sco_abs.ListeAbsJust(context, etudid=etudid, datedebut=datedebut)
absnonjust = sco_abs.ListeAbsNonJust(context, etudid=etudid, datedebut=datedebut)
# examens ces jours là ?
if with_evals:
cnx = ndb.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
for a in absnonjust + absjust:
cursor.execute(
"""select eval.*
from notes_evaluation eval, notes_moduleimpl_inscription mi, notes_moduleimpl m
where eval.jour = %(jour)s and eval.moduleimpl_id = m.moduleimpl_id
and mi.moduleimpl_id = m.moduleimpl_id and mi.etudid = %(etudid)s""",
{"jour": a["jour"].strftime("%Y-%m-%d"), "etudid": etudid},
)
a["evals"] = cursor.dictfetchall()
cursor.execute(
"""SELECT mi.moduleimpl_id
from absences abs, notes_moduleimpl_inscription mi, notes_moduleimpl m
where abs.matin = %(matin)s and abs.jour = %(jour)s and abs.etudid=%(etudid)s and abs.moduleimpl_id=mi.moduleimpl_id and mi.moduleimpl_id=m.moduleimpl_id
and mi.etudid = %(etudid)s""",
{
"matin": bool(a["matin"]),
"jour": a["jour"].strftime("%Y-%m-%d"),
"etudid": etudid,
},
)
a["absent"] = cursor.dictfetchall()
def matin(x):
if x:
return "matin"
else:
return "après-midi"
def descr_exams(a):
if not a.has_key("evals"):
return ""
ex = []
for ev in a["evals"]:
mod = sco_moduleimpl.do_moduleimpl_withmodule_list(
context.Notes, moduleimpl_id=ev["moduleimpl_id"]
)[0]
if format == "html":
ex.append(
'<a href="Notes/moduleimpl_status?moduleimpl_id=%s">%s</a>'
% (mod["moduleimpl_id"], mod["module"]["code"])
)
else:
ex.append(mod["module"]["code"])
if ex:
return ", ".join(ex)
return ""
def descr_abs(a):
ex = []
for ev in a.get("absent", []):
mod = sco_moduleimpl.do_moduleimpl_withmodule_list(
context.Notes, moduleimpl_id=ev["moduleimpl_id"]
)[0]
if format == "html":
ex.append(
'<a href="Notes/moduleimpl_status?moduleimpl_id=%s">%s</a>'
% (mod["moduleimpl_id"], mod["module"]["code"])
)
else:
ex.append(mod["module"]["code"])
if ex:
return ", ".join(ex)
return ""
# ajoute date formatée et évaluations
for L in (absnonjust, absjust):
for a in L:
if with_evals:
a["exams"] = descr_exams(a)
a["datedmy"] = a["jour"].strftime("%d/%m/%Y")
a["ampm"] = int(a["matin"])
a["matin"] = matin(a["matin"])
index = a["description"].find(")")
if index != -1:
a["motif"] = a["description"][1:index]
else:
a["motif"] = ""
a["description"] = descr_abs(a) or ""
# ajoute lien pour justifier
if format == "html":
for a in absnonjust:
a["justlink"] = "<em>justifier</em>"
a[
"_justlink_target"
] = "doJustifAbsence?etudid=%s&datedebut=%s&datefin=%s&demijournee=%s" % (
etudid,
a["datedmy"],
a["datedmy"],
a["ampm"],
)
#
titles = {
"datedmy": "Date",
"matin": "",
"exams": "Examens ce jour",
"justlink": "",
"description": "Modules",
"motif": "Motif",
}
columns_ids = ["datedmy", "matin"]
if format in ("json", "xml"):
columns_ids += ["jour", "ampm"]
if with_evals:
columns_ids.append("exams")
columns_ids.append("description")
columns_ids.append("motif")
if format == "html":
columns_ids.append("justlink")
return titles, columns_ids, absnonjust, absjust
def index_html(context, REQUEST=None):
"""Gestionnaire absences, page principale"""
# crude portage from 1999 DTML
sems = sco_formsemestre.do_formsemestre_list(context)
authuser = REQUEST.AUTHENTICATED_USER
H = [
html_sco_header.sco_header(
context,
REQUEST,
page_title="Gestion des absences",
cssstyles=["css/calabs.css"],
javascripts=["js/calabs.js"],
),
"""<h2>Gestion des Absences</h2>""",
]
if not sems:
H.append(
"""<p class="warning">Aucun semestre défini (ou aucun groupe d'étudiant)</p>"""
)
else:
H.append(
"""<ul><li><a href="EtatAbsences">Afficher l'état des absences (pour tout un groupe)</a></li>"""
)
if sco_preferences.get_preference(context, "handle_billets_abs"):
H.append(
"""<li><a href="listeBillets">Traitement des billets d'absence en attente</a></li>"""
)
H.append(
"""<p>Pour signaler, annuler ou justifier une absence, choisissez d'abord l'étudiant concerné:</p>"""
)
H.append(sco_find_etud.form_search_etud(context, REQUEST))
if authuser.has_permission(Permission.ScoAbsChange):
H.extend(
(
"""<hr/>
<form action="SignaleAbsenceGrHebdo" id="formw">
<input type="hidden" name="destination" value="%s"/>
<p>
<span style="font-weight: bold; font-size:120%%;">
Saisie par semaine </span> - Choix du groupe:
<input name="datelundi" type="hidden" value="x"/>
"""
% REQUEST.URL0,
formChoixSemestreGroupe(context),
"</p>",
cal_select_week(context, REQUEST=REQUEST),
"""<p class="help">Sélectionner le groupe d'étudiants, puis cliquez sur une semaine pour
saisir les absences de toute cette semaine.</p>
</form>""",
)
)
else:
H.append(
"""<p class="scoinfo">Vous n'avez pas l'autorisation d'ajouter, justifier ou supprimer des absences.</p>"""
)
H.append(html_sco_header.sco_footer(context, REQUEST))
return "\n".join(H)
def cal_select_week(context, year=None, REQUEST=None):
"display calendar allowing week selection"
if not year:
year = scu.AnneeScolaire(REQUEST)
sems = sco_formsemestre.do_formsemestre_list(context)
if not sems:
js = ""
else:
js = 'onmouseover="highlightweek(this);" onmouseout="deselectweeks();" onclick="wclick(this);"'
C = sco_abs.YearTable(context, int(year), dayattributes=js)
return C