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 jaxml
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 *
@ -68,10 +75,8 @@ import sco_groups_view
import sco_excel
import sco_abs_notification, sco_abs_views
import sco_compute_moy
import string, re
import time, calendar
from mx.DateTime import DateTime as mxDateTime
from mx.DateTime.ISO import ParseDateTimeUTC
import sco_abs
from sco_abs import ddmmyyyy
def _toboolean(x):
@ -84,189 +89,6 @@ def _toboolean(x):
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(
ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit
):
@ -373,7 +195,7 @@ class ZAbsences(
% vars(),
)
cnx.commit()
invalidateAbsEtudDate(self, etudid, jour)
sco_abs.invalidateAbsEtudDate(self, etudid, jour)
sco_abs_notification.abs_notify(self, etudid, jour)
def _AddJustif(self, etudid, jour, matin, REQUEST, description=None):
@ -396,7 +218,7 @@ class ZAbsences(
msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(),
)
cnx.commit()
invalidateAbsEtudDate(self, etudid, jour)
sco_abs.invalidateAbsEtudDate(self, etudid, jour)
def _AnnuleAbsence(self, etudid, jour, matin, moduleimpl_id=None, REQUEST=None):
"""Annule une absence ds base
@ -419,7 +241,7 @@ class ZAbsences(
% vars(),
)
cnx.commit()
invalidateAbsEtudDate(self, etudid, jour)
sco_abs.invalidateAbsEtudDate(self, etudid, jour)
def _AnnuleJustif(self, etudid, jour, matin, REQUEST=None):
"Annule un justificatif"
@ -443,7 +265,7 @@ class ZAbsences(
msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(),
)
cnx.commit()
invalidateAbsEtudDate(self, etudid, jour)
sco_abs.invalidateAbsEtudDate(self, etudid, jour)
# Fonction inutile à supprimer (gestion moduleimpl_id incorrecte):
# def _AnnuleAbsencesPeriodNoJust(self, etudid, datedebut, datefin,
@ -462,8 +284,8 @@ class ZAbsences(
# logdb(REQUEST, cnx, 'AnnuleAbsencesPeriodNoJust', etudid=etudid,
# msg='%(datedebut)s - %(datefin)s - (moduleimpl_id)s'%vars())
# cnx.commit()
# 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, datedebut)
# 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")
@ -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",
vars(),
)
invalidateAbsEtudDate(self, etudid, date)
sco_abs.invalidateAbsEtudDate(self, etudid, date)
# s'assure que les justificatifs ne sont pas "absents"
for date in dates:
cursor.execute(
@ -855,7 +677,7 @@ class ZAbsences(
js = ""
else:
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
# --- Misc tools.... ------------------
@ -1352,7 +1174,7 @@ class ZAbsences(
else:
checked = ""
# bulle lors du passage souris
coljour = DAYNAMES[
coljour = sco_abs.DAYNAMES[
(calendar.weekday(int(date[:4]), int(date[5:7]), int(date[8:])))
]
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)
#
cnx = self.GetDBConnexion()
billet_id = billet_absence_create(
billet_id = sco_abs.billet_absence_create(
cnx,
{
"etudid": etud["etudid"],
@ -1814,7 +1636,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
if REQUEST:
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)
log(
"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]
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)
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):
"""Page liste des billets non traités et formulaire recherche d'un billet"""
cnx = self.GetDBConnexion()
billets = billet_absence_list(cnx, {"etat": 0})
billets = sco_abs.billet_absence_list(cnx, {"etat": 0})
tab = self._tableBillets(billets)
T = tab.html()
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):
"""Supprime un billet."""
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:
return REQUEST.RESPONSE.redirect(
"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},
)
billet_absence_delete(cnx, billet_id)
sco_abs.billet_absence_delete(cnx, billet_id)
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
# 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
@ -2059,7 +1881,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
def ProcessBilletAbsenceForm(self, billet_id, REQUEST=None):
"""Formulaire traitement d'un billet"""
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:
return REQUEST.RESPONSE.redirect(
"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>'
% (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)
H.append(tab.html())
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)
_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
@ -2441,121 +2011,3 @@ def manage_addZAbsences(
# The form used to get the instance id from the user.
# 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",
args,
)
titles, res = [x[0] for x in cursor.description], cursor.dictfetchall()
_, res = [x[0] for x in cursor.description], cursor.dictfetchall()
R = []
for r in res:
r["etud_prenom"] = r["etud_prenom"] or ""
@ -450,7 +450,6 @@ class ZEntreprises(
def do_entreprise_correspondant_listnames(self, args={}):
"-> liste des noms des correspondants (pour affichage menu)"
cnx = self.GetDBConnexion()
C = self.do_entreprise_correspondant_list(args=args)
return [
(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)
raise NotImplementedError
# XXXXX fonction non achevee , non testee...
cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor)
vals = dictfilter(args, self.dbfields)
# DBSelect
what = ["*"]
operator = " " + operator + " "
cond = " E.entreprise_id = C.entreprise_id "
if vals:
cond += " where " + operator.join(
["%s%s%%(%s)s" % (x, test, x) for x in vals.keys() if vals[x] != None]
)
cnuls = " and ".join(
["%s is NULL" % x for x in vals.keys() if vals[x] is None]
)
if cnuls:
cond = cond + " and " + cnuls
else:
cond += ""
cursor.execute(
"select distinct"
+ ", ".join(what)
+ " from entreprises E, entreprise_contact C "
+ cond
+ orderby,
vals,
)
titles, res = [x[0] for x in cursor.description], cursor.fetchall()
#
R = []
for r in res:
d = {}
for i in range(len(titles)):
v = r[i]
# value not formatted ! (see EditableTable.list())
d[titles[i]] = v
R.append(d)
return R
# cnx = self.GetDBConnexion()
# cursor = cnx.cursor(cursor_factory=ScoDocCursor)
# if sortkey:
# orderby = " order by " + sortkey
# else:
# orderby = ""
# vals = dictfilter(args, self.dbfields)
# # DBSelect
# what = ["*"]
# operator = " " + operator + " "
# cond = " E.entreprise_id = C.entreprise_id "
# if vals:
# cond += " where " + operator.join(
# ["%s%s%%(%s)s" % (x, test, x) for x in vals.keys() if vals[x] != None]
# )
# cnuls = " and ".join(
# ["%s is NULL" % x for x in vals.keys() if vals[x] is None]
# )
# if cnuls:
# cond = cond + " and " + cnuls
# else:
# cond += ""
# cursor.execute(
# "select distinct"
# + ", ".join(what)
# + " from entreprises E, entreprise_contact C "
# + cond
# + orderby,
# vals,
# )
# titles, res = [x[0] for x in cursor.description], cursor.fetchall()
# #
# R = []
# for r in res:
# d = {}
# 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
security.declareProtected(ScoEntrepriseChange, "entreprise_create")
@ -892,7 +896,3 @@ def manage_addZEntreprises(
if REQUEST is not None:
return self.manage_main(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 sco_modalites
import ImportScolars
import sco_abs
import sco_portal_apogee
import sco_synchro_etuds
import sco_page_etud
@ -274,13 +275,13 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
</p>
</body>
"""
return (
self.sco_header(REQUEST)
+ """<div class="xp">%s</div>""" % x
+ self.sco_footer(REQUEST)
)
b = "<p>Hello, World !</p><br/>"
raise ValueError("essai exception")
# return (
# self.sco_header(REQUEST)
# + """<div class="xp">%s</div>""" % x
# + self.sco_footer(REQUEST)
# )
# b = "<p>Hello, World !</p><br/>"
# raise ValueError("essai exception")
# raise ScoValueError('essai exception !', dest_url='totoro', REQUEST=REQUEST)
# 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
try:
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.append(
'<td><form action="Absences/SignaleAbsenceGrSemestre" method="get">'

View File

@ -26,7 +26,7 @@
##############################################################################
from sco_utils import *
from ZAbsences import getAbsSemEtud
from sco_abs import getAbsSemEtud
"""
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_photos
import ZAbsences
import sco_abs
def doSignaleAbsence(
@ -631,7 +631,7 @@ def CalAbs(context, REQUEST=None): # etud implied
events.append(
(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 = [

View File

@ -44,7 +44,7 @@ import sco_groups
import sco_pvjury
import sco_formsemestre_status
import sco_photos
import ZAbsences
import sco_abs
import sco_abs_views
import sco_preferences
import sco_codes_parcours
@ -150,7 +150,7 @@ def formsemestre_bulletinetud_dict(
context, pid
)
# --- Absences
AbsSemEtud = ZAbsences.getAbsSemEtud(context, nt.sem, etudid)
AbsSemEtud = sco_abs.getAbsSemEtud(context, nt.sem, etudid)
I["nbabs"] = AbsSemEtud.CountAbs()
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 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
from notes_log import log
import sco_formsemestre
from sco_pdf import *
import sco_pdf
from sco_pdf import PDFLOCK
BULLETIN_CLASSES = (
collections.OrderedDict()
@ -145,7 +155,7 @@ class BulletinGenerator:
dt,
self.infos["etud"]["nom"],
)
filename = unescape_html(filename).replace(" ", "_").replace("&", "")
filename = sco_utils.unescape_html(filename).replace(" ", "_").replace("&", "")
return filename
def generate(self, format="", stand_alone=True):
@ -166,8 +176,10 @@ class BulletinGenerator:
def generate_html(self):
"""Return bulletin as an HTML string"""
H = ['<div class="notes_bulletin">']
H.append(self.bul_table(format="html")) # table des notes
H.append(self.bul_part_below(format="html")) # infos sous la table
# table des notes:
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>")
return "\n".join(H)
@ -179,10 +191,14 @@ class BulletinGenerator:
"""
formsemestre_id = self.infos["formsemestre_id"]
objects = self.bul_title_pdf() # partie haute du bulletin
objects += self.bul_table(format="pdf") # table des notes
objects += self.bul_part_below(format="pdf") # infos sous la table
objects += self.bul_signatures_pdf() # signatures
# partie haute du bulletin
objects = self.bul_title_pdf() # pylint: disable=no-member
# table des notes
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
objects = [KeepInFrame(0, 0, objects, mode="shrink")]
@ -194,13 +210,13 @@ class BulletinGenerator:
# Generation du document PDF
sem = sco_formsemestre.get_formsemestre(self.context, formsemestre_id)
report = cStringIO.StringIO() # in-memory document, no disk file
document = BaseDocTemplate(report)
document = sco_pdf.BaseDocTemplate(report)
document.addPageTemplates(
ScolarsPageTemplate(
sco_pdf.ScolarsPageTemplate(
document,
context=self.context,
author="%s %s (E. Viennet) [%s]"
% (SCONAME, SCOVERSION, self.description),
% (VERSION.SCONAME, VERSION.SCOVERSION, self.description),
title="Bulletin %s de %s"
% (sem["titremois"], self.infos["etud"]["nomprenom"]),
subject="Bulletin de note",
@ -222,7 +238,9 @@ class BulletinGenerator:
"""
try:
# 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:
# enquête sur exception intermittente...
log("*** bug in PDF buildTableObject:")

View File

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

View File

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

View File

@ -37,7 +37,7 @@ import sco_formsemestre
import sco_groups
import sco_evaluations
from sco_formulas import *
import ZAbsences
import sco_abs
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)
"""
if use_abs:
AbsSemEtud = ZAbsences.getAbsSemEtud(context, sem, etudid)
AbsSemEtud = sco_abs.getAbsSemEtud(context, sem, etudid)
nbabs = AbsSemEtud.CountAbs()
nbabs_just = AbsSemEtud.CountAbsJust()
else:

View File

@ -42,7 +42,7 @@ from TrivialFormulator import TrivialFormulator
import sco_news
import sco_formsemestre
import sco_groups
import ZAbsences
import sco_abs
import sco_evaluations
# --------------------------------------------------------------------
@ -487,7 +487,7 @@ def formsemestre_evaluations_cal(context, formsemestre_id, REQUEST=None):
if day > today:
e[2] = color_futur
CalHTML = ZAbsences.YearTable(
CalHTML = sco_abs.YearTable(
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 notes_table import *
import notes_table
from ZAbsences import getAbsSemEtud
from sco_abs import getAbsSemEtud
import sco_formsemestre
import sco_formsemestre_edit

View File

@ -35,7 +35,7 @@ from sco_utils import *
import html_sco_header
from gen_tables import GenTable
import scolars
import ZAbsences
import sco_abs
import sco_excel
import sco_formsemestre
import sco_groups
@ -870,7 +870,7 @@ def form_choix_jour_saisie_hebdo(
if not authuser.has_permission(ScoAbsChange, context):
return ""
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()
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 !
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.append('<form action="Absences/SignaleAbsenceGrHebdo" method="get">')
FA.append('<input type="hidden" name="datelundi" value="%s"/>' % datelundi)

View File

@ -43,7 +43,7 @@ import sco_formsemestre
import sco_formsemestre_status
from sco_formsemestre_status import makeMenu
import sco_compute_moy
import ZAbsences
import sco_abs
# 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(
ScoAbsChange, context
) 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(
'<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)

View File

@ -36,7 +36,7 @@ from notes_log import log
from gen_tables import GenTable
import sco_formsemestre
import sco_groups
import ZAbsences
import sco_abs
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])
# Absences
AbsSemEtud = ZAbsences.getAbsSemEtud(context, nt.sem, etudid)
AbsSemEtud = sco_abs.getAbsSemEtud(context, nt.sem, etudid)
NbAbs = AbsSemEtud.CountAbs()
NbAbsJust = AbsSemEtud.CountAbsJust()
if (

View File

@ -38,7 +38,7 @@ import sco_formsemestre
import sco_parcours_dut
import sco_codes_parcours
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):

View File

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

View File

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