Merge branch 'master' of https://scodoc.org/git/viennet/ScoDoc into ScoDoc8

This commit is contained in:
Emmanuel Viennet 2021-01-10 18:57:17 +01:00
commit fcd34c3bdf
19 changed files with 771 additions and 698 deletions

View File

@ -48,6 +48,13 @@ import urllib
import datetime import datetime
import jaxml import jaxml
import cgi import cgi
import string
import re
import time
import calendar
from mx.DateTime import DateTime as mxDateTime
from mx.DateTime.ISO import ParseDateTimeUTC
# --------------- # ---------------
from sco_zope import * from sco_zope import *
@ -68,10 +75,8 @@ import sco_groups_view
import sco_excel import sco_excel
import sco_abs_notification, sco_abs_views import sco_abs_notification, sco_abs_views
import sco_compute_moy import sco_compute_moy
import string, re import sco_abs
import time, calendar from sco_abs import ddmmyyyy
from mx.DateTime import DateTime as mxDateTime
from mx.DateTime.ISO import ParseDateTimeUTC
def _toboolean(x): def _toboolean(x):
@ -84,189 +89,6 @@ def _toboolean(x):
return False return False
def MonthNbDays(month, year):
"returns nb of days in month"
if month > 7:
month = month + 1
if month % 2:
return 31
elif month == 2:
if calendar.isleap(year):
return 29
else:
return 28
else:
return 30
class ddmmyyyy:
"""immutable dates"""
def __init__(self, date=None, fmt="ddmmyyyy", work_saturday=False):
self.work_saturday = work_saturday
if date is None:
return
try:
if fmt == "ddmmyyyy":
self.day, self.month, self.year = string.split(date, "/")
elif fmt == "iso":
self.year, self.month, self.day = string.split(date, "-")
else:
raise ValueError("invalid format spec. (%s)" % fmt)
self.year = string.atoi(self.year)
self.month = string.atoi(self.month)
self.day = string.atoi(self.day)
except:
raise ScoValueError("date invalide: %s" % date)
# accept years YYYY or YY, uses 1970 as pivot
if self.year < 1970:
if self.year > 100:
raise ScoInvalidDateError("Année invalide: %s" % self.year)
if self.year < 70:
self.year = self.year + 2000
else:
self.year = self.year + 1900
if self.month < 1 or self.month > 12:
raise ScoInvalidDateError("Mois invalide: %s" % self.month)
if self.day < 1 or self.day > MonthNbDays(self.month, self.year):
raise ScoInvalidDateError("Jour invalide: %s" % self.day)
# weekday in 0-6, where 0 is monday
self.weekday = calendar.weekday(self.year, self.month, self.day)
self.time = time.mktime((self.year, self.month, self.day, 0, 0, 0, 0, 0, 0))
def iswork(self):
"returns true if workable day"
if self.work_saturday:
nbdays = 6
else:
nbdays = 5
if (
self.weekday >= 0 and self.weekday < nbdays
): # monday-friday or monday-saturday
return 1
else:
return 0
def __repr__(self):
return "'%02d/%02d/%04d'" % (self.day, self.month, self.year)
def __str__(self):
return "%02d/%02d/%04d" % (self.day, self.month, self.year)
def ISO(self):
"iso8601 representation of the date"
return "%04d-%02d-%02d" % (self.year, self.month, self.day)
def next(self, days=1):
"date for the next day (nota: may be a non workable day)"
day = self.day + days
month = self.month
year = self.year
while day > MonthNbDays(month, year):
day = day - MonthNbDays(month, year)
month = month + 1
if month > 12:
month = 1
year = year + 1
return self.__class__(
"%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
)
def prev(self, days=1):
"date for previous day"
day = self.day - days
month = self.month
year = self.year
while day <= 0:
month = month - 1
if month == 0:
month = 12
year = year - 1
day = day + MonthNbDays(month, year)
return self.__class__(
"%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
)
def next_monday(self):
"date of next monday"
return self.next((7 - self.weekday) % 7)
def prev_monday(self):
"date of last monday, but on sunday, pick next monday"
if self.weekday == 6:
return self.next_monday()
else:
return self.prev(self.weekday)
def __cmp__(self, other):
"""return a negative integer if self < other,
zero if self == other, a positive integer if self > other"""
return int(self.time - other.time)
def __hash__(self):
"we are immutable !"
return hash(self.time) ^ hash(str(self))
# d = ddmmyyyy( '21/12/99' )
def YearTable(
context,
year,
events=[],
firstmonth=9,
lastmonth=7,
halfday=0,
dayattributes="",
pad_width=8,
):
"""Generate a calendar table
events = list of tuples (date, text, color, href [,halfday])
where date is a string in ISO format (yyyy-mm-dd)
halfday is boolean (true: morning, false: afternoon)
text = text to put in calendar (must be short, 1-5 cars) (optional)
if halfday, generate 2 cells per day (morning, afternoon)
"""
T = [
'<table id="maincalendar" class="maincalendar" border="3" cellpadding="1" cellspacing="1" frame="box">'
]
T.append("<tr>")
month = firstmonth
while 1:
T.append('<td valign="top">')
T.append(MonthTableHead(month))
T.append(
MonthTableBody(
month,
year,
events,
halfday,
dayattributes,
context.is_work_saturday(),
pad_width=pad_width,
)
)
T.append(MonthTableTail())
T.append("</td>")
if month == lastmonth:
break
month = month + 1
if month > 12:
month = 1
year = year + 1
T.append("</table>")
return string.join(T, "\n")
# ---------------
class ZAbsences( class ZAbsences(
ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit
): ):
@ -373,7 +195,7 @@ class ZAbsences(
% vars(), % vars(),
) )
cnx.commit() cnx.commit()
invalidateAbsEtudDate(self, etudid, jour) sco_abs.invalidateAbsEtudDate(self, etudid, jour)
sco_abs_notification.abs_notify(self, etudid, jour) sco_abs_notification.abs_notify(self, etudid, jour)
def _AddJustif(self, etudid, jour, matin, REQUEST, description=None): def _AddJustif(self, etudid, jour, matin, REQUEST, description=None):
@ -396,7 +218,7 @@ class ZAbsences(
msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(), msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(),
) )
cnx.commit() cnx.commit()
invalidateAbsEtudDate(self, etudid, jour) sco_abs.invalidateAbsEtudDate(self, etudid, jour)
def _AnnuleAbsence(self, etudid, jour, matin, moduleimpl_id=None, REQUEST=None): def _AnnuleAbsence(self, etudid, jour, matin, moduleimpl_id=None, REQUEST=None):
"""Annule une absence ds base """Annule une absence ds base
@ -419,7 +241,7 @@ class ZAbsences(
% vars(), % vars(),
) )
cnx.commit() cnx.commit()
invalidateAbsEtudDate(self, etudid, jour) sco_abs.invalidateAbsEtudDate(self, etudid, jour)
def _AnnuleJustif(self, etudid, jour, matin, REQUEST=None): def _AnnuleJustif(self, etudid, jour, matin, REQUEST=None):
"Annule un justificatif" "Annule un justificatif"
@ -443,7 +265,7 @@ class ZAbsences(
msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(), msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(),
) )
cnx.commit() cnx.commit()
invalidateAbsEtudDate(self, etudid, jour) sco_abs.invalidateAbsEtudDate(self, etudid, jour)
# Fonction inutile à supprimer (gestion moduleimpl_id incorrecte): # Fonction inutile à supprimer (gestion moduleimpl_id incorrecte):
# def _AnnuleAbsencesPeriodNoJust(self, etudid, datedebut, datefin, # def _AnnuleAbsencesPeriodNoJust(self, etudid, datedebut, datefin,
@ -462,8 +284,8 @@ class ZAbsences(
# logdb(REQUEST, cnx, 'AnnuleAbsencesPeriodNoJust', etudid=etudid, # logdb(REQUEST, cnx, 'AnnuleAbsencesPeriodNoJust', etudid=etudid,
# msg='%(datedebut)s - %(datefin)s - (moduleimpl_id)s'%vars()) # msg='%(datedebut)s - %(datefin)s - (moduleimpl_id)s'%vars())
# cnx.commit() # cnx.commit()
# invalidateAbsEtudDate(self, etudid, datedebut) # sco_abs.invalidateAbsEtudDate(self, etudid, datedebut)
# invalidateAbsEtudDate(self, etudid, datefin) # si un semestre commence apres datedebut et termine avant datefin, il ne sera pas invalide. Tant pis ;-) # sco_abs.invalidateAbsEtudDate(self, etudid, datefin) # si un semestre commence apres datedebut et termine avant datefin, il ne sera pas invalide. Tant pis ;-)
security.declareProtected(ScoAbsChange, "AnnuleAbsencesDatesNoJust") security.declareProtected(ScoAbsChange, "AnnuleAbsencesDatesNoJust")
@ -497,7 +319,7 @@ class ZAbsences(
"delete from absences where etudid=%(etudid)s and (not estjust) and jour=%(date)s and moduleimpl_id=%(moduleimpl_id)s", "delete from absences where etudid=%(etudid)s and (not estjust) and jour=%(date)s and moduleimpl_id=%(moduleimpl_id)s",
vars(), vars(),
) )
invalidateAbsEtudDate(self, etudid, date) sco_abs.invalidateAbsEtudDate(self, etudid, date)
# s'assure que les justificatifs ne sont pas "absents" # s'assure que les justificatifs ne sont pas "absents"
for date in dates: for date in dates:
cursor.execute( cursor.execute(
@ -855,7 +677,7 @@ class ZAbsences(
js = "" js = ""
else: else:
js = 'onmouseover="highlightweek(this);" onmouseout="deselectweeks();" onclick="wclick(this);"' js = 'onmouseover="highlightweek(this);" onmouseout="deselectweeks();" onclick="wclick(this);"'
C = YearTable(self, int(year), dayattributes=js) C = sco_abs.YearTable(self, int(year), dayattributes=js)
return C return C
# --- Misc tools.... ------------------ # --- Misc tools.... ------------------
@ -1352,7 +1174,7 @@ class ZAbsences(
else: else:
checked = "" checked = ""
# bulle lors du passage souris # bulle lors du passage souris
coljour = DAYNAMES[ coljour = sco_abs.DAYNAMES[
(calendar.weekday(int(date[:4]), int(date[5:7]), int(date[8:]))) (calendar.weekday(int(date[:4]), int(date[5:7]), int(date[8:])))
] ]
datecol = coljour + " " + date[8:] + "/" + date[5:7] + "/" + date[:4] datecol = coljour + " " + date[8:] + "/" + date[5:7] + "/" + date[:4]
@ -1798,7 +1620,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
justified = int(justified) justified = int(justified)
# #
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
billet_id = billet_absence_create( billet_id = sco_abs.billet_absence_create(
cnx, cnx,
{ {
"etudid": etud["etudid"], "etudid": etud["etudid"],
@ -1814,7 +1636,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
if REQUEST: if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE) REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
billets = billet_absence_list(cnx, {"billet_id": billet_id}) billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
tab = self._tableBillets(billets, etud=etud) tab = self._tableBillets(billets, etud=etud)
log( log(
"AddBilletAbsence: new billet_id=%s (%gs)" "AddBilletAbsence: new billet_id=%s (%gs)"
@ -1940,7 +1762,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
etud = etuds[0] etud = etuds[0]
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
billets = billet_absence_list(cnx, {"etudid": etud["etudid"]}) billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]})
tab = self._tableBillets(billets, etud=etud) tab = self._tableBillets(billets, etud=etud)
return tab.make_page(self, REQUEST=REQUEST, format=format) return tab.make_page(self, REQUEST=REQUEST, format=format)
@ -1960,7 +1782,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
def listeBillets(self, REQUEST=None): def listeBillets(self, REQUEST=None):
"""Page liste des billets non traités et formulaire recherche d'un billet""" """Page liste des billets non traités et formulaire recherche d'un billet"""
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
billets = billet_absence_list(cnx, {"etat": 0}) billets = sco_abs.billet_absence_list(cnx, {"etat": 0})
tab = self._tableBillets(billets) tab = self._tableBillets(billets)
T = tab.html() T = tab.html()
H = [ H = [
@ -1986,7 +1808,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
def deleteBilletAbsence(self, billet_id, REQUEST=None, dialog_confirmed=False): def deleteBilletAbsence(self, billet_id, REQUEST=None, dialog_confirmed=False):
"""Supprime un billet.""" """Supprime un billet."""
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
billets = billet_absence_list(cnx, {"billet_id": billet_id}) billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
if not billets: if not billets:
return REQUEST.RESPONSE.redirect( return REQUEST.RESPONSE.redirect(
"listeBillets?head_message=Billet%%20%s%%20inexistant !" % billet_id "listeBillets?head_message=Billet%%20%s%%20inexistant !" % billet_id
@ -2001,7 +1823,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
parameters={"billet_id": billet_id}, parameters={"billet_id": billet_id},
) )
billet_absence_delete(cnx, billet_id) sco_abs.billet_absence_delete(cnx, billet_id)
return REQUEST.RESPONSE.redirect("listeBillets?head_message=Billet%20supprimé") return REQUEST.RESPONSE.redirect("listeBillets?head_message=Billet%20supprimé")
@ -2050,7 +1872,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
n += 2 n += 2
# 2- change etat du billet # 2- change etat du billet
billet_absence_edit(cnx, {"billet_id": billet["billet_id"], "etat": 1}) sco_abs.billet_absence_edit(cnx, {"billet_id": billet["billet_id"], "etat": 1})
return n return n
@ -2059,7 +1881,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
def ProcessBilletAbsenceForm(self, billet_id, REQUEST=None): def ProcessBilletAbsenceForm(self, billet_id, REQUEST=None):
"""Formulaire traitement d'un billet""" """Formulaire traitement d'un billet"""
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
billets = billet_absence_list(cnx, {"billet_id": billet_id}) billets = sco_abs.billet_absence_list(cnx, {"billet_id": billet_id})
if not billets: if not billets:
return REQUEST.RESPONSE.redirect( return REQUEST.RESPONSE.redirect(
"listeBillets?head_message=Billet%%20%s%%20inexistant !" % billet_id "listeBillets?head_message=Billet%%20%s%%20inexistant !" % billet_id
@ -2134,7 +1956,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
'</div><p><a class="stdlink" href="listeBillets">Autre billets en attente</a></p><h4>Billets déclarés par %s</h4>' '</div><p><a class="stdlink" href="listeBillets">Autre billets en attente</a></p><h4>Billets déclarés par %s</h4>'
% (etud["nomprenom"]) % (etud["nomprenom"])
) )
billets = billet_absence_list(cnx, {"etudid": etud["etudid"]}) billets = sco_abs.billet_absence_list(cnx, {"etudid": etud["etudid"]})
tab = self._tableBillets(billets, etud=etud) tab = self._tableBillets(billets, etud=etud)
H.append(tab.html()) H.append(tab.html())
return "\n".join(H) + self.sco_footer(REQUEST) return "\n".join(H) + self.sco_footer(REQUEST)
@ -2172,258 +1994,6 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
return repr(doc) return repr(doc)
_billet_absenceEditor = notesdb.EditableTable(
"billet_absence",
"billet_id",
(
"billet_id",
"etudid",
"abs_begin",
"abs_end",
"description",
"etat",
"entry_date",
"justified",
),
sortkey="entry_date desc",
)
billet_absence_create = _billet_absenceEditor.create
billet_absence_delete = _billet_absenceEditor.delete
billet_absence_list = _billet_absenceEditor.list
billet_absence_edit = _billet_absenceEditor.edit
# ------ HTML Calendar functions (see YearTable function)
# MONTH/DAY NAMES:
MONTHNAMES = (
"Janvier",
"F&eacute;vrier",
"Mars",
"Avril",
"Mai",
"Juin",
"Juillet",
"Aout",
"Septembre",
"Octobre",
"Novembre",
"D&eacute;cembre",
)
MONTHNAMES_ABREV = (
"Jan.",
"F&eacute;v.",
"Mars",
"Avr.",
"Mai&nbsp;",
"Juin",
"Juil",
"Aout",
"Sept",
"Oct.",
"Nov.",
"D&eacute;c.",
)
DAYNAMES = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
DAYNAMES_ABREV = ("L", "M", "M", "J", "V", "S", "D")
# COLORS:
WHITE = "#FFFFFF"
GRAY1 = "#EEEEEE"
GREEN3 = "#99CC99"
WEEKDAYCOLOR = GRAY1
WEEKENDCOLOR = GREEN3
def MonthTableHead(month):
color = WHITE
return """<table class="monthcalendar" border="0" cellpadding="0" cellspacing="0" frame="box">
<tr bgcolor="%s"><td class="calcol" colspan="2" align="center">%s</td></tr>\n""" % (
color,
MONTHNAMES_ABREV[month - 1],
)
def MonthTableTail():
return "</table>\n"
def MonthTableBody(
month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8
):
firstday, nbdays = calendar.monthrange(year, month)
localtime = time.localtime()
current_weeknum = time.strftime("%U", localtime)
current_year = localtime[0]
T = []
# cherche date du lundi de la 1ere semaine de ce mois
monday = ddmmyyyy("1/%d/%d" % (month, year))
while monday.weekday != 0:
monday = monday.prev()
if work_saturday:
weekend = ("D",)
else:
weekend = ("S", "D")
if not halfday:
for d in range(1, nbdays + 1):
weeknum = time.strftime(
"%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
)
day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
if day in weekend:
bgcolor = WEEKENDCOLOR
weekclass = "wkend"
attrs = ""
else:
bgcolor = WEEKDAYCOLOR
weekclass = "wk" + str(monday).replace("/", "_")
attrs = trattributes
color = None
legend = ""
href = ""
descr = ""
# event this day ?
# each event is a tuple (date, text, color, href)
# where date is a string in ISO format (yyyy-mm-dd)
for ev in events:
ev_year = int(ev[0][:4])
ev_month = int(ev[0][5:7])
ev_day = int(ev[0][8:10])
if year == ev_year and month == ev_month and ev_day == d:
if ev[1]:
legend = ev[1]
if ev[2]:
color = ev[2]
if ev[3]:
href = ev[3]
if len(ev) > 4 and ev[4]:
descr = ev[4]
#
cc = []
if color != None:
cc.append('<td bgcolor="%s" class="calcell">' % color)
else:
cc.append('<td class="calcell">')
if href:
href = 'href="%s"' % href
if descr:
descr = 'title="%s"' % cgi.escape(descr, quote=True)
if href or descr:
cc.append("<a %s %s>" % (href, descr))
if legend or d == 1:
if pad_width != None:
n = pad_width - len(legend) # pad to 8 cars
if n > 0:
legend = "&nbsp;" * (n / 2) + legend + "&nbsp;" * ((n + 1) / 2)
else:
legend = "&nbsp;" # empty cell
cc.append(legend)
if href or descr:
cc.append("</a>")
cc.append("</td>")
cell = string.join(cc, "")
if day == "D":
monday = monday.next(7)
if (
weeknum == current_weeknum
and current_year == year
and weekclass != "wkend"
):
weekclass += " currentweek"
T.append(
'<tr bgcolor="%s" class="%s" %s><td class="calday">%d%s</td>%s</tr>'
% (bgcolor, weekclass, attrs, d, day, cell)
)
else:
# Calendar with 2 cells / day
for d in range(1, nbdays + 1):
weeknum = time.strftime(
"%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
)
day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
if day in weekend:
bgcolor = WEEKENDCOLOR
weekclass = "wkend"
attrs = ""
else:
bgcolor = WEEKDAYCOLOR
weekclass = "wk" + str(monday).replace("/", "_")
attrs = trattributes
if (
weeknum == current_weeknum
and current_year == year
and weekclass != "wkend"
):
weeknum += " currentweek"
if day == "D":
monday = monday.next(7)
T.append(
'<tr bgcolor="%s" class="wk%s" %s><td class="calday">%d%s</td>'
% (bgcolor, weekclass, attrs, d, day)
)
cc = []
for morning in (1, 0):
color = None
legend = ""
href = ""
descr = ""
for ev in events:
ev_year = int(ev[0][:4])
ev_month = int(ev[0][5:7])
ev_day = int(ev[0][8:10])
if ev[4] != None:
ev_half = int(ev[4])
else:
ev_half = 0
if (
year == ev_year
and month == ev_month
and ev_day == d
and morning == ev_half
):
if ev[1]:
legend = ev[1]
if ev[2]:
color = ev[2]
if ev[3]:
href = ev[3]
if len(ev) > 5 and ev[5]:
descr = ev[5]
#
if color != None:
cc.append('<td bgcolor="%s" class="calcell">' % (color))
else:
cc.append('<td class="calcell">')
if href:
href = 'href="%s"' % href
if descr:
descr = 'title="%s"' % cgi.escape(descr, quote=True)
if href or descr:
cc.append("<a %s %s>" % (href, descr))
if legend or d == 1:
n = 3 - len(legend) # pad to 3 cars
if n > 0:
legend = "&nbsp;" * (n / 2) + legend + "&nbsp;" * ((n + 1) / 2)
else:
legend = "&nbsp;&nbsp;&nbsp;" # empty cell
cc.append(legend)
if href or descr:
cc.append("</a>")
cc.append("</td>\n")
T.append(string.join(cc, "") + "</tr>")
return string.join(T, "\n")
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# #
# Zope Product Administration # Zope Product Administration
@ -2441,121 +2011,3 @@ def manage_addZAbsences(
# The form used to get the instance id from the user. # The form used to get the instance id from the user.
# manage_addZAbsencesForm = DTMLFile('dtml/manage_addZAbsencesForm', globals()) # manage_addZAbsencesForm = DTMLFile('dtml/manage_addZAbsencesForm', globals())
# --------------------------------------------------------------------
#
# Cache absences
#
# On cache simplement (à la demande) le nombre d'absences de chaque etudiant
# dans un semestre donné.
# Toute modification du semestre (invalidation) invalide le cache
# (simple mécanisme de "listener" sur le cache de semestres)
# Toute modification des absences d'un étudiant invalide les caches des semestres
# concernés à cette date (en général un seul semestre)
#
# On ne cache pas la liste des absences car elle est rarement utilisée (calendrier,
# absences à une date donnée).
#
# --------------------------------------------------------------------
class CAbsSemEtud:
"""Comptes d'absences d'un etudiant dans un semestre"""
def __init__(self, context, sem, etudid):
self.context = context
self.sem = sem
self.etudid = etudid
self._loaded = False
formsemestre_id = sem["formsemestre_id"]
context.Notes._getNotesCache().add_listener(
self.invalidate, formsemestre_id, (etudid, formsemestre_id)
)
def CountAbs(self):
if not self._loaded:
self.load()
return self._CountAbs
def CountAbsJust(self):
if not self._loaded:
self.load()
return self._CountAbsJust
def load(self):
"Load state from DB"
# log('loading CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id']))
# Reload sem, it may have changed
self.sem = sco_formsemestre.get_formsemestre(
self.context, self.sem["formsemestre_id"]
)
debut_sem = notesdb.DateDMYtoISO(self.sem["date_debut"])
fin_sem = notesdb.DateDMYtoISO(self.sem["date_fin"])
self._CountAbs = self.context.Absences.CountAbs(
etudid=self.etudid, debut=debut_sem, fin=fin_sem
)
self._CountAbsJust = self.context.Absences.CountAbsJust(
etudid=self.etudid, debut=debut_sem, fin=fin_sem
)
self._loaded = True
def invalidate(self, args=None):
"Notify me that DB has been modified"
# log('invalidate CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id']))
self._loaded = False
# Accès au cache des absences
ABS_CACHE_INST = {} # { DeptId : { formsemestre_id : { etudid : CAbsEtudSem } } }
def getAbsSemEtud(context, sem, etudid):
AbsSemEtuds = getAbsSemEtuds(context, sem)
if not etudid in AbsSemEtuds:
AbsSemEtuds[etudid] = CAbsSemEtud(context, sem, etudid)
return AbsSemEtuds[etudid]
def getAbsSemEtuds(context, sem):
u = context.GetDBConnexionString() # identifie le dept de facon fiable
if not u in ABS_CACHE_INST:
ABS_CACHE_INST[u] = {}
C = ABS_CACHE_INST[u]
if sem["formsemestre_id"] not in C:
C[sem["formsemestre_id"]] = {}
return C[sem["formsemestre_id"]]
def invalidateAbsEtudDate(context, etudid, date):
"""Doit etre appelé à chaque modification des absences pour cet étudiant et cette date.
Invalide cache absence et PDF bulletins si nécessaire.
date: date au format ISO
"""
# Semestres a cette date:
etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
sems = [
sem
for sem in etud["sems"]
if sem["date_debut_iso"] <= date and sem["date_fin_iso"] >= date
]
# Invalide les PDF et les abscences:
for sem in sems:
# Inval cache bulletin et/ou note_table
if sco_compute_moy.formsemestre_expressions_use_abscounts(
context, sem["formsemestre_id"]
):
pdfonly = False # seules certaines formules utilisent les absences
else:
pdfonly = (
True # efface toujours le PDF car il affiche en général les absences
)
context.Notes._inval_cache(
pdfonly=pdfonly, formsemestre_id=sem["formsemestre_id"]
)
# Inval cache compteurs absences:
AbsSemEtuds = getAbsSemEtuds(context, sem)
if etudid in AbsSemEtuds:
AbsSemEtuds[etudid].invalidate()

View File

@ -107,7 +107,7 @@ class EntreprisesEditor(EditableTable):
"select E.*, I.nom as etud_nom, I.prenom as etud_prenom, C.date from entreprises E, entreprise_contact C, identite I where C.entreprise_id = E.entreprise_id and C.etudid = I.etudid and I.nom ~* %(etud_nom)s ORDER BY E.nom", "select E.*, I.nom as etud_nom, I.prenom as etud_prenom, C.date from entreprises E, entreprise_contact C, identite I where C.entreprise_id = E.entreprise_id and C.etudid = I.etudid and I.nom ~* %(etud_nom)s ORDER BY E.nom",
args, args,
) )
titles, res = [x[0] for x in cursor.description], cursor.dictfetchall() _, res = [x[0] for x in cursor.description], cursor.dictfetchall()
R = [] R = []
for r in res: for r in res:
r["etud_prenom"] = r["etud_prenom"] or "" r["etud_prenom"] = r["etud_prenom"] or ""
@ -450,7 +450,6 @@ class ZEntreprises(
def do_entreprise_correspondant_listnames(self, args={}): def do_entreprise_correspondant_listnames(self, args={}):
"-> liste des noms des correspondants (pour affichage menu)" "-> liste des noms des correspondants (pour affichage menu)"
cnx = self.GetDBConnexion()
C = self.do_entreprise_correspondant_list(args=args) C = self.do_entreprise_correspondant_list(args=args)
return [ return [
(x["prenom"] + " " + x["nom"], str(x["entreprise_corresp_id"])) for x in C (x["prenom"] + " " + x["nom"], str(x["entreprise_corresp_id"])) for x in C
@ -538,43 +537,48 @@ class ZEntreprises(
# (fonction ad-hoc car requete sur plusieurs tables) # (fonction ad-hoc car requete sur plusieurs tables)
raise NotImplementedError raise NotImplementedError
# XXXXX fonction non achevee , non testee... # XXXXX fonction non achevee , non testee...
cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor) # cnx = self.GetDBConnexion()
vals = dictfilter(args, self.dbfields) # cursor = cnx.cursor(cursor_factory=ScoDocCursor)
# DBSelect # if sortkey:
what = ["*"] # orderby = " order by " + sortkey
operator = " " + operator + " " # else:
cond = " E.entreprise_id = C.entreprise_id " # orderby = ""
if vals: # vals = dictfilter(args, self.dbfields)
cond += " where " + operator.join( # # DBSelect
["%s%s%%(%s)s" % (x, test, x) for x in vals.keys() if vals[x] != None] # what = ["*"]
) # operator = " " + operator + " "
cnuls = " and ".join( # cond = " E.entreprise_id = C.entreprise_id "
["%s is NULL" % x for x in vals.keys() if vals[x] is None] # if vals:
) # cond += " where " + operator.join(
if cnuls: # ["%s%s%%(%s)s" % (x, test, x) for x in vals.keys() if vals[x] != None]
cond = cond + " and " + cnuls # )
else: # cnuls = " and ".join(
cond += "" # ["%s is NULL" % x for x in vals.keys() if vals[x] is None]
cursor.execute( # )
"select distinct" # if cnuls:
+ ", ".join(what) # cond = cond + " and " + cnuls
+ " from entreprises E, entreprise_contact C " # else:
+ cond # cond += ""
+ orderby, # cursor.execute(
vals, # "select distinct"
) # + ", ".join(what)
titles, res = [x[0] for x in cursor.description], cursor.fetchall() # + " from entreprises E, entreprise_contact C "
# # + cond
R = [] # + orderby,
for r in res: # vals,
d = {} # )
for i in range(len(titles)): # titles, res = [x[0] for x in cursor.description], cursor.fetchall()
v = r[i] # #
# value not formatted ! (see EditableTable.list()) # R = []
d[titles[i]] = v # for r in res:
R.append(d) # d = {}
return R # for i in range(len(titles)):
# v = r[i]
# # value not formatted ! (see EditableTable.list())
# d[titles[i]] = v
# R.append(d)
# return R
# -------- Formulaires: traductions du DTML # -------- Formulaires: traductions du DTML
security.declareProtected(ScoEntrepriseChange, "entreprise_create") security.declareProtected(ScoEntrepriseChange, "entreprise_create")
@ -892,7 +896,3 @@ def manage_addZEntreprises(
if REQUEST is not None: if REQUEST is not None:
return self.manage_main(self, REQUEST) return self.manage_main(self, REQUEST)
# return self.manage_editForm(self, REQUEST) # return self.manage_editForm(self, REQUEST)
# The form used to get the instance id from the user.
# manage_addZAbsencesForm = DTMLFile('dtml/manage_addZAbsencesForm', globals())

View File

@ -89,6 +89,7 @@ import ZEntreprises
import ZScoUsers import ZScoUsers
import sco_modalites import sco_modalites
import ImportScolars import ImportScolars
import sco_abs
import sco_portal_apogee import sco_portal_apogee
import sco_synchro_etuds import sco_synchro_etuds
import sco_page_etud import sco_page_etud
@ -274,13 +275,13 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
</p> </p>
</body> </body>
""" """
return ( # return (
self.sco_header(REQUEST) # self.sco_header(REQUEST)
+ """<div class="xp">%s</div>""" % x # + """<div class="xp">%s</div>""" % x
+ self.sco_footer(REQUEST) # + self.sco_footer(REQUEST)
) # )
b = "<p>Hello, World !</p><br/>" # b = "<p>Hello, World !</p><br/>"
raise ValueError("essai exception") # raise ValueError("essai exception")
# raise ScoValueError('essai exception !', dest_url='totoro', REQUEST=REQUEST) # raise ScoValueError('essai exception !', dest_url='totoro', REQUEST=REQUEST)
# cursor = cnx.cursor(cursor_factory=ScoDocCursor) # cursor = cnx.cursor(cursor_factory=ScoDocCursor)
@ -592,7 +593,7 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
# calcule dates 1er jour semaine pour absences # calcule dates 1er jour semaine pour absences
try: try:
if with_absences: if with_absences:
first_monday = ZAbsences.ddmmyyyy(sem["date_debut"]).prev_monday() first_monday = sco_abs.ddmmyyyy(sem["date_debut"]).prev_monday()
FA = [] # formulaire avec menu saisi absences FA = [] # formulaire avec menu saisi absences
FA.append( FA.append(
'<td><form action="Absences/SignaleAbsenceGrSemestre" method="get">' '<td><form action="Absences/SignaleAbsenceGrSemestre" method="get">'

View File

@ -26,7 +26,7 @@
############################################################################## ##############################################################################
from sco_utils import * from sco_utils import *
from ZAbsences import getAbsSemEtud from sco_abs import getAbsSemEtud
""" """
Génération de la "sidebar" (marge gauche des pages HTML) Génération de la "sidebar" (marge gauche des pages HTML)

595
sco_abs.py Normal file
View File

@ -0,0 +1,595 @@
# -*- 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
#
##############################################################################
"""Fonctions sur les absences
"""
# Anciennement dans ZAbscences.py, séparé pour migration
import string
import datetime
import re
import time
import calendar
import cgi
import notesdb
from sco_exceptions import ScoValueError, ScoInvalidDateError
import sco_formsemestre
import sco_compute_moy
def MonthNbDays(month, year):
"returns nb of days in month"
if month > 7:
month = month + 1
if month % 2:
return 31
elif month == 2:
if calendar.isleap(year):
return 29
else:
return 28
else:
return 30
class ddmmyyyy:
"""immutable dates"""
def __init__(self, date=None, fmt="ddmmyyyy", work_saturday=False):
self.work_saturday = work_saturday
if date is None:
return
try:
if fmt == "ddmmyyyy":
self.day, self.month, self.year = string.split(date, "/")
elif fmt == "iso":
self.year, self.month, self.day = string.split(date, "-")
else:
raise ValueError("invalid format spec. (%s)" % fmt)
self.year = string.atoi(self.year)
self.month = string.atoi(self.month)
self.day = string.atoi(self.day)
except:
raise ScoValueError("date invalide: %s" % date)
# accept years YYYY or YY, uses 1970 as pivot
if self.year < 1970:
if self.year > 100:
raise ScoInvalidDateError("Année invalide: %s" % self.year)
if self.year < 70:
self.year = self.year + 2000
else:
self.year = self.year + 1900
if self.month < 1 or self.month > 12:
raise ScoInvalidDateError("Mois invalide: %s" % self.month)
if self.day < 1 or self.day > MonthNbDays(self.month, self.year):
raise ScoInvalidDateError("Jour invalide: %s" % self.day)
# weekday in 0-6, where 0 is monday
self.weekday = calendar.weekday(self.year, self.month, self.day)
self.time = time.mktime((self.year, self.month, self.day, 0, 0, 0, 0, 0, 0))
def iswork(self):
"returns true if workable day"
if self.work_saturday:
nbdays = 6
else:
nbdays = 5
if (
self.weekday >= 0 and self.weekday < nbdays
): # monday-friday or monday-saturday
return 1
else:
return 0
def __repr__(self):
return "'%02d/%02d/%04d'" % (self.day, self.month, self.year)
def __str__(self):
return "%02d/%02d/%04d" % (self.day, self.month, self.year)
def ISO(self):
"iso8601 representation of the date"
return "%04d-%02d-%02d" % (self.year, self.month, self.day)
def next(self, days=1):
"date for the next day (nota: may be a non workable day)"
day = self.day + days
month = self.month
year = self.year
while day > MonthNbDays(month, year):
day = day - MonthNbDays(month, year)
month = month + 1
if month > 12:
month = 1
year = year + 1
return self.__class__(
"%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
)
def prev(self, days=1):
"date for previous day"
day = self.day - days
month = self.month
year = self.year
while day <= 0:
month = month - 1
if month == 0:
month = 12
year = year - 1
day = day + MonthNbDays(month, year)
return self.__class__(
"%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
)
def next_monday(self):
"date of next monday"
return self.next((7 - self.weekday) % 7)
def prev_monday(self):
"date of last monday, but on sunday, pick next monday"
if self.weekday == 6:
return self.next_monday()
else:
return self.prev(self.weekday)
def __cmp__(self, other):
"""return a negative integer if self < other,
zero if self == other, a positive integer if self > other"""
return int(self.time - other.time)
def __hash__(self):
"we are immutable !"
return hash(self.time) ^ hash(str(self))
# d = ddmmyyyy( '21/12/99' )
def YearTable(
context,
year,
events=[],
firstmonth=9,
lastmonth=7,
halfday=0,
dayattributes="",
pad_width=8,
):
"""Generate a calendar table
events = list of tuples (date, text, color, href [,halfday])
where date is a string in ISO format (yyyy-mm-dd)
halfday is boolean (true: morning, false: afternoon)
text = text to put in calendar (must be short, 1-5 cars) (optional)
if halfday, generate 2 cells per day (morning, afternoon)
"""
T = [
'<table id="maincalendar" class="maincalendar" border="3" cellpadding="1" cellspacing="1" frame="box">'
]
T.append("<tr>")
month = firstmonth
while 1:
T.append('<td valign="top">')
T.append(MonthTableHead(month))
T.append(
MonthTableBody(
month,
year,
events,
halfday,
dayattributes,
context.is_work_saturday(),
pad_width=pad_width,
)
)
T.append(MonthTableTail())
T.append("</td>")
if month == lastmonth:
break
month = month + 1
if month > 12:
month = 1
year = year + 1
T.append("</table>")
return string.join(T, "\n")
# ---- BILLETS
_billet_absenceEditor = notesdb.EditableTable(
"billet_absence",
"billet_id",
(
"billet_id",
"etudid",
"abs_begin",
"abs_end",
"description",
"etat",
"entry_date",
"justified",
),
sortkey="entry_date desc",
)
billet_absence_create = _billet_absenceEditor.create
billet_absence_delete = _billet_absenceEditor.delete
billet_absence_list = _billet_absenceEditor.list
billet_absence_edit = _billet_absenceEditor.edit
# ------ HTML Calendar functions (see YearTable function)
# MONTH/DAY NAMES:
MONTHNAMES = (
"Janvier",
"F&eacute;vrier",
"Mars",
"Avril",
"Mai",
"Juin",
"Juillet",
"Aout",
"Septembre",
"Octobre",
"Novembre",
"D&eacute;cembre",
)
MONTHNAMES_ABREV = (
"Jan.",
"F&eacute;v.",
"Mars",
"Avr.",
"Mai&nbsp;",
"Juin",
"Juil",
"Aout",
"Sept",
"Oct.",
"Nov.",
"D&eacute;c.",
)
DAYNAMES = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
DAYNAMES_ABREV = ("L", "M", "M", "J", "V", "S", "D")
# COLORS:
WHITE = "#FFFFFF"
GRAY1 = "#EEEEEE"
GREEN3 = "#99CC99"
WEEKDAYCOLOR = GRAY1
WEEKENDCOLOR = GREEN3
def MonthTableHead(month):
color = WHITE
return """<table class="monthcalendar" border="0" cellpadding="0" cellspacing="0" frame="box">
<tr bgcolor="%s"><td class="calcol" colspan="2" align="center">%s</td></tr>\n""" % (
color,
MONTHNAMES_ABREV[month - 1],
)
def MonthTableTail():
return "</table>\n"
def MonthTableBody(
month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8
):
firstday, nbdays = calendar.monthrange(year, month)
localtime = time.localtime()
current_weeknum = time.strftime("%U", localtime)
current_year = localtime[0]
T = []
# cherche date du lundi de la 1ere semaine de ce mois
monday = ddmmyyyy("1/%d/%d" % (month, year))
while monday.weekday != 0:
monday = monday.prev()
if work_saturday:
weekend = ("D",)
else:
weekend = ("S", "D")
if not halfday:
for d in range(1, nbdays + 1):
weeknum = time.strftime(
"%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
)
day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
if day in weekend:
bgcolor = WEEKENDCOLOR
weekclass = "wkend"
attrs = ""
else:
bgcolor = WEEKDAYCOLOR
weekclass = "wk" + str(monday).replace("/", "_")
attrs = trattributes
color = None
legend = ""
href = ""
descr = ""
# event this day ?
# each event is a tuple (date, text, color, href)
# where date is a string in ISO format (yyyy-mm-dd)
for ev in events:
ev_year = int(ev[0][:4])
ev_month = int(ev[0][5:7])
ev_day = int(ev[0][8:10])
if year == ev_year and month == ev_month and ev_day == d:
if ev[1]:
legend = ev[1]
if ev[2]:
color = ev[2]
if ev[3]:
href = ev[3]
if len(ev) > 4 and ev[4]:
descr = ev[4]
#
cc = []
if color != None:
cc.append('<td bgcolor="%s" class="calcell">' % color)
else:
cc.append('<td class="calcell">')
if href:
href = 'href="%s"' % href
if descr:
descr = 'title="%s"' % cgi.escape(descr, quote=True)
if href or descr:
cc.append("<a %s %s>" % (href, descr))
if legend or d == 1:
if pad_width != None:
n = pad_width - len(legend) # pad to 8 cars
if n > 0:
legend = "&nbsp;" * (n / 2) + legend + "&nbsp;" * ((n + 1) / 2)
else:
legend = "&nbsp;" # empty cell
cc.append(legend)
if href or descr:
cc.append("</a>")
cc.append("</td>")
cell = string.join(cc, "")
if day == "D":
monday = monday.next(7)
if (
weeknum == current_weeknum
and current_year == year
and weekclass != "wkend"
):
weekclass += " currentweek"
T.append(
'<tr bgcolor="%s" class="%s" %s><td class="calday">%d%s</td>%s</tr>'
% (bgcolor, weekclass, attrs, d, day, cell)
)
else:
# Calendar with 2 cells / day
for d in range(1, nbdays + 1):
weeknum = time.strftime(
"%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
)
day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
if day in weekend:
bgcolor = WEEKENDCOLOR
weekclass = "wkend"
attrs = ""
else:
bgcolor = WEEKDAYCOLOR
weekclass = "wk" + str(monday).replace("/", "_")
attrs = trattributes
if (
weeknum == current_weeknum
and current_year == year
and weekclass != "wkend"
):
weeknum += " currentweek"
if day == "D":
monday = monday.next(7)
T.append(
'<tr bgcolor="%s" class="wk%s" %s><td class="calday">%d%s</td>'
% (bgcolor, weekclass, attrs, d, day)
)
cc = []
for morning in (1, 0):
color = None
legend = ""
href = ""
descr = ""
for ev in events:
ev_year = int(ev[0][:4])
ev_month = int(ev[0][5:7])
ev_day = int(ev[0][8:10])
if ev[4] != None:
ev_half = int(ev[4])
else:
ev_half = 0
if (
year == ev_year
and month == ev_month
and ev_day == d
and morning == ev_half
):
if ev[1]:
legend = ev[1]
if ev[2]:
color = ev[2]
if ev[3]:
href = ev[3]
if len(ev) > 5 and ev[5]:
descr = ev[5]
#
if color != None:
cc.append('<td bgcolor="%s" class="calcell">' % (color))
else:
cc.append('<td class="calcell">')
if href:
href = 'href="%s"' % href
if descr:
descr = 'title="%s"' % cgi.escape(descr, quote=True)
if href or descr:
cc.append("<a %s %s>" % (href, descr))
if legend or d == 1:
n = 3 - len(legend) # pad to 3 cars
if n > 0:
legend = "&nbsp;" * (n / 2) + legend + "&nbsp;" * ((n + 1) / 2)
else:
legend = "&nbsp;&nbsp;&nbsp;" # empty cell
cc.append(legend)
if href or descr:
cc.append("</a>")
cc.append("</td>\n")
T.append(string.join(cc, "") + "</tr>")
return string.join(T, "\n")
# --------------------------------------------------------------------
#
# Cache absences
#
# On cache simplement (à la demande) le nombre d'absences de chaque etudiant
# dans un semestre donné.
# Toute modification du semestre (invalidation) invalide le cache
# (simple mécanisme de "listener" sur le cache de semestres)
# Toute modification des absences d'un étudiant invalide les caches des semestres
# concernés à cette date (en général un seul semestre)
#
# On ne cache pas la liste des absences car elle est rarement utilisée (calendrier,
# absences à une date donnée).
#
# --------------------------------------------------------------------
class CAbsSemEtud:
"""Comptes d'absences d'un etudiant dans un semestre"""
def __init__(self, context, sem, etudid):
self.context = context
self.sem = sem
self.etudid = etudid
self._loaded = False
formsemestre_id = sem["formsemestre_id"]
context.Notes._getNotesCache().add_listener(
self.invalidate, formsemestre_id, (etudid, formsemestre_id)
)
def CountAbs(self):
if not self._loaded:
self.load()
return self._CountAbs
def CountAbsJust(self):
if not self._loaded:
self.load()
return self._CountAbsJust
def load(self):
"Load state from DB"
# log('loading CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id']))
# Reload sem, it may have changed
self.sem = sco_formsemestre.get_formsemestre(
self.context, self.sem["formsemestre_id"]
)
debut_sem = notesdb.DateDMYtoISO(self.sem["date_debut"])
fin_sem = notesdb.DateDMYtoISO(self.sem["date_fin"])
self._CountAbs = self.context.Absences.CountAbs(
etudid=self.etudid, debut=debut_sem, fin=fin_sem
)
self._CountAbsJust = self.context.Absences.CountAbsJust(
etudid=self.etudid, debut=debut_sem, fin=fin_sem
)
self._loaded = True
def invalidate(self, args=None):
"Notify me that DB has been modified"
# log('invalidate CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id']))
self._loaded = False
# Accès au cache des absences
ABS_CACHE_INST = {} # { DeptId : { formsemestre_id : { etudid : CAbsEtudSem } } }
def getAbsSemEtud(context, sem, etudid):
AbsSemEtuds = getAbsSemEtuds(context, sem)
if not etudid in AbsSemEtuds:
AbsSemEtuds[etudid] = CAbsSemEtud(context, sem, etudid)
return AbsSemEtuds[etudid]
def getAbsSemEtuds(context, sem):
u = context.GetDBConnexionString() # identifie le dept de facon fiable
if not u in ABS_CACHE_INST:
ABS_CACHE_INST[u] = {}
C = ABS_CACHE_INST[u]
if sem["formsemestre_id"] not in C:
C[sem["formsemestre_id"]] = {}
return C[sem["formsemestre_id"]]
def invalidateAbsEtudDate(context, etudid, date):
"""Doit etre appelé à chaque modification des absences pour cet étudiant et cette date.
Invalide cache absence et PDF bulletins si nécessaire.
date: date au format ISO
"""
# Semestres a cette date:
etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
sems = [
sem
for sem in etud["sems"]
if sem["date_debut_iso"] <= date and sem["date_fin_iso"] >= date
]
# Invalide les PDF et les abscences:
for sem in sems:
# Inval cache bulletin et/ou note_table
if sco_compute_moy.formsemestre_expressions_use_abscounts(
context, sem["formsemestre_id"]
):
pdfonly = False # seules certaines formules utilisent les absences
else:
pdfonly = (
True # efface toujours le PDF car il affiche en général les absences
)
context.Notes._inval_cache(
pdfonly=pdfonly, formsemestre_id=sem["formsemestre_id"]
)
# Inval cache compteurs absences:
AbsSemEtuds = getAbsSemEtuds(context, sem)
if etudid in AbsSemEtuds:
AbsSemEtuds[etudid].invalidate()

View File

@ -40,7 +40,7 @@ import sco_find_etud
import sco_formsemestre import sco_formsemestre
import sco_photos import sco_photos
import ZAbsences import sco_abs
def doSignaleAbsence( def doSignaleAbsence(
@ -631,7 +631,7 @@ def CalAbs(context, REQUEST=None): # etud implied
events.append( events.append(
(str(a["jour"]), "X", "#8EA2C6", "", a["matin"], a["description"]) (str(a["jour"]), "X", "#8EA2C6", "", a["matin"], a["description"])
) )
CalHTML = ZAbsences.YearTable(context, anneescolaire, events=events, halfday=1) CalHTML = sco_abs.YearTable(context, anneescolaire, events=events, halfday=1)
# #
H = [ H = [

View File

@ -44,7 +44,7 @@ import sco_groups
import sco_pvjury import sco_pvjury
import sco_formsemestre_status import sco_formsemestre_status
import sco_photos import sco_photos
import ZAbsences import sco_abs
import sco_abs_views import sco_abs_views
import sco_preferences import sco_preferences
import sco_codes_parcours import sco_codes_parcours
@ -150,7 +150,7 @@ def formsemestre_bulletinetud_dict(
context, pid context, pid
) )
# --- Absences # --- Absences
AbsSemEtud = ZAbsences.getAbsSemEtud(context, nt.sem, etudid) AbsSemEtud = sco_abs.getAbsSemEtud(context, nt.sem, etudid)
I["nbabs"] = AbsSemEtud.CountAbs() I["nbabs"] = AbsSemEtud.CountAbs()
I["nbabsjust"] = AbsSemEtud.CountAbsJust() I["nbabsjust"] = AbsSemEtud.CountAbsJust()

View File

@ -42,12 +42,22 @@ La préférence 'bul_pdf_class_name' est obsolete (inutilisée).
""" """
import time
import cStringIO
import collections import collections
import traceback
import reportlab
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
import sco_utils
import VERSION
from sco_exceptions import NoteProcessError
import sco_preferences import sco_preferences
from notes_log import log from notes_log import log
import sco_formsemestre import sco_formsemestre
from sco_pdf import * import sco_pdf
from sco_pdf import PDFLOCK
BULLETIN_CLASSES = ( BULLETIN_CLASSES = (
collections.OrderedDict() collections.OrderedDict()
@ -145,7 +155,7 @@ class BulletinGenerator:
dt, dt,
self.infos["etud"]["nom"], self.infos["etud"]["nom"],
) )
filename = unescape_html(filename).replace(" ", "_").replace("&", "") filename = sco_utils.unescape_html(filename).replace(" ", "_").replace("&", "")
return filename return filename
def generate(self, format="", stand_alone=True): def generate(self, format="", stand_alone=True):
@ -166,8 +176,10 @@ class BulletinGenerator:
def generate_html(self): def generate_html(self):
"""Return bulletin as an HTML string""" """Return bulletin as an HTML string"""
H = ['<div class="notes_bulletin">'] H = ['<div class="notes_bulletin">']
H.append(self.bul_table(format="html")) # table des notes # table des notes:
H.append(self.bul_part_below(format="html")) # infos sous la table H.append(self.bul_table(format="html")) # pylint: disable=no-member
# infos sous la table:
H.append(self.bul_part_below(format="html")) # pylint: disable=no-member
H.append("</div>") H.append("</div>")
return "\n".join(H) return "\n".join(H)
@ -179,10 +191,14 @@ class BulletinGenerator:
""" """
formsemestre_id = self.infos["formsemestre_id"] formsemestre_id = self.infos["formsemestre_id"]
objects = self.bul_title_pdf() # partie haute du bulletin # partie haute du bulletin
objects += self.bul_table(format="pdf") # table des notes objects = self.bul_title_pdf() # pylint: disable=no-member
objects += self.bul_part_below(format="pdf") # infos sous la table # table des notes
objects += self.bul_signatures_pdf() # signatures objects += self.bul_table(format="pdf") # pylint: disable=no-member
# infos sous la table
objects += self.bul_part_below(format="pdf") # pylint: disable=no-member
# signatures
objects += self.bul_signatures_pdf() # pylint: disable=no-member
# Réduit sur une page # Réduit sur une page
objects = [KeepInFrame(0, 0, objects, mode="shrink")] objects = [KeepInFrame(0, 0, objects, mode="shrink")]
@ -194,13 +210,13 @@ class BulletinGenerator:
# Generation du document PDF # Generation du document PDF
sem = sco_formsemestre.get_formsemestre(self.context, formsemestre_id) sem = sco_formsemestre.get_formsemestre(self.context, formsemestre_id)
report = cStringIO.StringIO() # in-memory document, no disk file report = cStringIO.StringIO() # in-memory document, no disk file
document = BaseDocTemplate(report) document = sco_pdf.BaseDocTemplate(report)
document.addPageTemplates( document.addPageTemplates(
ScolarsPageTemplate( sco_pdf.ScolarsPageTemplate(
document, document,
context=self.context, context=self.context,
author="%s %s (E. Viennet) [%s]" author="%s %s (E. Viennet) [%s]"
% (SCONAME, SCOVERSION, self.description), % (VERSION.SCONAME, VERSION.SCOVERSION, self.description),
title="Bulletin %s de %s" title="Bulletin %s de %s"
% (sem["titremois"], self.infos["etud"]["nomprenom"]), % (sem["titremois"], self.infos["etud"]["nomprenom"]),
subject="Bulletin de note", subject="Bulletin de note",
@ -222,7 +238,9 @@ class BulletinGenerator:
""" """
try: try:
# put each table cell in a Paragraph # put each table cell in a Paragraph
Pt = [[Paragraph(SU(x), self.CellStyle) for x in line] for line in P] Pt = [
[Paragraph(sco_pdf.SU(x), self.CellStyle) for x in line] for line in P
]
except: except:
# enquête sur exception intermittente... # enquête sur exception intermittente...
log("*** bug in PDF buildTableObject:") log("*** bug in PDF buildTableObject:")

View File

@ -34,7 +34,7 @@ from notes_table import *
import sco_formsemestre import sco_formsemestre
import sco_groups import sco_groups
import sco_photos import sco_photos
import ZAbsences import sco_abs
import sco_bulletins import sco_bulletins
# -------- Bulletin en JSON # -------- Bulletin en JSON
@ -321,7 +321,7 @@ def formsemestre_bulletinetud_published_dict(
if context.get_preference("bul_show_abs", formsemestre_id): if context.get_preference("bul_show_abs", formsemestre_id):
debut_sem = DateDMYtoISO(sem["date_debut"]) debut_sem = DateDMYtoISO(sem["date_debut"])
fin_sem = DateDMYtoISO(sem["date_fin"]) fin_sem = DateDMYtoISO(sem["date_fin"])
AbsEtudSem = ZAbsences.getAbsSemEtud(context, sem, etudid) AbsEtudSem = sco_abs.getAbsSemEtud(context, sem, etudid)
nbabs = AbsEtudSem.CountAbs() nbabs = AbsEtudSem.CountAbs()
nbabsjust = AbsEtudSem.CountAbsJust() nbabsjust = AbsEtudSem.CountAbsJust()

View File

@ -41,7 +41,7 @@ from notes_table import *
import sco_formsemestre import sco_formsemestre
import sco_groups import sco_groups
import sco_photos import sco_photos
import ZAbsences import sco_abs
import sco_bulletins import sco_bulletins
# -------- Bulletin en XML # -------- Bulletin en XML
@ -323,7 +323,7 @@ def make_xml_formsemestre_bulletinetud(
if context.get_preference("bul_show_abs", formsemestre_id): if context.get_preference("bul_show_abs", formsemestre_id):
debut_sem = DateDMYtoISO(sem["date_debut"]) debut_sem = DateDMYtoISO(sem["date_debut"])
fin_sem = DateDMYtoISO(sem["date_fin"]) fin_sem = DateDMYtoISO(sem["date_fin"])
AbsEtudSem = ZAbsences.getAbsSemEtud(context, sem, etudid) AbsEtudSem = sco_abs.getAbsSemEtud(context, sem, etudid)
nbabs = AbsEtudSem.CountAbs() nbabs = AbsEtudSem.CountAbs()
nbabsjust = AbsEtudSem.CountAbsJust() nbabsjust = AbsEtudSem.CountAbsJust()
doc._push() doc._push()

View File

@ -37,7 +37,7 @@ import sco_formsemestre
import sco_groups import sco_groups
import sco_evaluations import sco_evaluations
from sco_formulas import * from sco_formulas import *
import ZAbsences import sco_abs
def moduleimpl_has_expression(context, mod): def moduleimpl_has_expression(context, mod):
@ -124,7 +124,7 @@ def compute_user_formula(
Retourne moy, et en cas d'erreur met à jour diag_info (msg) Retourne moy, et en cas d'erreur met à jour diag_info (msg)
""" """
if use_abs: if use_abs:
AbsSemEtud = ZAbsences.getAbsSemEtud(context, sem, etudid) AbsSemEtud = sco_abs.getAbsSemEtud(context, sem, etudid)
nbabs = AbsSemEtud.CountAbs() nbabs = AbsSemEtud.CountAbs()
nbabs_just = AbsSemEtud.CountAbsJust() nbabs_just = AbsSemEtud.CountAbsJust()
else: else:

View File

@ -42,7 +42,7 @@ from TrivialFormulator import TrivialFormulator
import sco_news import sco_news
import sco_formsemestre import sco_formsemestre
import sco_groups import sco_groups
import ZAbsences import sco_abs
import sco_evaluations import sco_evaluations
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -487,7 +487,7 @@ def formsemestre_evaluations_cal(context, formsemestre_id, REQUEST=None):
if day > today: if day > today:
e[2] = color_futur e[2] = color_futur
CalHTML = ZAbsences.YearTable( CalHTML = sco_abs.YearTable(
context.Absences, year, events=events.values(), halfday=False, pad_width=None context.Absences, year, events=events.values(), halfday=False, pad_width=None
) )

View File

@ -35,7 +35,7 @@ from notes_log import log
from scolog import logdb from scolog import logdb
from notes_table import * from notes_table import *
import notes_table import notes_table
from ZAbsences import getAbsSemEtud from sco_abs import getAbsSemEtud
import sco_formsemestre import sco_formsemestre
import sco_formsemestre_edit import sco_formsemestre_edit

View File

@ -35,7 +35,7 @@ from sco_utils import *
import html_sco_header import html_sco_header
from gen_tables import GenTable from gen_tables import GenTable
import scolars import scolars
import ZAbsences import sco_abs
import sco_excel import sco_excel
import sco_formsemestre import sco_formsemestre
import sco_groups import sco_groups
@ -870,7 +870,7 @@ def form_choix_jour_saisie_hebdo(
if not authuser.has_permission(ScoAbsChange, context): if not authuser.has_permission(ScoAbsChange, context):
return "" return ""
sem = groups_infos.formsemestre sem = groups_infos.formsemestre
first_monday = ZAbsences.ddmmyyyy(sem["date_debut"]).prev_monday() first_monday = sco_abs.ddmmyyyy(sem["date_debut"]).prev_monday()
today_idx = datetime.date.today().weekday() today_idx = datetime.date.today().weekday()
FA = [] # formulaire avec menu saisi absences FA = [] # formulaire avec menu saisi absences
@ -923,7 +923,7 @@ def form_choix_saisie_semaine(context, groups_infos, REQUEST=None):
) # car ici utilisee dans un format string ! ) # car ici utilisee dans un format string !
DateJour = time.strftime("%d/%m/%Y") DateJour = time.strftime("%d/%m/%Y")
datelundi = ZAbsences.ddmmyyyy(DateJour).prev_monday() datelundi = sco_abs.ddmmyyyy(DateJour).prev_monday()
FA = [] # formulaire avec menu saisi hebdo des absences FA = [] # formulaire avec menu saisi hebdo des absences
FA.append('<form action="Absences/SignaleAbsenceGrHebdo" method="get">') FA.append('<form action="Absences/SignaleAbsenceGrHebdo" method="get">')
FA.append('<input type="hidden" name="datelundi" value="%s"/>' % datelundi) FA.append('<input type="hidden" name="datelundi" value="%s"/>' % datelundi)

View File

@ -43,7 +43,7 @@ import sco_formsemestre
import sco_formsemestre_status import sco_formsemestre_status
from sco_formsemestre_status import makeMenu from sco_formsemestre_status import makeMenu
import sco_compute_moy import sco_compute_moy
import ZAbsences import sco_abs
# ported from old DTML code in oct 2009 # ported from old DTML code in oct 2009
@ -241,7 +241,7 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No
if authuser.has_permission( if authuser.has_permission(
ScoAbsChange, context ScoAbsChange, context
) and sco_formsemestre.sem_est_courant(context, sem): ) and sco_formsemestre.sem_est_courant(context, sem):
datelundi = ZAbsences.ddmmyyyy(time.strftime("%d/%m/%Y")).prev_monday() datelundi = sco_abs.ddmmyyyy(time.strftime("%d/%m/%Y")).prev_monday()
H.append( H.append(
'<span class="moduleimpl_abs_link"><a class="stdlink" href="Absences/SignaleAbsenceGrHebdo?formsemestre_id=%s&moduleimpl_id=%s&datelundi=%s">Saisie Absences hebdo.</a></span>' '<span class="moduleimpl_abs_link"><a class="stdlink" href="Absences/SignaleAbsenceGrHebdo?formsemestre_id=%s&moduleimpl_id=%s&datelundi=%s">Saisie Absences hebdo.</a></span>'
% (formsemestre_id, moduleimpl_id, datelundi) % (formsemestre_id, moduleimpl_id, datelundi)

View File

@ -36,7 +36,7 @@ from notes_log import log
from gen_tables import GenTable from gen_tables import GenTable
import sco_formsemestre import sco_formsemestre
import sco_groups import sco_groups
import ZAbsences import sco_abs
from sco_codes_parcours import code_semestre_validant, code_semestre_attente from sco_codes_parcours import code_semestre_validant, code_semestre_attente
@ -90,7 +90,7 @@ def etud_get_poursuite_info(context, sem, etud):
rangs.append(["rang_" + codeModule, rangModule]) rangs.append(["rang_" + codeModule, rangModule])
# Absences # Absences
AbsSemEtud = ZAbsences.getAbsSemEtud(context, nt.sem, etudid) AbsSemEtud = sco_abs.getAbsSemEtud(context, nt.sem, etudid)
NbAbs = AbsSemEtud.CountAbs() NbAbs = AbsSemEtud.CountAbs()
NbAbsJust = AbsSemEtud.CountAbsJust() NbAbsJust = AbsSemEtud.CountAbsJust()
if ( if (

View File

@ -38,7 +38,7 @@ import sco_formsemestre
import sco_parcours_dut import sco_parcours_dut
import sco_codes_parcours import sco_codes_parcours
from scolars import format_nom, format_prenom, format_sexe, format_lycee from scolars import format_nom, format_prenom, format_sexe, format_lycee
from ZAbsences import getAbsSemEtud from sco_abs import getAbsSemEtud
def feuille_preparation_jury(context, formsemestre_id, REQUEST): def feuille_preparation_jury(context, formsemestre_id, REQUEST):

View File

@ -29,16 +29,22 @@
- statistiques decisions - statistiques decisions
- suivi cohortes - suivi cohortes
""" """
import os
import tempfile
import urllib
import re
import time
import mx
import mx.DateTime import mx.DateTime
from mx.DateTime import DateTime as mxDateTime from mx.DateTime import DateTime as mxDateTime
import tempfile, urllib, re
import sco_utils as scu
from notesdb import * import VERSION
from sco_utils import *
from notes_log import log from notes_log import log
from gen_tables import GenTable from gen_tables import GenTable
import sco_excel, sco_pdf import sco_excel, sco_pdf
from notesdb import DateDMYtoISO
from sco_exceptions import ScoValueError
import sco_codes_parcours import sco_codes_parcours
from sco_codes_parcours import code_semestre_validant from sco_codes_parcours import code_semestre_validant
import sco_parcours_dut import sco_parcours_dut
@ -129,7 +135,7 @@ def _results_by_category(
if Count.has_key(etud[category]): if Count.has_key(etud[category]):
Count[etud[category]][etud[result]] += 1 Count[etud[category]][etud[result]] += 1
else: else:
Count[etud[category]] = DictDefault(kv_dict={etud[result]: 1}) Count[etud[category]] = scu.DictDefault(kv_dict={etud[result]: 1})
# conversion en liste de dict # conversion en liste de dict
C = [Count[cat] for cat in categories] C = [Count[cat] for cat in categories]
# Totaux par lignes et colonnes # Totaux par lignes et colonnes
@ -214,9 +220,9 @@ def formsemestre_report(
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
) )
# #
tab.filename = make_filename("stats " + sem["titreannee"]) tab.filename = scu.make_filename("stats " + sem["titreannee"])
tab.origin = "Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "" tab.origin = "Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + ""
tab.caption = "Répartition des résultats par %s, semestre %s" % ( tab.caption = "Répartition des résultats par %s, semestre %s" % (
category_name, category_name,
sem["titreannee"], sem["titreannee"],
@ -259,7 +265,7 @@ def formsemestre_report_counts(
Tableau comptage avec choix des categories Tableau comptage avec choix des categories
""" """
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
category_name = strcapitalize(category) category_name = scu.strcapitalize(category)
title = "Comptages " + category_name title = "Comptages " + category_name
etuds = formsemestre_etuds_stats(context, sem, only_primo=only_primo) etuds = formsemestre_etuds_stats(context, sem, only_primo=only_primo)
tab = formsemestre_report( tab = formsemestre_report(
@ -484,7 +490,7 @@ def table_suivi_cohorte(
indices_sems.sort() indices_sems.sort()
for p in P: for p in P:
p.nb_etuds = 0 # nombre total d'etudiants dans la periode p.nb_etuds = 0 # nombre total d'etudiants dans la periode
p.sems_by_id = DictDefault(defaultvalue=[]) p.sems_by_id = scu.DictDefault(defaultvalue=[])
for s in p.sems: for s in p.sems:
p.sems_by_id[s["semestre_id"]].append(s) p.sems_by_id[s["semestre_id"]].append(s)
p.nb_etuds += len(s["members"]) p.nb_etuds += len(s["members"])
@ -623,8 +629,8 @@ def table_suivi_cohorte(
rows=L, rows=L,
html_col_width="4em", html_col_width="4em",
html_sortable=True, html_sortable=True,
filename=make_filename("cohorte " + sem["titreannee"]), filename=scu.make_filename("cohorte " + sem["titreannee"]),
origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "", origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "",
caption="Suivi cohorte " + pp + sem["titreannee"] + dbac, caption="Suivi cohorte " + pp + sem["titreannee"] + dbac,
page_title="Suivi cohorte " + sem["titreannee"], page_title="Suivi cohorte " + sem["titreannee"],
html_class="table_cohorte", html_class="table_cohorte",
@ -666,7 +672,6 @@ def formsemestre_suivi_cohorte(
): ):
"""Affiche suivi cohortes par numero de semestre""" """Affiche suivi cohortes par numero de semestre"""
percent = int(percent) percent = int(percent)
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
tab, expl, bacs, bacspecialites, annee_bacs, sexes, statuts = table_suivi_cohorte( tab, expl, bacs, bacspecialites, annee_bacs, sexes, statuts = table_suivi_cohorte(
context, context,
formsemestre_id, formsemestre_id,
@ -1049,7 +1054,7 @@ def table_suivi_parcours(
etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = tsp_etud_list( etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = tsp_etud_list(
context, formsemestre_id, only_primo=only_primo context, formsemestre_id, only_primo=only_primo
) )
codes_etuds = DictDefault(defaultvalue=[]) codes_etuds = scu.DictDefault(defaultvalue=[])
for etud in etuds: for etud in etuds:
etud["codeparcours"], etud["decisions_jury"] = get_codeparcoursetud( etud["codeparcours"], etud["decisions_jury"] = get_codeparcoursetud(
context, etud context, etud
@ -1105,7 +1110,7 @@ def table_suivi_parcours(
columns_ids=columns_ids, columns_ids=columns_ids,
rows=L, rows=L,
titles=titles, titles=titles,
origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "", origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "",
caption="Parcours suivis, étudiants %s semestre " % primostr caption="Parcours suivis, étudiants %s semestre " % primostr
+ sem["titreannee"], + sem["titreannee"],
page_title="Parcours " + sem["titreannee"], page_title="Parcours " + sem["titreannee"],
@ -1164,7 +1169,6 @@ def formsemestre_suivi_parcours(
REQUEST=None, REQUEST=None,
): ):
"""Effectifs dans les differents parcours possibles.""" """Effectifs dans les differents parcours possibles."""
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
tab = table_suivi_parcours( tab = table_suivi_parcours(
context, context,
formsemestre_id, formsemestre_id,
@ -1211,9 +1215,8 @@ def graph_parcours(
statut="", statut="",
): ):
"""""" """"""
if not WITH_PYDOT: if not scu.WITH_PYDOT:
raise ScoValueError("pydot module is not installed") raise ScoValueError("pydot module is not installed")
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = tsp_etud_list( etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = tsp_etud_list(
context, context,
formsemestre_id, formsemestre_id,
@ -1227,12 +1230,12 @@ def graph_parcours(
# log('graph_parcours: %s etuds (only_primo=%s)' % (len(etuds), only_primo)) # log('graph_parcours: %s etuds (only_primo=%s)' % (len(etuds), only_primo))
if not etuds: if not etuds:
return "", etuds, bacs, bacspecialites, annee_bacs, sexes, statuts return "", etuds, bacs, bacspecialites, annee_bacs, sexes, statuts
edges = DictDefault( edges = scu.DictDefault(
defaultvalue=set() defaultvalue=set()
) # {(formsemestre_id_origin, formsemestre_id_dest) : etud_set} ) # {(formsemestre_id_origin, formsemestre_id_dest) : etud_set}
sems = {} sems = {}
effectifs = DictDefault(defaultvalue=set()) # formsemestre_id : etud_set effectifs = scu.DictDefault(defaultvalue=set()) # formsemestre_id : etud_set
decisions = DictDefault(defaultvalue={}) # formsemestre_id : { code : nb_etud } decisions = scu.DictDefault(defaultvalue={}) # formsemestre_id : { code : nb_etud }
isolated_nodes = [] isolated_nodes = []
connected_nodes = set() connected_nodes = set()
diploma_nodes = [] diploma_nodes = []
@ -1279,7 +1282,11 @@ def graph_parcours(
dem_nodes[s["formsemestre_id"]] = nid dem_nodes[s["formsemestre_id"]] = nid
edges[(s["formsemestre_id"], nid)].add(etudid) edges[(s["formsemestre_id"], nid)].add(etudid)
# ajout noeud pour NAR (seulement pour noeud de depart) # ajout noeud pour NAR (seulement pour noeud de depart)
if s["formsemestre_id"] == formsemestre_id and dec and dec["code"] == NAR: if (
s["formsemestre_id"] == formsemestre_id
and dec
and dec["code"] == sco_codes_parcours.NAR
):
nid = "_nar_" + s["formsemestre_id"] nid = "_nar_" + s["formsemestre_id"]
nar_nodes[s["formsemestre_id"]] = nid nar_nodes[s["formsemestre_id"]] = nid
edges[(s["formsemestre_id"], nid)].add(etudid) edges[(s["formsemestre_id"], nid)].add(etudid)
@ -1295,10 +1302,10 @@ def graph_parcours(
edges[(s["formsemestre_id"], nid)].add(etudid) edges[(s["formsemestre_id"], nid)].add(etudid)
diploma_nodes.append(nid) diploma_nodes.append(nid)
# #
g = pydot.graph_from_edges(edges.keys()) g = scu.pydot.graph_from_edges(edges.keys())
for fid in isolated_nodes: for fid in isolated_nodes:
if not fid in connected_nodes: if not fid in connected_nodes:
n = pydot.Node(name=fid) n = scu.pydot.Node(name=fid)
g.add_node(n) g.add_node(n)
g.set("rankdir", "LR") # left to right g.set("rankdir", "LR") # left to right
g.set_fontname("Helvetica") g.set_fontname("Helvetica")
@ -1306,7 +1313,7 @@ def graph_parcours(
g.set_bgcolor("#fffff0") # ou 'transparent' g.set_bgcolor("#fffff0") # ou 'transparent'
# titres des semestres: # titres des semestres:
for s in sems.values(): for s in sems.values():
n = pydot_get_node(g, s["formsemestre_id"]) n = scu.pydot_get_node(g, s["formsemestre_id"])
log("s['formsemestre_id'] = %s" % s["formsemestre_id"]) log("s['formsemestre_id'] = %s" % s["formsemestre_id"])
log("n=%s" % n) log("n=%s" % n)
log("get=%s" % g.get_node(s["formsemestre_id"])) log("get=%s" % g.get_node(s["formsemestre_id"]))
@ -1324,31 +1331,31 @@ def graph_parcours(
s["annee_fin"][2:], s["annee_fin"][2:],
len(effectifs[s["formsemestre_id"]]), len(effectifs[s["formsemestre_id"]]),
) )
n.set("label", suppress_accents(label)) n.set("label", scu.suppress_accents(label))
n.set_fontname("Helvetica") n.set_fontname("Helvetica")
n.set_fontsize(8.0) n.set_fontsize(8.0)
n.set_width(1.2) n.set_width(1.2)
n.set_shape("box") n.set_shape("box")
n.set_URL("formsemestre_status?formsemestre_id=" + s["formsemestre_id"]) n.set_URL("formsemestre_status?formsemestre_id=" + s["formsemestre_id"])
# semestre de depart en vert # semestre de depart en vert
n = pydot_get_node(g, formsemestre_id) n = scu.pydot_get_node(g, formsemestre_id)
n.set_color("green") n.set_color("green")
# demissions en rouge, octagonal # demissions en rouge, octagonal
for nid in dem_nodes.values(): for nid in dem_nodes.values():
n = pydot_get_node(g, nid) n = scu.pydot_get_node(g, nid)
n.set_color("red") n.set_color("red")
n.set_shape("octagon") n.set_shape("octagon")
n.set("label", "Dem.") n.set("label", "Dem.")
# NAR en rouge, Mcircle # NAR en rouge, Mcircle
for nid in nar_nodes.values(): for nid in nar_nodes.values():
n = pydot_get_node(g, nid) n = scu.pydot_get_node(g, nid)
n.set_color("red") n.set_color("red")
n.set_shape("Mcircle") n.set_shape("Mcircle")
n.set("label", NAR) n.set("label", sco_codes_parcours.NAR)
# diplomes: # diplomes:
for nid in diploma_nodes: for nid in diploma_nodes:
n = pydot_get_node(g, nid) n = scu.pydot_get_node(g, nid)
n.set_color("red") n.set_color("red")
n.set_shape("ellipse") n.set_shape("ellipse")
n.set("label", "Diplome") # bug si accent (pas compris pourquoi) n.set("label", "Diplome") # bug si accent (pas compris pourquoi)
@ -1367,7 +1374,7 @@ def graph_parcours(
bubbles[src_id + ":" + dst_id] = etud_descr bubbles[src_id + ":" + dst_id] = etud_descr
e.set_URL("__xxxetudlist__?" + src_id + ":" + dst_id) e.set_URL("__xxxetudlist__?" + src_id + ":" + dst_id)
# Genere graphe # Genere graphe
f, path = tempfile.mkstemp(".gr") _, path = tempfile.mkstemp(".gr")
g.write(path=path, format=format) g.write(path=path, format=format)
data = open(path, "r").read() data = open(path, "r").read()
log("dot generated %d bytes in %s format" % (len(data), format)) log("dot generated %d bytes in %s format" % (len(data), format))
@ -1403,7 +1410,7 @@ def graph_parcours(
) )
return ( return (
'<a xlink:href="formsemestre_status?formsemestre_id=%s" xlink:title="%s"' '<a xlink:href="formsemestre_status?formsemestre_id=%s" xlink:title="%s"'
% (fid, suppress_accents(title)) % (fid, scu.suppress_accents(title))
) # evite accents car svg utf-8 vs page en latin1... ) # evite accents car svg utf-8 vs page en latin1...
data = exp1.sub(repl_title, data) data = exp1.sub(repl_title, data)
@ -1443,8 +1450,8 @@ def formsemestre_graph_parcours(
sexe=sexe, sexe=sexe,
statut=statut, statut=statut,
) )
filename = make_filename("flux " + sem["titreannee"]) filename = scu.make_filename("flux " + sem["titreannee"])
return sco_pdf.sendPDFFile(REQUEST, doc, filename + ".pdf") return scu.sendPDFFile(REQUEST, doc, filename + ".pdf")
elif format == "png": elif format == "png":
# #
doc, etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = graph_parcours( doc, etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = graph_parcours(
@ -1458,7 +1465,7 @@ def formsemestre_graph_parcours(
sexe=sexe, sexe=sexe,
statut=statut, statut=statut,
) )
filename = make_filename("flux " + sem["titreannee"]) filename = scu.make_filename("flux " + sem["titreannee"])
REQUEST.RESPONSE.setHeader( REQUEST.RESPONSE.setHeader(
"content-disposition", 'attachment; filename="%s"' % filename "content-disposition", 'attachment; filename="%s"' % filename
) )

View File

@ -40,7 +40,7 @@ import tempfile
from notes_log import log from notes_log import log
from sco_utils import * from sco_utils import *
import ZAbsences import sco_abs
import scolars import scolars
import sco_photos import sco_photos
import sco_formsemestre import sco_formsemestre
@ -301,9 +301,9 @@ def pdf_feuille_releve_absences(
NB_CELL_PM = context.get_preference("feuille_releve_abs_PM") NB_CELL_PM = context.get_preference("feuille_releve_abs_PM")
COLWIDTH = 0.85 * cm COLWIDTH = 0.85 * cm
if context.get_preference("feuille_releve_abs_samedi"): if context.get_preference("feuille_releve_abs_samedi"):
days = ZAbsences.DAYNAMES[:6] # Lundi, ..., Samedi days = sco_abs.DAYNAMES[:6] # Lundi, ..., Samedi
else: else:
days = ZAbsences.DAYNAMES[:5] # Lundi, ..., Vendredi days = sco_abs.DAYNAMES[:5] # Lundi, ..., Vendredi
nb_days = len(days) nb_days = len(days)
# Informations sur les groupes à afficher: # Informations sur les groupes à afficher: