Compare commits

..

9 Commits

186 changed files with 1089 additions and 1585 deletions

View File

@ -93,7 +93,7 @@ ADMISSION_MODIFIABLE_FIELDS = (
def sco_import_format(with_codesemestre=True): def sco_import_format(with_codesemestre=True):
"returns tuples (Attribut, Type, Table, AllowNulls, Description)" "returns tuples (Attribut, Type, Table, AllowNulls, Description)"
r = [] r = []
for l in open(SCO_SRCDIR + "/" + FORMAT_FILE): for l in open(SCO_SRC_DIR + "/" + FORMAT_FILE):
l = l.strip() l = l.strip()
if l and l[0] != "#": if l and l[0] != "#":
fs = l.split(";") fs = l.split(";")

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "7.19a" SCOVERSION = "8.00a"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"

View File

@ -45,19 +45,17 @@ L'API de plus bas niveau est en gros:
""" """
import urllib import urllib
import datetime
import jaxml
# ---------------
from sco_zope import * from sco_zope import *
# --------------- # ---------------
import sco_utils as scu
import notesdb from notesdb import *
from notes_log import log from notes_log import log
from scolog import logdb from scolog import logdb
from sco_permissions import ScoAbsAddBillet, ScoAbsChange, ScoView from sco_utils import *
from sco_exceptions import ScoValueError, ScoInvalidDateError
# import notes_users
from TrivialFormulator import TrivialFormulator, TF from TrivialFormulator import TrivialFormulator, TF
from gen_tables import GenTable from gen_tables import GenTable
import scolars import scolars
@ -203,7 +201,7 @@ class ddmmyyyy:
return self.prev(self.weekday) return self.prev(self.weekday)
def __cmp__(self, other): def __cmp__(self, other):
"""return a negative integer if self < other, """return a negative integer if self < other,
zero if self == other, a positive integer if self > other""" zero if self == other, a positive integer if self > other"""
return int(self.time - other.time) return int(self.time - other.time)
@ -358,7 +356,7 @@ class ZAbsences(
estjust = _toboolean(estjust) estjust = _toboolean(estjust)
matin = _toboolean(matin) matin = _toboolean(matin)
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute( cursor.execute(
"insert into absences (etudid,jour,estabs,estjust,matin,description, moduleimpl_id) values (%(etudid)s, %(jour)s, TRUE, %(estjust)s, %(matin)s, %(description)s, %(moduleimpl_id)s )", "insert into absences (etudid,jour,estabs,estjust,matin,description, moduleimpl_id) values (%(etudid)s, %(jour)s, TRUE, %(estjust)s, %(matin)s, %(description)s, %(moduleimpl_id)s )",
vars(), vars(),
@ -382,7 +380,7 @@ class ZAbsences(
raise ScoValueError("date justificatif trop loin dans le futur !") raise ScoValueError("date justificatif trop loin dans le futur !")
matin = _toboolean(matin) matin = _toboolean(matin)
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute( cursor.execute(
"insert into absences (etudid,jour,estabs,estjust,matin, description) values (%(etudid)s,%(jour)s, FALSE, TRUE, %(matin)s, %(description)s )", "insert into absences (etudid,jour,estabs,estjust,matin, description) values (%(etudid)s,%(jour)s, FALSE, TRUE, %(matin)s, %(description)s )",
vars(), vars(),
@ -404,7 +402,7 @@ class ZAbsences(
# unpublished # unpublished
matin = _toboolean(matin) matin = _toboolean(matin)
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
req = "delete from absences where jour=%(jour)s and matin=%(matin)s and etudid=%(etudid)s and estabs" req = "delete from absences where jour=%(jour)s and matin=%(matin)s and etudid=%(etudid)s and estabs"
if moduleimpl_id: if moduleimpl_id:
req += " and moduleimpl_id=%(moduleimpl_id)s" req += " and moduleimpl_id=%(moduleimpl_id)s"
@ -425,7 +423,7 @@ class ZAbsences(
# unpublished # unpublished
matin = _toboolean(matin) matin = _toboolean(matin)
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute( cursor.execute(
"delete from absences where jour=%(jour)s and matin=%(matin)s and etudid=%(etudid)s and ESTJUST AND NOT ESTABS", "delete from absences where jour=%(jour)s and matin=%(matin)s and etudid=%(etudid)s and ESTJUST AND NOT ESTABS",
vars(), vars(),
@ -452,7 +450,7 @@ class ZAbsences(
# """ # """
# # unpublished # # unpublished
# cnx = self.GetDBConnexion() # cnx = self.GetDBConnexion()
# cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) # cursor = cnx.cursor(cursor_factory=ScoDocCursor)
# # supr les absences non justifiees # # supr les absences non justifiees
# cursor.execute("delete from absences where etudid=%(etudid)s and (not estjust) and moduleimpl_id=(moduleimpl_id)s and jour BETWEEN %(datedebut)s AND %(datefin)s", # cursor.execute("delete from absences where etudid=%(etudid)s and (not estjust) and moduleimpl_id=(moduleimpl_id)s and jour BETWEEN %(datedebut)s AND %(datefin)s",
# vars() ) # vars() )
@ -489,7 +487,7 @@ class ZAbsences(
self._AnnuleAbsence(etudid, jour, matin, moduleimpl_id, REQUEST) self._AnnuleAbsence(etudid, jour, matin, moduleimpl_id, REQUEST)
return return
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
# supr les absences non justifiees # supr les absences non justifiees
for date in dates: for date in dates:
cursor.execute( cursor.execute(
@ -536,7 +534,7 @@ class ZAbsences(
else: else:
modul = "" modul = ""
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute( cursor.execute(
"""SELECT COUNT(*) AS NbAbs FROM ( """SELECT COUNT(*) AS NbAbs FROM (
SELECT DISTINCT A.JOUR, A.MATIN SELECT DISTINCT A.JOUR, A.MATIN
@ -567,7 +565,7 @@ class ZAbsences(
else: else:
modul = "" modul = ""
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute( cursor.execute(
"""SELECT COUNT(*) AS NbAbsJust FROM ( """SELECT COUNT(*) AS NbAbsJust FROM (
SELECT DISTINCT A.JOUR, A.MATIN SELECT DISTINCT A.JOUR, A.MATIN
@ -590,7 +588,7 @@ class ZAbsences(
def _ListeAbsDate(self, etudid, beg_date, end_date): def _ListeAbsDate(self, etudid, beg_date, end_date):
# Liste des absences et justifs entre deux dates # Liste des absences et justifs entre deux dates
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute( cursor.execute(
"""SELECT jour, matin, estabs, estjust, description FROM ABSENCES A """SELECT jour, matin, estabs, estjust, description FROM ABSENCES A
WHERE A.ETUDID = %(etudid)s WHERE A.ETUDID = %(etudid)s
@ -600,6 +598,7 @@ class ZAbsences(
vars(), vars(),
) )
Abs = cursor.dictfetchall() Abs = cursor.dictfetchall()
# log('ListeAbsDate: abs=%s' % Abs)
# remove duplicates # remove duplicates
A = {} # { (jour, matin) : abs } A = {} # { (jour, matin) : abs }
for a in Abs: for a in Abs:
@ -626,6 +625,7 @@ class ZAbsences(
# sort # sort
R = A.values() R = A.values()
R.sort(key=lambda x: (x["begin"])) R.sort(key=lambda x: (x["begin"]))
# log('R=%s' % R)
return R return R
security.declareProtected(ScoView, "ListeAbsJust") security.declareProtected(ScoView, "ListeAbsJust")
@ -633,7 +633,7 @@ class ZAbsences(
def ListeAbsJust(self, etudid, datedebut): def ListeAbsJust(self, etudid, datedebut):
"Liste des absences justifiees (par ordre chronologique)" "Liste des absences justifiees (par ordre chronologique)"
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute( cursor.execute(
"""SELECT DISTINCT A.ETUDID, A.JOUR, A.MATIN FROM ABSENCES A, ABSENCES B """SELECT DISTINCT A.ETUDID, A.JOUR, A.MATIN FROM ABSENCES A, ABSENCES B
WHERE A.ETUDID = %(etudid)s WHERE A.ETUDID = %(etudid)s
@ -654,7 +654,7 @@ class ZAbsences(
def ListeAbsNonJust(self, etudid, datedebut): def ListeAbsNonJust(self, etudid, datedebut):
"Liste des absences NON justifiees (par ordre chronologique)" "Liste des absences NON justifiees (par ordre chronologique)"
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute( cursor.execute(
"""SELECT ETUDID, JOUR, MATIN FROM ABSENCES A """SELECT ETUDID, JOUR, MATIN FROM ABSENCES A
WHERE A.ETUDID = %(etudid)s WHERE A.ETUDID = %(etudid)s
@ -680,7 +680,7 @@ class ZAbsences(
Si only_no_abs: seulement les justificatifs correspondant aux jours sans absences relevées. Si only_no_abs: seulement les justificatifs correspondant aux jours sans absences relevées.
""" """
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
req = """SELECT DISTINCT ETUDID, JOUR, MATIN FROM ABSENCES A req = """SELECT DISTINCT ETUDID, JOUR, MATIN FROM ABSENCES A
WHERE A.ETUDID = %(etudid)s WHERE A.ETUDID = %(etudid)s
AND A.ESTJUST AND A.ESTJUST
@ -704,7 +704,7 @@ class ZAbsences(
"Description associee a l'absence" "Description associee a l'absence"
if not cursor: if not cursor:
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
a = a.copy() a = a.copy()
# a['jour'] = a['jour'].date() # a['jour'] = a['jour'].date()
if a["matin"]: # devrait etre booleen... :-( if a["matin"]: # devrait etre booleen... :-(
@ -732,6 +732,7 @@ class ZAbsences(
if desc: if desc:
return "(%s) %s" % (desc, module) return "(%s) %s" % (desc, module)
return desc
if module: if module:
return module return module
return "" return ""
@ -744,7 +745,7 @@ class ZAbsences(
is_just: idem is_just: idem
""" """
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
req = """SELECT DISTINCT etudid, jour, matin FROM ABSENCES A req = """SELECT DISTINCT etudid, jour, matin FROM ABSENCES A
WHERE A.jour = %(date)s WHERE A.jour = %(date)s
""" """
@ -768,7 +769,7 @@ class ZAbsences(
def ListeAbsNonJustJour(self, date, am=True, pm=True): def ListeAbsNonJustJour(self, date, am=True, pm=True):
"Liste des absences non justifiees ce jour" "Liste des absences non justifiees ce jour"
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
reqa = "" reqa = ""
if not am: if not am:
reqa += " AND NOT matin " reqa += " AND NOT matin "
@ -848,7 +849,7 @@ class ZAbsences(
def CalSelectWeek(self, year=None, REQUEST=None): def CalSelectWeek(self, year=None, REQUEST=None):
"display calendar allowing week selection" "display calendar allowing week selection"
if not year: if not year:
year = scu.AnneeScolaire(REQUEST) year = AnneeScolaire(REQUEST)
sems = sco_formsemestre.do_formsemestre_list(self) sems = sco_formsemestre.do_formsemestre_list(self)
if not sems: if not sems:
js = "" js = ""
@ -885,9 +886,10 @@ class ZAbsences(
security.declareProtected(ScoView, "ListMondays") security.declareProtected(ScoView, "ListMondays")
def ListMondays(self, year=None, REQUEST=None): def ListMondays(self, year=None, REQUEST=None):
"""return list of mondays (ISO dates), from september to june""" """return list of mondays (ISO dates), from september to june
"""
if not year: if not year:
year = scu.AnneeScolaire(REQUEST) year = AnneeScolaire(REQUEST)
d = ddmmyyyy("1/9/%d" % year, work_saturday=self.is_work_saturday()) d = ddmmyyyy("1/9/%d" % year, work_saturday=self.is_work_saturday())
while d.weekday != 0: while d.weekday != 0:
d = d.next() d = d.next()
@ -932,7 +934,7 @@ class ZAbsences(
): ):
"Saisie hebdomadaire des absences" "Saisie hebdomadaire des absences"
if not moduleimpl_id: if not moduleimpl_id:
moduleimpl_id = None moduleimp_id = None
groups_infos = sco_groups_view.DisplayedGroupsInfos( groups_infos = sco_groups_view.DisplayedGroupsInfos(
self, group_ids, REQUEST=REQUEST self, group_ids, REQUEST=REQUEST
@ -961,10 +963,10 @@ class ZAbsences(
)[0] )[0]
# calcule dates jours de cette semaine # calcule dates jours de cette semaine
# liste de dates iso "yyyy-mm-dd" datessem = [DateDMYtoISO(datelundi)]
datessem = [notesdb.DateDMYtoISO(datelundi)] for jour in self.day_names()[1:]:
for _ in self.day_names()[1:]:
datessem.append(self.NextISODay(datessem[-1])) datessem.append(self.NextISODay(datessem[-1]))
# #
if groups_infos.tous_les_etuds_du_sem: if groups_infos.tous_les_etuds_du_sem:
gr_tit = "en" gr_tit = "en"
@ -1042,7 +1044,9 @@ class ZAbsences(
% {"menu_module": menu_module, "url": base_url, "sel": sel} % {"menu_module": menu_module, "url": base_url, "sel": sel}
) )
H += self._gen_form_saisie_groupe(etuds, datessem, destination, moduleimpl_id) H += self._gen_form_saisie_groupe(
etuds, self.day_names(), datessem, destination, None, moduleimpl_id
)
H.append(self.sco_footer(REQUEST)) H.append(self.sco_footer(REQUEST))
return "\n".join(H) return "\n".join(H)
@ -1059,7 +1063,8 @@ class ZAbsences(
moduleimpl_id=None, moduleimpl_id=None,
REQUEST=None, REQUEST=None,
): ):
"""Saisie des absences sur une journée sur un semestre (ou intervalle de dates) entier""" """Saisie des absences sur une journée sur un semestre (ou intervalle de dates) entier
"""
# log('SignaleAbsenceGrSemestre: moduleimpl_id=%s destination=%s' % (moduleimpl_id, destination)) # log('SignaleAbsenceGrSemestre: moduleimpl_id=%s destination=%s' % (moduleimpl_id, destination))
groups_infos = sco_groups_view.DisplayedGroupsInfos( groups_infos = sco_groups_view.DisplayedGroupsInfos(
self, group_ids, REQUEST=REQUEST self, group_ids, REQUEST=REQUEST
@ -1078,12 +1083,15 @@ class ZAbsences(
] ]
if not moduleimpl_id: if not moduleimpl_id:
moduleimpl_id = None moduleimp_id = None
base_url_noweeks = "SignaleAbsenceGrSemestre?datedebut=%s&amp;datefin=%s&amp;%s&amp;destination=%s" % ( base_url_noweeks = (
datedebut, "SignaleAbsenceGrSemestre?datedebut=%s&amp;datefin=%s&amp;%s&amp;destination=%s"
datefin, % (
groups_infos.groups_query_args, datedebut,
urllib.quote(destination), datefin,
groups_infos.groups_query_args,
urllib.quote(destination),
)
) )
base_url = ( base_url = (
base_url_noweeks + "&amp;nbweeks=%s" % nbweeks base_url_noweeks + "&amp;nbweeks=%s" % nbweeks
@ -1131,6 +1139,7 @@ class ZAbsences(
if moduleimpl_id: if moduleimpl_id:
url_link_semaines += "&amp;moduleimpl_id=" + moduleimpl_id url_link_semaines += "&amp;moduleimpl_id=" + moduleimpl_id
# #
colnames = [str(x) for x in dates]
dates = [x.ISO() for x in dates] dates = [x.ISO() for x in dates]
dayname = self.day_names()[jourdebut.weekday] dayname = self.day_names()[jourdebut.weekday]
@ -1138,9 +1147,9 @@ class ZAbsences(
gr_tit = "en" gr_tit = "en"
else: else:
if len(groups_infos.group_ids) > 1: if len(groups_infos.group_ids) > 1:
p = "des groupes " p = "des groupes"
else: else:
p = "du groupe " p = "du groupe"
gr_tit = ( gr_tit = (
p + '<span class="fontred">' + groups_infos.groups_titles + "</span>" p + '<span class="fontred">' + groups_infos.groups_titles + "</span>"
) )
@ -1208,18 +1217,15 @@ class ZAbsences(
% {"menu_module": menu_module, "url": base_url, "sel": sel} % {"menu_module": menu_module, "url": base_url, "sel": sel}
) )
H += self._gen_form_saisie_groupe(etuds, dates, destination, moduleimpl_id) H += self._gen_form_saisie_groupe(
etuds, colnames, dates, destination, dayname, moduleimpl_id
)
H.append(self.sco_footer(REQUEST)) H.append(self.sco_footer(REQUEST))
return "\n".join(H) return "\n".join(H)
def _gen_form_saisie_groupe(self, etuds, dates, destination="", moduleimpl_id=None): def _gen_form_saisie_groupe(
"""Formulaire saisie absences self, etuds, colnames, dates, destination="", dayname="", moduleimpl_id=None
):
Args:
etuds: liste des étudiants
dates: liste de dates iso, par exemple: [ '2020-12-24', ... ]
moduleimpl_id: optionnel, module concerné.
"""
H = [ H = [
""" """
<script type="text/javascript"> <script type="text/javascript">
@ -1246,29 +1252,25 @@ class ZAbsences(
""" """
% len(etuds) % len(etuds)
] ]
# Dates
odates = [datetime.date(*[int(x) for x in d.split("-")]) for d in dates]
# Titres colonnes # Titres colonnes
noms_jours = [] # eg [ "Lundi", "mardi", "Samedi", ... ] if dayname:
jn = self.day_names() for jour in colnames:
for d in odates: H.append(
idx_jour = d.weekday() '<th colspan="2" width="100px" style="padding-left: 5px; padding-right: 5px;">'
noms_jours.append(jn[idx_jour]) + dayname
for jour in noms_jours: + "</th>"
)
H.append("</tr><tr><td>&nbsp;</td>")
for jour in colnames:
H.append( H.append(
'<th colspan="2" width="100px" style="padding-left: 5px; padding-right: 5px;">' '<th colspan="2" width="100px" style="padding-left: 5px; padding-right: 5px;">'
+ jour + jour
+ "</th>" + "</th>"
) )
H.append("</tr><tr><td>&nbsp;</td>") H.append("</tr><tr><td>&nbsp;</td>")
for d in odates: H.append("<th>AM</th><th>PM</th>" * len(colnames))
H.append(
'<th colspan="2" width="100px" style="padding-left: 5px; padding-right: 5px;">'
+ d.strftime("%d/%m/%Y")
+ "</th>"
)
H.append("</tr><tr><td>&nbsp;</td>")
H.append("<th>AM</th><th>PM</th>" * len(dates))
H.append("</tr>") H.append("</tr>")
# #
if not etuds: if not etuds:
@ -1371,13 +1373,14 @@ class ZAbsences(
absjust_only=0, absjust_only=0,
REQUEST=None, REQUEST=None,
): ):
"""Tables des absences justifiees et non justifiees d'un étudiant sur l'année en cours""" """Tables des absences justifiees et non justifiees d'un étudiant sur l'année en cours
"""
absjust = self.ListeAbsJust(etudid=etudid, datedebut=datedebut) absjust = self.ListeAbsJust(etudid=etudid, datedebut=datedebut)
absnonjust = self.ListeAbsNonJust(etudid=etudid, datedebut=datedebut) absnonjust = self.ListeAbsNonJust(etudid=etudid, datedebut=datedebut)
# examens ces jours là ? # examens ces jours là ?
if with_evals: if with_evals:
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=notesdb.ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
for a in absnonjust + absjust: for a in absnonjust + absjust:
cursor.execute( cursor.execute(
"""select eval.* """select eval.*
@ -1496,9 +1499,10 @@ class ZAbsences(
format="html", format="html",
REQUEST=None, REQUEST=None,
): ):
"""Liste les absences de groupes""" """Liste les absences de groupes
datedebut = notesdb.DateDMYtoISO(debut) """
datefin = notesdb.DateDMYtoISO(fin) datedebut = DateDMYtoISO(debut)
datefin = DateDMYtoISO(fin)
# Informations sur les groupes à afficher: # Informations sur les groupes à afficher:
groups_infos = sco_groups_view.DisplayedGroupsInfos( groups_infos = sco_groups_view.DisplayedGroupsInfos(
self, group_ids, REQUEST=REQUEST self, group_ids, REQUEST=REQUEST
@ -1599,7 +1603,7 @@ class ZAbsences(
base_url="%s&amp;formsemestre_id=%s&amp;debut=%s&amp;fin=%s" base_url="%s&amp;formsemestre_id=%s&amp;debut=%s&amp;fin=%s"
% (groups_infos.base_url, formsemestre_id, debut, fin), % (groups_infos.base_url, formsemestre_id, debut, fin),
filename="etat_abs_" filename="etat_abs_"
+ scu.make_filename( + make_filename(
"%s de %s" % (groups_infos.groups_filename, sem["titreannee"]) "%s de %s" % (groups_infos.groups_filename, sem["titreannee"])
), ),
caption=title, caption=title,
@ -1628,20 +1632,26 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
def EtatAbsencesDate( def EtatAbsencesDate(
self, group_ids=[], date=None, REQUEST=None # list of groups to display self, group_ids=[], date=None, REQUEST=None # list of groups to display
): ):
"""Etat des absences pour un groupe à une date donnée""" """Etat des absences pour un groupe à une date donnée
"""
# Informations sur les groupes à afficher: # Informations sur les groupes à afficher:
groups_infos = sco_groups_view.DisplayedGroupsInfos( groups_infos = sco_groups_view.DisplayedGroupsInfos(
self, group_ids, REQUEST=REQUEST self, group_ids, REQUEST=REQUEST
) )
formsemestre_id = groups_infos.formsemestre_id
sem = sco_formsemestre.do_formsemestre_list(
self, {"formsemestre_id": formsemestre_id}
)[0]
H = [self.sco_header(page_title="Etat des absences", REQUEST=REQUEST)] H = [self.sco_header(page_title="Etat des absences", REQUEST=REQUEST)]
if date: if date:
dateiso = notesdb.DateDMYtoISO(date) dateiso = DateDMYtoISO(date)
nbetud = 0 nbetud = 0
t_nbabsjustam = 0 t_nbabsjustam = 0
t_nbabsam = 0 t_nbabsam = 0
t_nbabsjustpm = 0 t_nbabsjustpm = 0
t_nbabspm = 0 t_nbabspm = 0
H.append("<h2>État des absences le %s</h2>" % date) etuds = self.getEtudInfoGroupes(groups_infos.group_ids)
H.append("<h2>Etat des absences le %s</h2>" % date)
H.append( H.append(
"""<table border="0" cellspacing="4" cellpadding="0"> """<table border="0" cellspacing="4" cellpadding="0">
<tr><th>&nbsp;</th> <tr><th>&nbsp;</th>
@ -1728,7 +1738,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
etudid=etudid, code_nip=code_nip, REQUEST=REQUEST, filled=True etudid=etudid, code_nip=code_nip, REQUEST=REQUEST, filled=True
) )
if not etuds: if not etuds:
return scu.log_unknown_etud(self, REQUEST=REQUEST) return log_unknown_etud(self, REQUEST=REQUEST)
etud = etuds[0] etud = etuds[0]
# check dates # check dates
begin_date = ParseDateTimeUTC(begin) # may raises ValueError begin_date = ParseDateTimeUTC(begin) # may raises ValueError
@ -1753,7 +1763,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
if xml_reply: if xml_reply:
# Renvoie le nouveau billet en XML # Renvoie le nouveau billet en XML
if REQUEST: if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE) REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
billets = billet_absence_list(cnx, {"billet_id": billet_id}) billets = billet_absence_list(cnx, {"billet_id": billet_id})
tab = self._tableBillets(billets, etud=etud) tab = self._tableBillets(billets, etud=etud)
@ -1874,10 +1884,11 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
security.declareProtected(ScoView, "listeBilletsEtud") security.declareProtected(ScoView, "listeBilletsEtud")
def listeBilletsEtud(self, etudid=False, REQUEST=None, format="html"): def listeBilletsEtud(self, etudid=False, REQUEST=None, format="html"):
"""Liste billets pour un etudiant""" """Liste billets pour un etudiant
"""
etuds = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST) etuds = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)
if not etuds: if not etuds:
return scu.log_unknown_etud(self, format=format, REQUEST=REQUEST) return log_unknown_etud(self, format=format, REQUEST=REQUEST)
etud = etuds[0] etud = etuds[0]
cnx = self.GetDBConnexion() cnx = self.GetDBConnexion()
@ -1888,7 +1899,8 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
security.declareProtected(ScoView, "XMLgetBilletsEtud") security.declareProtected(ScoView, "XMLgetBilletsEtud")
def XMLgetBilletsEtud(self, etudid=False, REQUEST=None): def XMLgetBilletsEtud(self, etudid=False, REQUEST=None):
"""Liste billets pour un etudiant""" """Liste billets pour un etudiant
"""
if not self.get_preference("handle_billets_abs"): if not self.get_preference("handle_billets_abs"):
return "" return ""
t0 = time.time() t0 = time.time()
@ -1925,7 +1937,8 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
security.declareProtected(ScoAbsChange, "deleteBilletAbsence") security.declareProtected(ScoAbsChange, "deleteBilletAbsence")
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 = billet_absence_list(cnx, {"billet_id": billet_id})
if not billets: if not billets:
@ -2094,8 +2107,8 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
Abs = self._ListeAbsDate(etud["etudid"], beg_date, end_date) Abs = self._ListeAbsDate(etud["etudid"], beg_date, end_date)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE) REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
doc = jaxml.XML_document(encoding=scu.SCO_ENCODING) doc = jaxml.XML_document(encoding=SCO_ENCODING)
doc.absences(etudid=etud["etudid"], beg_date=beg_date, end_date=end_date) doc.absences(etudid=etud["etudid"], beg_date=beg_date, end_date=end_date)
doc._push() doc._push()
for a in Abs: for a in Abs:
@ -2113,7 +2126,7 @@ ou entrez une date pour visualiser les absents un jour donné&nbsp;:
return repr(doc) return repr(doc)
_billet_absenceEditor = notesdb.EditableTable( _billet_absenceEditor = EditableTable(
"billet_absence", "billet_absence",
"billet_id", "billet_id",
( (
@ -2197,6 +2210,7 @@ def MonthTableTail():
def MonthTableBody( def MonthTableBody(
month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8 month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8
): ):
# log('XXX events=%s' % events)
firstday, nbdays = calendar.monthrange(year, month) firstday, nbdays = calendar.monthrange(year, month)
localtime = time.localtime() localtime = time.localtime()
current_weeknum = time.strftime("%U", localtime) current_weeknum = time.strftime("%U", localtime)
@ -2429,8 +2443,8 @@ class CAbsSemEtud:
self.sem = sco_formsemestre.get_formsemestre( self.sem = sco_formsemestre.get_formsemestre(
self.context, self.sem["formsemestre_id"] self.context, self.sem["formsemestre_id"]
) )
debut_sem = notesdb.DateDMYtoISO(self.sem["date_debut"]) debut_sem = DateDMYtoISO(self.sem["date_debut"])
fin_sem = notesdb.DateDMYtoISO(self.sem["date_fin"]) fin_sem = DateDMYtoISO(self.sem["date_fin"])
self._CountAbs = self.context.Absences.CountAbs( self._CountAbs = self.context.Absences.CountAbs(
etudid=self.etudid, debut=debut_sem, fin=fin_sem etudid=self.etudid, debut=debut_sem, fin=fin_sem

View File

@ -1617,7 +1617,7 @@ class ZNotes(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Impl
security.declareProtected(ScoView, "view_module_abs") security.declareProtected(ScoView, "view_module_abs")
def view_module_abs(self, REQUEST, moduleimpl_id, format="html"): def view_module_abs(self, REQUEST, moduleimpl_id, format="html"):
"""Visualisation des absences a un module""" """Visulalisation des absences a un module"""
M = self.do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0] M = self.do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
sem = sco_formsemestre.get_formsemestre(self, M["formsemestre_id"]) sem = sco_formsemestre.get_formsemestre(self, M["formsemestre_id"])
debut_sem = DateDMYtoISO(sem["date_debut"]) debut_sem = DateDMYtoISO(sem["date_debut"])

View File

@ -33,6 +33,7 @@
import time, string, glob, re, inspect import time, string, glob, re, inspect
import urllib, urllib2, cgi, xml import urllib, urllib2, cgi, xml
import datetime
try: try:
from cStringIO import StringIO from cStringIO import StringIO
@ -42,13 +43,22 @@ from zipfile import ZipFile
import os.path, glob import os.path, glob
import traceback import traceback
from email.MIMEMultipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.MIMEText import MIMEText from email.mime.text import MIMEText
from email.MIMEBase import MIMEBase from email.header import Header
from email.Header import Header from email.mime.base import MIMEBase
from email import Encoders
from sco_zope import * from sco_zope import (
ObjectManager,
PropertyManager,
RoleManager,
Item,
Persistent,
Implicit,
ClassSecurityInfo,
DTMLFile,
Globals,
)
# #
try: try:
@ -56,7 +66,21 @@ try:
except: except:
import ZPsycopgDA.DA as ZopeDA # interp.py import ZPsycopgDA.DA as ZopeDA # interp.py
from sco_utils import * import sco_utils
from sco_utils import (
SCO_DEFAULT_SQL_USERS_CNX,
SCO_ENCODING,
CUSTOM_HTML_HEADER_CNX,
SCO_USERS_LIST,
SCO_WEBSITE,
SCO_EXC_MAIL,
SCO_DEV_MAIL,
scodoc_html2txt,
VERSION,
SCODOC_CFG_DIR,
)
from sco_permissions import ScoView, ScoSuperAdmin
from sco_exceptions import AccessDenied
from notes_log import log from notes_log import log
import sco_find_etud import sco_find_etud
from ZScoUsers import pwdFascistCheck from ZScoUsers import pwdFascistCheck
@ -111,7 +135,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
def _check_users_folder(self, REQUEST=None): def _check_users_folder(self, REQUEST=None):
"""Vérifie UserFolder et le crée s'il le faut""" """Vérifie UserFolder et le crée s'il le faut"""
try: try:
udb = self.UsersDB self.UsersDB
return "<!-- uf ok -->" return "<!-- uf ok -->"
except: except:
e = self._check_admin_perm(REQUEST) e = self._check_admin_perm(REQUEST)
@ -164,11 +188,11 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
pass pass
# add missing getAuthFailedMessage (bug in exUserFolder ?) # add missing getAuthFailedMessage (bug in exUserFolder ?)
try: try:
x = self.getAuthFailedMessage self.getAuthFailedMessage
except: except:
log("adding getAuthFailedMessage to Zope install") log("adding getAuthFailedMessage to Zope install")
parent = self.aq_parent parent = self.aq_parent
from OFS.DTMLMethod import addDTMLMethod from OFS.DTMLMethod import addDTMLMethod # pylint: disable=import-error
addDTMLMethod(parent, "getAuthFailedMessage", file="Identification") addDTMLMethod(parent, "getAuthFailedMessage", file="Identification")
@ -264,7 +288,7 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
r = [] r = []
for folder in folders: for folder in folders:
try: try:
s = folder.Scolarite _ = folder.Scolarite
r.append(folder) r.append(folder)
except: except:
pass pass
@ -284,7 +308,6 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
raise ValueError("nom de departement invalide") raise ValueError("nom de departement invalide")
if not pass2: if not pass2:
# 1- Creation de repertoire Dept # 1- Creation de repertoire Dept
log("creating Zope folder " + DeptId)
add_method = self.manage_addProduct["OFSP"].manage_addFolder add_method = self.manage_addProduct["OFSP"].manage_addFolder
add_method(DeptId, title="Site dept. " + DeptId) add_method(DeptId, title="Site dept. " + DeptId)
@ -292,12 +315,10 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp
if not pass2: if not pass2:
# 2- Creation du repertoire Fotos # 2- Creation du repertoire Fotos
log("creating Zope folder %s/Fotos" % DeptId)
add_method = DeptFolder.manage_addProduct["OFSP"].manage_addFolder add_method = DeptFolder.manage_addProduct["OFSP"].manage_addFolder
add_method("Fotos", title="Photos identites " + DeptId) add_method("Fotos", title="Photos identites " + DeptId)
# 3- Creation instance ScoDoc # 3- Creation instance ScoDoc
log("creating Zope ZScolar instance")
add_method = DeptFolder.manage_addProduct["ScoDoc"].manage_addZScolarForm add_method = DeptFolder.manage_addProduct["ScoDoc"].manage_addZScolarForm
return add_method(DeptId, REQUEST=REQUEST) return add_method(DeptId, REQUEST=REQUEST)
@ -649,8 +670,8 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
**kv **kv
): ):
"Recuperation des exceptions Zope" "Recuperation des exceptions Zope"
sco_exc_mail = SCO_EXC_MAIL sco_exc_mail = SCO_EXC_MAIL # pylint: disable=unused-variable
sco_dev_mail = SCO_DEV_MAIL sco_dev_mail = SCO_DEV_MAIL # pylint: disable=unused-variable
# neat (or should I say dirty ?) hack to get REQUEST # neat (or should I say dirty ?) hack to get REQUEST
# in fact, our caller (probably SimpleItem.py) has the REQUEST variable # in fact, our caller (probably SimpleItem.py) has the REQUEST variable
# that we'd like to use for our logs, but does not pass it as an argument. # that we'd like to use for our logs, but does not pass it as an argument.
@ -717,6 +738,7 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
) )
# display error traceback (? may open a security risk via xss attack ?) # display error traceback (? may open a security risk via xss attack ?)
# log('exc B') # log('exc B')
# pylint: disable=unused-variable
txt_html = self._report_request(REQUEST, fmt="html") txt_html = self._report_request(REQUEST, fmt="html")
H.append( H.append(
"""<h4 class="scodoc">Zope Traceback (à envoyer par mail à <a href="mailto:%(sco_dev_mail)s">%(sco_dev_mail)s</a>)</h4><div style="background-color: rgb(153,153,204); border: 1px;"> """<h4 class="scodoc">Zope Traceback (à envoyer par mail à <a href="mailto:%(sco_dev_mail)s">%(sco_dev_mail)s</a>)</h4><div style="background-color: rgb(153,153,204); border: 1px;">
@ -737,6 +759,7 @@ Problème de connexion (identifiant, mot de passe): <em>contacter votre responsa
pass pass
# --- Mail: # --- Mail:
# pylint: disable=unused-variable
error_traceback_txt = scodoc_html2txt(error_tb) error_traceback_txt = scodoc_html2txt(error_tb)
txt = ( txt = (
""" """
@ -755,6 +778,7 @@ ErrorType: %(error_type)s
def _report_request(self, REQUEST, fmt="txt"): def _report_request(self, REQUEST, fmt="txt"):
"""string describing current request for bug reports""" """string describing current request for bug reports"""
# pylint: disable=unused-variable
AUTHENTICATED_USER = REQUEST.get("AUTHENTICATED_USER", "") AUTHENTICATED_USER = REQUEST.get("AUTHENTICATED_USER", "")
dt = time.asctime() dt = time.asctime()
URL = REQUEST.get("URL", "") URL = REQUEST.get("URL", "")
@ -771,8 +795,7 @@ ErrorType: %(error_type)s
HTTP_USER_AGENT = "na" HTTP_USER_AGENT = "na"
form = REQUEST.get("form", "") form = REQUEST.get("form", "")
HTTP_X_FORWARDED_FOR = REQUEST.get("HTTP_X_FORWARDED_FOR", "") HTTP_X_FORWARDED_FOR = REQUEST.get("HTTP_X_FORWARDED_FOR", "")
svn_version = get_svn_version(self.file_path) SCOVERSION = sco_utils.get_scodoc_version() or VERSION.SCOVERSION
SCOVERSION = VERSION.SCOVERSION
txt = ( txt = (
""" """
@ -786,8 +809,6 @@ REFERER: %(REFERER)s
Form: %(form)s Form: %(form)s
Origin: %(HTTP_X_FORWARDED_FOR)s Origin: %(HTTP_X_FORWARDED_FOR)s
Agent: %(HTTP_USER_AGENT)s Agent: %(HTTP_USER_AGENT)s
subversion: %(svn_version)s
""" """
% vars() % vars()
) )
@ -890,7 +911,7 @@ subversion: %(svn_version)s
"""Liste de id de departements definis par create_dept.sh """Liste de id de departements definis par create_dept.sh
(fichiers depts/*.cfg) (fichiers depts/*.cfg)
""" """
filenames = glob.glob(SCODOC_VAR_DIR + "/config/depts/*.cfg") filenames = glob.glob(SCODOC_CFG_DIR + "/config/depts/*.cfg")
ids = [os.path.split(os.path.splitext(f)[0])[1] for f in filenames] ids = [os.path.split(os.path.splitext(f)[0])[1] for f in filenames]
return ids return ids

View File

@ -52,7 +52,7 @@ log("restarting...")
log("ZScolar home=%s" % file_path) log("ZScolar home=%s" % file_path)
import sco_utils
from sco_utils import * from sco_utils import *
import notesdb import notesdb
from notesdb import * from notesdb import *
@ -405,9 +405,9 @@ UE11 Découverte métiers <span class="ue_code">(code UCOD46, 16 ECTS, Apo <span
H = [ H = [
"""<h2>Système de gestion scolarité</h2> """<h2>Système de gestion scolarité</h2>
<p>&copy; Emmanuel Viennet 1997-2020</p> <p>&copy; Emmanuel Viennet 1997-2020</p>
<p>Version %s (subversion %s)</p> <p>Version %s</p>
""" """
% (SCOVERSION, get_svn_version(file_path)) % (sco_utils.get_scodoc_version())
] ]
H.append( H.append(
'<p>Logiciel libre écrit en <a href="http://www.python.org">Python</a>.</p><p>Utilise <a href="http://www.reportlab.org/">ReportLab</a> pour générer les documents PDF, et <a href="http://sourceforge.net/projects/pyexcelerator">pyExcelerator</a> pour le traitement des documents Excel.</p>' '<p>Logiciel libre écrit en <a href="http://www.python.org">Python</a>.</p><p>Utilise <a href="http://www.reportlab.org/">ReportLab</a> pour générer les documents PDF, et <a href="http://sourceforge.net/projects/pyexcelerator">pyExcelerator</a> pour le traitement des documents Excel.</p>'
@ -2037,8 +2037,49 @@ function tweakmenu( gname ) {
) )
if not edit: if not edit:
etud = scolars.create_etud(self, cnx, args=tf[2], REQUEST=REQUEST) # creation d'un etudiant
etudid = etud["etudid"] etudid = scolars.etudident_create(
cnx, tf[2], context=self, REQUEST=REQUEST
)
# crée une adresse vide (chaque etudiant doit etre dans la table "adresse" !)
adresse_id = scolars.adresse_create(
cnx,
{
"etudid": etudid,
"typeadresse": "domicile",
"description": "(creation individuelle)",
},
)
# event
scolars.scolar_events_create(
cnx,
args={
"etudid": etudid,
"event_date": time.strftime("%d/%m/%Y"),
"formsemestre_id": None,
"event_type": "CREATION",
},
)
# log
logdb(
REQUEST,
cnx,
method="etudident_edit_form",
etudid=etudid,
msg="creation initiale",
)
etud = scolars.etudident_list(cnx, {"etudid": etudid})[0]
self.fillEtudsInfo([etud])
etud["url"] = "ficheEtud?etudid=%(etudid)s" % etud
sco_news.add(
self,
REQUEST,
typ=NEWS_INSCR,
object=None, # pas d'object pour ne montrer qu'un etudiant
text='Nouvel étudiant <a href="%(url)s">%(nomprenom)s</a>' % etud,
url=etud["url"],
)
else: else:
# modif d'un etudiant # modif d'un etudiant
scolars.etudident_edit(cnx, tf[2], context=self, REQUEST=REQUEST) scolars.etudident_edit(cnx, tf[2], context=self, REQUEST=REQUEST)

View File

@ -1,4 +1,4 @@
#!/bin/bash
# Version majeure de Debian (..., 9, 10) # Version majeure de Debian (..., 9, 10)
debian_version=$(cat /etc/debian_version) debian_version=$(cat /etc/debian_version)
debian_version=${debian_version%%.*} debian_version=${debian_version%%.*}
@ -24,28 +24,28 @@ export POSTGRES_SUPERUSER=postgres
export POSTGRES_USER=www-data export POSTGRES_USER=www-data
# psql command: if various versions installed, force the one we want: # psql command: if various versions installed, force the one we want:
if [ ${debian_version} = "10" ] if [ "${debian_version}" = "10" ]
then then
PSQL=/usr/lib/postgresql/11/bin/psql PSQL=/usr/lib/postgresql/11/bin/psql
elif [ ${debian_version} = "9" ] elif [ "${debian_version}" = "9" ]
then then
PSQL=/usr/lib/postgresql/9.6/bin/psql PSQL=/usr/lib/postgresql/9.6/bin/psql
elif [ ${debian_version} = "8" ] elif [ "${debian_version}" = "8" ]
then then
PSQL=/usr/lib/postgresql/9.4/bin/psql PSQL=/usr/lib/postgresql/9.4/bin/psql
elif [ ${debian_version} = "7" ] elif [ "${debian_version}" = "7" ]
then then
PSQL=/usr/lib/postgresql/9.1/bin/psql PSQL=/usr/lib/postgresql/9.1/bin/psql
elif [ ${debian_version} = "5" ] elif [ "${debian_version}" = "5" ]
then then
PSQL=/usr/lib/postgresql/8.3/bin/psql PSQL=/usr/lib/postgresql/8.3/bin/psql
elif [ ${debian_version} = "6" ] elif [ "${debian_version}" = "6" ]
then then
PSQL=/usr/lib/postgresql/8.4/bin/psql PSQL=/usr/lib/postgresql/8.4/bin/psql
else else
PSQL=/usr/lib/postgresql/8.1/bin/psql PSQL=/usr/lib/postgresql/8.1/bin/psql
fi fi
export PSQL
# tcp port for SQL server (under Debian 4, 5432 or 5433 for 8.1 if 7.4 also installed !) # tcp port for SQL server (under Debian 4, 5432 or 5433 for 8.1 if 7.4 also installed !)
# Important note: if changed, you should probably also change it in # Important note: if changed, you should probably also change it in
@ -53,7 +53,7 @@ fi
export POSTGRES_PORT=5432 export POSTGRES_PORT=5432
# Utilise par le script de reset du mot de passe: # Utilise par le script de reset du mot de passe:
if [ ${debian_version} -ge "7" ] if [ "${debian_version}" -ge "7" ]
then then
export ZOPE_VERSION=2.13 export ZOPE_VERSION=2.13
else else

View File

@ -5,10 +5,10 @@
source config.sh source config.sh
source utils.sh source utils.sh
check_uid_root $0 check_uid_root "$0"
echo 'Installation du demarrage automatique de ScoDoc (systemd)' echo 'Installation du demarrage automatique de ScoDoc (systemd)'
cp $SCODOC_DIR/config/etc/scodoc.service /etc/systemd/system cp "$SCODOC_DIR"/config/etc/scodoc.service /etc/systemd/system
systemctl enable scodoc.service systemctl enable scodoc.service
echo "A partir de maintenant, utiliser" echo "A partir de maintenant, utiliser"

View File

@ -11,6 +11,6 @@ source utils.sh
echo 'Creating postgresql database' echo 'Creating postgresql database'
# --- # ---
echo 'Creating postgresql database ' $db_name echo 'Creating postgresql database ' "$db_name"
createdb -E UTF-8 -p $POSTGRES_PORT -O $POSTGRES_USER $db_name createdb -E UTF-8 -p "$POSTGRES_PORT" -O "$POSTGRES_USER" "$db_name"

View File

@ -15,37 +15,19 @@ source utils.sh
check_uid_root "$0" check_uid_root "$0"
usage() {
echo "$0 [-n DEPT]"
echo "(default to interactive mode)"
exit 1
}
[ $# = 0 ] || [ $# = 2 ] || usage
if [ "$1" = "-n" ] echo -n "Nom du departement (un mot sans ponctuation, exemple \"Info\"): "
then read -r DEPT
interactive=0
if [ $# -lt 2 ]
then
usage
fi
DEPT=$2
else
interactive=1
echo -n "Nom du departement (un mot sans ponctuation, exemple \"Info\"): "
read -r DEPT
fi
if [[ ! "$DEPT" =~ ^[A-Za-z0-9]+$ ]] if [[ ! "$DEPT" =~ ^[A-Za-z0-9]+$ ]]
then then
echo 'Nom de departement invalide !' echo 'Nom de departement invalide !'
exit 2 exit 1
fi fi
export DEPT export DEPT
db_name=SCO$(to_upper "$DEPT") export db_name=SCO$(to_upper "$DEPT")
export db_name
cfg_pathname="${SCODOC_VAR_DIR}/config/depts/$DEPT".cfg cfg_pathname="${SCODOC_VAR_DIR}/config/depts/$DEPT".cfg
@ -59,39 +41,29 @@ fi
init_postgres_user init_postgres_user
# ----------------------- Create database # ----------------------- Create database
su -c ./create_database.sh "$POSTGRES_SUPERUSER" su -c ./create_database.sh $POSTGRES_SUPERUSER
# ----------------------- Create tables # ----------------------- Create tables
# POSTGRES_USER == regular unix user (www-data) # POSTGRES_USER == regular unix user (www-data)
if [ "$interactive" = 1 ] su -c ./initialize_database.sh $POSTGRES_USER
then
su -c ./initialize_database.sh "$POSTGRES_USER"
else
su -c ./initialize_database.sh "$POSTGRES_USER" > /dev/null 2>&1
fi
# ----------------------- Enregistre fichier config # ----------------------- Enregistre fichier config
echo "dbname=${db_name}" > "$cfg_pathname" echo "dbname=$db_name" > "$cfg_pathname"
# ----------------------- Force mise à jour
if [ "$interactive" = 1 ] echo -n "Voulez vous mettre a jour ScoDoc (tres recommande) ? (y/n) [y] "
read -r ans
if [ "$(norm_ans "$ans")" != 'N' ]
then then
# ----------------------- Force mise à jour (cd "$SCODOC_DIR/config"; ./upgrade.sh)
echo -n "Voulez vous mettre a jour ScoDoc (tres recommande) ? (y/n) [y] "
read -r ans
if [ "$(norm_ans "$ans")" != 'N' ]
then
(cd "$SCODOC_DIR/config" || terminate "no config directory"; ./upgrade.sh)
fi
# -----------------------
echo
echo " Departement $DEPT cree"
echo
echo " Attention: la base de donnees n'a pas de copies de sauvegarde"
echo
echo " Maintenant, vous pouvez ajouter le departement via l'application web"
echo " en suivant le lien \"Administration de ScoDoc\" sur la page d'accueil."
echo
fi fi
# -----------------------
echo
echo " Departement $DEPT cree"
echo
echo " Attention: la base de donnees n'a pas de copies de sauvegarde"
echo
echo " Maintenant, vous pouvez ajouter le departement via l'application web"
echo " en suivant le lien \"Administration de ScoDoc\" sur la page d'accueil."
echo

View File

@ -12,7 +12,7 @@
source config.sh source config.sh
source utils.sh source utils.sh
check_uid_root $0 check_uid_root "$0"
# --- Ensure postgres user www-data exists # --- Ensure postgres user www-data exists
init_postgres_user init_postgres_user
@ -21,8 +21,8 @@ db_name=SCOUSERS
echo 'Creating postgresql database ' $db_name echo 'Creating postgresql database ' $db_name
su -c "createdb -E UTF-8 -O $POSTGRES_USER -p $POSTGRES_PORT $db_name" $POSTGRES_SUPERUSER su -c "createdb -E UTF-8 -O $POSTGRES_USER -p $POSTGRES_PORT $db_name" "$POSTGRES_SUPERUSER"
echo 'Initializing tables in database ' $db_name echo 'Initializing tables in database ' "$db_name"
echo su -c "$PSQL -U $POSTGRES_USER -p $POSTGRES_PORT $db_name < $SCODOC_DIR/misc/create_user_table.sql" $POSTGRES_USER echo su -c "$PSQL -U $POSTGRES_USER -p $POSTGRES_PORT $db_name < $SCODOC_DIR/misc/create_user_table.sql" "$POSTGRES_USER"
su -c "$PSQL -U $POSTGRES_USER -p $POSTGRES_PORT $db_name < $SCODOC_DIR/misc/create_user_table.sql" $POSTGRES_USER su -c "$PSQL -U $POSTGRES_USER -p $POSTGRES_PORT $db_name < $SCODOC_DIR/misc/create_user_table.sql" "$POSTGRES_USER"

View File

@ -17,38 +17,23 @@
source config.sh source config.sh
source utils.sh source utils.sh
check_uid_root $0 check_uid_root "$0"
usage() {
echo "$0 [-n DEPT]" echo
echo "(default to interactive mode)" echo "Ce script supprime la base de donnees ScoDoc d'un departement"
exit 1 echo
} echo "Attention: le departement doit au prealable avoir ete supprime via l'interface web !"
[ $# = 0 ] || [ $# = 2 ] || usage echo "faites le AVANT d'executer ce script !!!"
if [ "$1" = "-n" ] echo
then echo -n "Nom du departement a supprimer (un mot sans ponctuation, exemple \"Info\"): "
interactive=0 read -r DEPT
if [ $# -lt 2 ]
then
usage
fi
DEPT=$2
else
interactive=1
echo
echo "Ce script supprime la base de donnees ScoDoc d'un departement"
echo
echo "Attention: le departement doit au prealable avoir ete supprime via l'interface web !"
echo "faites le AVANT d'executer ce script !!!"
echo
echo -n "Nom du departement a supprimer (un mot sans ponctuation, exemple \"Info\"): "
read -r DEPT
fi
if [[ ! "$DEPT" =~ ^[A-Za-z0-9]+$ ]] if [[ ! "$DEPT" =~ ^[A-Za-z0-9]+$ ]]
then then
echo "Nom de departement invalide !" echo "Nom de departement invalide !"
exit 1 exit 1
fi fi
export DEPT export DEPT
cfg_pathname="${SCODOC_VAR_DIR}/config/depts/$DEPT".cfg cfg_pathname="${SCODOC_VAR_DIR}/config/depts/$DEPT".cfg
@ -59,20 +44,17 @@ then
scodocctl stop scodocctl stop
# suppression de la base postgres # suppression de la base postgres
db_name=$(cat "$cfg_pathname" | sed '/^dbname=*/!d; s///;q') db_name=$(sed '/^dbname=*/!d; s///;q' < "$cfg_pathname")
echo "suppression de la base postgres $db_name" echo "suppression de la base postgres $db_name"
su -c "dropdb $db_name" "$POSTGRES_SUPERUSER" || terminate "ne peux supprimer base de donnees $db_name" su -c "dropdb $db_name" "$POSTGRES_SUPERUSER" || terminate "ne peux supprimer base de donnees $db_name"
# suppression du fichier de config # suppression du fichier de config
/bin/rm -f "$cfg_pathname" || terminate "ne peux supprimer $cfg_pathname" /bin/rm -f "$cfg_pathname" || terminate "ne peux supprimer $cfg_pathname"
# relance ScoDoc # relance ScoDoc
if [ "$interactive" = 1 ] echo -n "Demarrer le serveur ScoDoc ? (y/n) [n]"
read -r ans
if [ "$(norm_ans "$ans")" = 'Y' ]
then then
echo -n "Demarrer le serveur ScoDoc ? (y/n) [n]" scodocctl start
read -r ans
if [ "$(norm_ans "$ans")" = 'Y' ]
then
scodocctl start
fi
fi fi
exit 0 exit 0
else else

View File

@ -6,7 +6,7 @@
# Avec option: # Avec option:
# -a : sauve aussi les bases de données # -a : sauve aussi les bases de données
# #
DEST_ADDRESS=emmanuel.viennet@univ-paris13.fr DEST_ADDRESS=emmanuel.viennet@gmail.com
INSTANCE_DIR=/opt/scodoc INSTANCE_DIR=/opt/scodoc
@ -36,7 +36,7 @@ while getopts ":d:aunh" opt; do
SEND_BY_MAIL=0 SEND_BY_MAIL=0
;; ;;
d) d)
DEPTS_TO_SAVE=$( join_by ' ' $DEPTS_TO_SAVE $OPTARG ) DEPTS_TO_SAVE=$( join_by ' ' "$DEPTS_TO_SAVE" "$OPTARG" )
;; ;;
h) h)
echo "Diagnostic installation ScoDoc" echo "Diagnostic installation ScoDoc"
@ -71,7 +71,7 @@ then
apt-get install sharutils apt-get install sharutils
fi fi
mkdir $TMP mkdir "$TMP"
# Files to copy: # Files to copy:
FILES="/etc/hosts /etc/debian_version /etc/apt /etc/apache2" FILES="/etc/hosts /etc/debian_version /etc/apt /etc/apache2"
@ -91,33 +91,33 @@ echo "left in ${TMP}"
# ------------------------------------- # -------------------------------------
copy_log() { copy_log() {
if [ -e $1 ] if [ -e "$1" ]
then then
cp $1 $TMP/scodoc_logs/ cp "$1" "$TMP"/scodoc_logs/
fi fi
} }
mkdir $TMP/scodoc_logs/ mkdir "$TMP"/scodoc_logs/
copy_log /opt/scodoc/instance/log/event.log copy_log /opt/scodoc/log/event.log
copy_log /opt/scodoc/instance/log/event.log.1 copy_log /opt/scodoc/log/event.log.1
copy_log /opt/scodoc/instance/log/notes.log copy_log /opt/scodoc/log/notes.log
copy_log /opt/scodoc/instance/log/notes.log.1 copy_log /opt/scodoc/log/notes.log.1
# ------------------------------------- # -------------------------------------
# Linux System Configuration # Linux System Configuration
# ------------------------------------- # -------------------------------------
iptables -L > $TMP/iptables.out iptables -L > "$TMP"/iptables.out
ip a > $TMP/ifconfig.out ip a > "$TMP"/ifconfig.out
ps auxww > $TMP/ps.out ps auxww > "$TMP"/ps.out
df -h > $TMP/df.out df -h > "$TMP"/df.out
dpkg -l > $TMP/dpkg.lst dpkg -l > "$TMP"/dpkg.lst
(cd /opt/scodoc/instance/Products/ScoDoc; svn status > $TMP/svn.status) (cd /opt/scodoc/Products/ScoDoc; svn status > "$TMP"/svn.status)
(cd /opt/scodoc/instance/Products/ScoDoc; svn diff > $TMP/svn.diff) (cd /opt/scodoc/Products/ScoDoc; svn diff > "$TMP"/svn.diff)
(cd /opt/scodoc/instance/Products/ScoDoc; svnversion > $TMP/svn.version) (cd /opt/scodoc/Products/ScoDoc; svnversion > "$TMP"/svn.version)
ls -laR /opt/scodoc/instance/Products/ScoDoc > $TMP/ls-laR ls -laR /opt/scodoc/Products/ScoDoc > "$TMP"/ls-laR
# ------------------------------------- # -------------------------------------
@ -126,7 +126,7 @@ ls -laR /opt/scodoc/instance/Products/ScoDoc > $TMP/ls-laR
(su postgres -c "psql -l") > "${TMP}/psql-l.out" (su postgres -c "psql -l") > "${TMP}/psql-l.out"
for dept in "${INSTANCE_DIR}"/var/scodoc/config/depts/*.cfg for dept in "${INSTANCE_DIR}"/var/scodoc/config/depts/*.cfg
do do
cnx=$(cat $dept) cnx=$(cat "$dept")
(su postgres -c "echo '\dt' | psql -d $cnx") > "${TMP}/psql-$(basename ${dept%%.*}).out" (su postgres -c "echo '\dt' | psql -d $cnx") > "${TMP}/psql-$(basename ${dept%%.*}).out"
done done
@ -137,14 +137,14 @@ done
# copy files: # copy files:
for f in $FILES for f in $FILES
do do
cp -R $f $TMP cp -R "$f" "$TMP"
done done
# ------------------------------------- # -------------------------------------
# Optionally save dept(s) database(s) # Optionally save dept(s) database(s)
# ------------------------------------- # -------------------------------------
DEPTS_TO_SAVE=$(echo ${DEPTS_TO_SAVE} | tr ' ' '\n' | sort | uniq) DEPTS_TO_SAVE=$(echo "${DEPTS_TO_SAVE}" | tr ' ' '\n' | sort | uniq)
# Dump database of a dept (eg "RT") # Dump database of a dept (eg "RT")
function dump_dept_db { function dump_dept_db {
@ -176,10 +176,10 @@ fi
# Archive all stuff to /tmp # Archive all stuff to /tmp
# ------------------------------------- # -------------------------------------
tar cfz $TMP.tgz $TMP tar cfz "$TMP".tgz "$TMP"
echo echo
echo "Fichier de diagnostic: $TMP.tgz" echo "Fichier de diagnostic: "$TMP".tgz"
echo echo
# If no mail, stop here # If no mail, stop here
@ -197,9 +197,9 @@ fi
#requires: basename,date,md5sum,sed,sendmail,uuencode #requires: basename,date,md5sum,sed,sendmail,uuencode
function fappend { function fappend {
echo "$2">>$1; echo "$2">>"$1";
} }
YYYYMMDD=`date +%Y%m%d` YYYYMMDD=$(date +%Y%m%d)
# CHANGE THESE # CHANGE THESE
TOEMAIL=$DEST_ADDRESS TOEMAIL=$DEST_ADDRESS
@ -212,45 +212,45 @@ MIMETYPE="application/gnutar" #if not sure, use http://www.webmaster-toolkit.com
# DON'T CHANGE ANYTHING BELOW # DON'T CHANGE ANYTHING BELOW
TMP="/tmp/tmpfil_123"$RANDOM; TMP="/tmp/tmpfil_123"$RANDOM;
BOUNDARY=`date +%s|md5sum` BOUNDARY=$(date +%s|md5sum)
BOUNDARY=${BOUNDARY:0:32} BOUNDARY=${BOUNDARY:0:32}
FILENAME=`basename $ATTACHMENT` FILENAME=$(basename "$ATTACHMENT")
rm -rf $TMP; rm -rf "$TMP"
cat $ATTACHMENT|uuencode --base64 $FILENAME>$TMP; uuencode --base64 "$FILENAME" < "$ATTACHMENT" >"$TMP"
sed -i -e '1,1d' -e '$d' $TMP;#removes first & last lines from $TMP sed -i -e '1,1d' -e '$d' "$TMP"; #removes first & last lines from "$TMP"
DATA=`cat $TMP` DATA=$(cat "$TMP")
rm -rf $TMP; rm -rf "$TMP";
fappend $TMP "From: $FREMAIL"; fappend "$TMP" "From: $FREMAIL";
fappend $TMP "To: $TOEMAIL"; fappend "$TMP" "To: $TOEMAIL";
fappend $TMP "Reply-To: $FREMAIL"; fappend "$TMP" "Reply-To: $FREMAIL";
fappend $TMP "Subject: $SUBJECT"; fappend "$TMP" "Subject: $SUBJECT";
fappend $TMP "Content-Type: multipart/mixed; boundary=\""$BOUNDARY"\""; fappend "$TMP" "Content-Type: multipart/mixed; boundary=\""$BOUNDARY"\"";
fappend $TMP ""; fappend "$TMP" "";
fappend $TMP "This is a MIME formatted message. If you see this text it means that your"; fappend "$TMP" "This is a MIME formatted message. If you see this text it means that your";
fappend $TMP "email software does not support MIME formatted messages."; fappend "$TMP" "email software does not support MIME formatted messages.";
fappend $TMP ""; fappend "$TMP" "";
fappend $TMP "--$BOUNDARY"; fappend "$TMP" "--$BOUNDARY";
fappend $TMP "Content-Type: text/plain; charset=ISO-8859-1; format=flowed"; fappend "$TMP" "Content-Type: text/plain; charset=ISO-8859-1; format=flowed";
fappend $TMP "Content-Transfer-Encoding: 7bit"; fappend "$TMP" "Content-Transfer-Encoding: 7bit";
fappend $TMP "Content-Disposition: inline"; fappend "$TMP" "Content-Disposition: inline";
fappend $TMP ""; fappend "$TMP" "";
fappend $TMP "$MSGBODY"; fappend "$TMP" "$MSGBODY";
fappend $TMP ""; fappend "$TMP" "";
fappend $TMP ""; fappend "$TMP" "";
fappend $TMP "--$BOUNDARY"; fappend "$TMP" "--$BOUNDARY";
fappend $TMP "Content-Type: $MIMETYPE; name=\"$FILENAME\""; fappend "$TMP" "Content-Type: $MIMETYPE; name=\"$FILENAME\"";
fappend $TMP "Content-Transfer-Encoding: base64"; fappend "$TMP" "Content-Transfer-Encoding: base64";
fappend $TMP "Content-Disposition: attachment; filename=\"$FILENAME\";"; fappend "$TMP" "Content-Disposition: attachment; filename=\"$FILENAME\";";
fappend $TMP ""; fappend "$TMP" "";
fappend $TMP "$DATA"; fappend "$TMP" "$DATA";
fappend $TMP ""; fappend "$TMP" "";
fappend $TMP ""; fappend "$TMP" "";
fappend $TMP "--$BOUNDARY--"; fappend "$TMP" "--$BOUNDARY--";
fappend $TMP ""; fappend "$TMP" "";
fappend $TMP ""; fappend "$TMP" "";
#cat $TMP>out.txt #cat "$TMP">out.txt
cat $TMP|sendmail -t -f none@example.com; cat "$TMP"|sendmail -t -f none@example.com;
rm $TMP; rm "$TMP";

View File

@ -15,15 +15,15 @@ then
fi fi
echo "Changing to directory " $SCODOC_DIR/config echo "Changing to directory " "$SCODOC_DIR"/config
cd $SCODOC_DIR/config cd "$SCODOC_DIR"/config || { echo "directory does not exist"; exit 1; }
echo "Stopping ScoDoc..." echo "Stopping ScoDoc..."
scodocctl stop scodocctl stop
# DROITS # DROITS
echo -n "Verification des droits: proprietaire www-data ? (y/n) [y] " echo -n "Verification des droits: proprietaire www-data ? (y/n) [y] "
read ans read -r ans
if [ "$(norm_ans "$ans")" != 'N' ] if [ "$(norm_ans "$ans")" != 'N' ]
then then
echo 'changing owner to www-data' echo 'changing owner to www-data'
@ -31,7 +31,7 @@ then
fi fi
echo -n 'Suppression des backups des sources (*~) ? (y/n) [y] ' echo -n 'Suppression des backups des sources (*~) ? (y/n) [y] '
read ans read -r ans
if [ "$(norm_ans "$ans")" != 'N' ] if [ "$(norm_ans "$ans")" != 'N' ]
then then
/bin/rm -f ../*~ ../*/*~ /bin/rm -f ../*~ ../*/*~
@ -40,7 +40,7 @@ fi
# SVN # SVN
echo -n "svn update ? (y/n) [y] " echo -n "svn update ? (y/n) [y] "
read ans read -r ans
if [ "$(norm_ans "$ans")" != 'N' ] if [ "$(norm_ans "$ans")" != 'N' ]
then then
echo 'Updating from SVN...' echo 'Updating from SVN...'
@ -50,7 +50,7 @@ fi
# DEPARTEMENTS (maintenant inutile car dans /var) # DEPARTEMENTS (maintenant inutile car dans /var)
echo -n "Supprimer les (anciennes) configs de departements ? (y/n) [y] " echo -n "Supprimer les (anciennes) configs de departements ? (y/n) [y] "
read ans read -r ans
if [ "$(norm_ans "$ans")" != 'N' ] if [ "$(norm_ans "$ans")" != 'N' ]
then then
echo "moving " depts/*.cfg "to /tmp" echo "moving " depts/*.cfg "to /tmp"
@ -59,7 +59,7 @@ fi
# .../var/ # .../var/
echo -n "Supprimer et recréer .../var (archives, photos, configs, ...) ? (y/n) [y] " echo -n "Supprimer et recréer .../var (archives, photos, configs, ...) ? (y/n) [y] "
read ans read -r ans
if [ "$(norm_ans "$ans")" != 'N' ] if [ "$(norm_ans "$ans")" != 'N' ]
then then
echo "moving ../../../var/scodoc to /tmp" echo "moving ../../../var/scodoc to /tmp"
@ -73,7 +73,7 @@ fi
# LOGS ZOPE # LOGS ZOPE
echo -n "Effacer les logs de Zope et ScoDoc ? (y/n) [y] " echo -n "Effacer les logs de Zope et ScoDoc ? (y/n) [y] "
read ans read -r ans
if [ "$(norm_ans "$ans")" != 'N' ] if [ "$(norm_ans "$ans")" != 'N' ]
then then
(cd ../../../log/; ./purge) (cd ../../../log/; ./purge)
@ -81,7 +81,7 @@ fi
# IMAGE Data.fs # IMAGE Data.fs
echo -n "Recopier le Data.fs original ? (y/n) [y] " echo -n "Recopier le Data.fs original ? (y/n) [y] "
read ans read -r ans
if [ "$(norm_ans "$ans")" != 'N' ] if [ "$(norm_ans "$ans")" != 'N' ]
then then
echo "moving Data.fs to /tmp" echo "moving Data.fs to /tmp"

50
config/get_scodoc_version.sh Executable file
View File

@ -0,0 +1,50 @@
#!/bin/bash
# Get version information
# Use VERSION.py, last commit, diff, and last upstream commit date
source config.sh
source utils.sh
# Source code version:
x=$(grep SCOVERSION ../VERSION.py) || terminate "can't access VERSION.py" 1
x=${x#*\"}
src_version=${x%\"*}
# last commit
git_last_commit_hash=$(git log -1 --format=%h)
git_last_commit_date=$(git log -1 --format=%ci)
git_up_commit_hash=$(git log -1 --format=%h origin/ScoDoc8)
git_up_commit_date=$(git log -1 --format=%ci origin/ScoDoc8)
# Check if git has local changes
nchanges=$(git status --porcelain | grep -c -v '^??')
if [ "$nchanges" -gt 0 ]
then
has_local_changes="yes"
else
has_local_changes="no"
fi
# Synthetic one-line version:
sco_version="$src_version ($git_up_commit_hash) $git_up_commit_date"
if [ "$has_local_changes" = "yes" ]
then
sco_version="$sco_version (modified)"
fi
#
if [ "$1" = "-s" ]
then
echo "$sco_version"
else
echo src_version: "$src_version"
echo git_last_commit_hash: "$git_last_commit_hash"
echo git_last_commit_date: "$git_last_commit_date"
echo git_up_commit_hash: "$git_up_commit_hash"
echo git_up_commit_date: "$git_up_commit_date"
echo has_local_diffs: "$has_local_changes"
echo sco_version: "$sco_version"
fi

View File

@ -8,15 +8,16 @@
source config.sh source config.sh
source utils.sh source utils.sh
if [ $(id -nu) != $POSTGRES_USER ] if [ "$(id -nu)" != "$POSTGRES_USER" ]
then then
echo "$0: script must be runned as user $POSTGRES_USER" echo "$0: script must be runned as user $POSTGRES_USER"
exit 1 exit 1
fi fi
echo 'Initializing tables in database ' $db_name # shellcheck disable=SC2154
$PSQL -U $POSTGRES_USER -p $POSTGRES_PORT $db_name -f $SCODOC_DIR/misc/createtables.sql echo 'Initializing tables in database ' "$db_name"
$PSQL -U "$POSTGRES_USER" -p "$POSTGRES_PORT" "$db_name" -f "$SCODOC_DIR"/misc/createtables.sql
# Set DeptName in preferences: # Set DeptName in preferences:
echo "insert into sco_prefs (name, value) values ('DeptName', '"${DEPT}\'\) | $PSQL -U $POSTGRES_USER -p $POSTGRES_PORT $db_name echo "insert into sco_prefs (name, value) values ('DeptName', '"${DEPT}\'\) | $PSQL -U "$POSTGRES_USER" -p "$POSTGRES_PORT" "$db_name"

View File

@ -2,9 +2,10 @@
# #
# ScoDoc: install third-party software necessary for our installation # ScoDoc: install third-party software necessary for our installation
# starting for a minimal Debian (Stretch, 9.0) install. # starting for a minimal Debian (Buster, 10.0) install.
# #
# E. Viennet, Jun 2008, Apr 2009, Sept 2011, Sept 2013, Nov 2013, Mar 2017, Jul 2017, Jun 2019, Oct 2019 # E. Viennet, Jun 2008, Apr 2009, Sept 2011, Sept 2013, Nov 2013, Mar 2017, Jul 2017,
# Jun 2019, Oct 2019, Dec 2020
# #
source config.sh source config.sh
@ -12,16 +13,14 @@ source utils.sh
check_uid_root $0 check_uid_root $0
PYTHON=/opt/zope213/bin/python
# ------------ Safety checks # ------------ Safety checks
if [ ${debian_version} != "10" ] if [ "${debian_version}" != "10" ]
then then
echo "Version du systeme Linux Debian incompatible" echo "Version du systeme Linux Debian incompatible"
exit 1 exit 1
fi fi
if [ $(arch) != "x86_64" ] if [ "$(arch)" != "x86_64" ]
then then
echo "Version du systeme Linux Debian incompatible (pas X86 64 bits)" echo "Version du systeme Linux Debian incompatible (pas X86 64 bits)"
exit 1 exit 1
@ -29,8 +28,8 @@ fi
# ------------ Permissions & directories # ------------ Permissions & directories
# source dir should be writable by scodoc to write bytecode files # source dir should be writable by scodoc to write bytecode files
chgrp www-data $SCODOC_DIR $SCODOC_DIR/ZopeProducts/* chgrp www-data "$SCODOC_DIR" "$SCODOC_DIR"/ZopeProducts/*
chmod g+w $SCODOC_DIR $SCODOC_DIR/ZopeProducts/* chmod g+w "$SCODOC_DIR" "$SCODOC_DIR"/ZopeProducts/*
chgrp -R www-data "${SCODOC_VAR_DIR}"/photos chgrp -R www-data "${SCODOC_VAR_DIR}"/photos
chmod -R g+w "${SCODOC_VAR_DIR}"/photos chmod -R g+w "${SCODOC_VAR_DIR}"/photos
@ -53,8 +52,8 @@ fi
for locname in en_US.UTF-8 en_US.ISO-8859-15 en_US.ISO-8859-1 for locname in en_US.UTF-8 en_US.ISO-8859-15 en_US.ISO-8859-1
do do
outname=$(echo ${locname//-/} | tr '[A-Z]' '[a-z]') outname=$(echo ${locname//-/} | tr 'A-Z' 'a-z')
if [ $(locale -a | egrep -i ^${outname}$ | wc -l) -lt 1 ] if [ "$(locale -a | grep -E -i ^${outname}$ | wc -l)" -lt 1 ]
then then
echo adding $locname echo adding $locname
echo "$locname ${locname##*.}" >> /etc/locale.gen echo "$locname ${locname##*.}" >> /etc/locale.gen
@ -86,7 +85,7 @@ apt-get -y install postgresql
apt-get -y install graphviz apt-get -y install graphviz
# ------------ INSTALL DES EXTENSIONS PYTHON (2.7) # ------------ INSTALL DES EXTENSIONS PYTHON (2.7)
# XXX to fix: pip in our env
apt-get -y install python-docutils apt-get -y install python-docutils
apt-get -y install python-jaxml apt-get -y install python-jaxml
apt-get -y install python-psycopg2 apt-get -y install python-psycopg2
@ -96,20 +95,21 @@ apt-get -y install python-cracklib # was python-crack
apt-get -y install python-icalendar apt-get -y install python-icalendar
apt-get -y install python-requests apt-get -y install python-requests
# XXX to fix: mx not needed anymore !
apt-get -y install python-egenix-mxtools python-egenix-mxdatetime apt-get -y install python-egenix-mxtools python-egenix-mxdatetime
# ------------ # ------------
SVNVERSION=$(cd ..; svnversion) SVNVERSION=$(cd ..; svnversion)
SVERSION=$(curl --silent http://scodoc.iutv.univ-paris13.fr/scodoc-installmgr/version?mode=install\&svn=$SVNVERSION) SVERSION=$(curl --silent http://scodoc.iutv.univ-paris13.fr/scodoc-installmgr/version?mode=install\&svn="$SVNVERSION")
echo $SVERSION > "${SCODOC_VERSION_DIR}/scodoc.sn" echo "$SVERSION" > "${SCODOC_VERSION_DIR}/scodoc.sn"
# ------------ POSTFIX # ------------ POSTFIX
echo echo
echo "ScoDoc a besoin de pouvoir envoyer des messages par mail." echo "ScoDoc a besoin de pouvoir envoyer des messages par mail."
echo -n "Voulez vous configurer la messagerie (tres recommande) ? (y/n) [y] " echo -n "Voulez vous configurer la messagerie (tres recommande) ? (y/n) [y] "
read ans read -r ans
if [ "$(norm_ans "$ans")" != 'N' ] if [ "$(norm_ans "$ans")" != 'N' ]
then then
apt-get -y install postfix apt-get -y install postfix
@ -119,7 +119,7 @@ fi
echo echo
echo "Le firewall aide a proteger votre serveur d'intrusions indesirables." echo "Le firewall aide a proteger votre serveur d'intrusions indesirables."
echo -n "Voulez vous configurer un firewall minimal (ufw) ? (y/n) [n] " echo -n "Voulez vous configurer un firewall minimal (ufw) ? (y/n) [n] "
read ans read -r ans
if [ "$(norm_ans "$ans")" = 'Y' ] if [ "$(norm_ans "$ans")" = 'Y' ]
then then
echo 'Installation du firewall IP ufw (voir documentation Debian)' echo 'Installation du firewall IP ufw (voir documentation Debian)'
@ -144,16 +144,16 @@ a2enmod rewrite
echo echo
echo "La configuration du serveur web va modifier votre installation Apache pour supporter ScoDoc." echo "La configuration du serveur web va modifier votre installation Apache pour supporter ScoDoc."
echo -n "Voulez vous configurer le serveur web Apache maintenant (tres conseille) ? (y/n) [y] " echo -n "Voulez vous configurer le serveur web Apache maintenant (tres conseille) ? (y/n) [y] "
read ans read -r ans
if [ "$(norm_ans "$ans")" != 'N' ] if [ "$(norm_ans "$ans")" != 'N' ]
then then
echo "Configuration d'Apache" echo "Configuration d'Apache"
server_name="" server_name=""
while [ -z $server_name ] while [ -z "$server_name" ]
do do
echo "Le nom de votre serveur doit normalement etre connu dans le DNS." echo "Le nom de votre serveur doit normalement etre connu dans le DNS."
echo -n "Nom complet de votre serveur (exemple: notes.univ.fr): " echo -n "Nom complet de votre serveur (exemple: notes.univ.fr): "
read server_name read -r server_name
done done
# --- CERTIFICATS AUTO-SIGNES # --- CERTIFICATS AUTO-SIGNES
echo echo
@ -161,7 +161,7 @@ then
echo "auto-signes, qui ne seront pas reconnus comme de confiance" echo "auto-signes, qui ne seront pas reconnus comme de confiance"
echo "par les navigateurs, mais offrent une certaine securite." echo "par les navigateurs, mais offrent une certaine securite."
echo -n 'Voulez vous generer des certificats ssl auto-signes ? (y/n) [y] ' echo -n 'Voulez vous generer des certificats ssl auto-signes ? (y/n) [y] '
read ans read -r ans
if [ "$(norm_ans "$ans")" != 'N' ] if [ "$(norm_ans "$ans")" != 'N' ]
then then
# attention: utilise dans scodoc-site-ssl.orig # attention: utilise dans scodoc-site-ssl.orig
@ -177,7 +177,7 @@ then
fi fi
# --- # ---
echo 'generation de /etc/apache2/sites-available/scodoc-site-ssl' echo 'generation de /etc/apache2/sites-available/scodoc-site-ssl'
cat $SCODOC_DIR/config/etc/scodoc-site-ssl-apache2.4.orig | sed -e "s:YOUR\.FULL\.HOST\.NAME:$server_name:g" > /etc/apache2/sites-available/scodoc-site-ssl.conf cat "$SCODOC_DIR"/config/etc/scodoc-site-ssl-apache2.4.orig | sed -e "s:YOUR\.FULL\.HOST\.NAME:$server_name:g" > /etc/apache2/sites-available/scodoc-site-ssl.conf
echo 'activation du site...' echo 'activation du site...'
a2ensite scodoc-site-ssl a2ensite scodoc-site-ssl
@ -187,7 +187,7 @@ then
then then
mv $fn $fn.bak mv $fn $fn.bak
fi fi
cp $SCODOC_DIR/config/etc/scodoc-site.orig $fn cp "$SCODOC_DIR"/config/etc/scodoc-site.orig $fn
if [ -z "$(grep Listen /etc/apache2/ports.conf | grep 443)" ] if [ -z "$(grep Listen /etc/apache2/ports.conf | grep 443)" ]
then then
@ -221,7 +221,7 @@ if [ "$(norm_ans "$ans")" != 'N' ]
then then
# ScoDoc 7.19+ uses systemd # ScoDoc 7.19+ uses systemd
echo 'Installation du demarrage automatique de ScoDoc (systemd)' echo 'Installation du demarrage automatique de ScoDoc (systemd)'
cp $SCODOC_DIR/config/etc/scodoc.service /etc/systemd/system cp "$SCODOC_DIR"/config/etc/scodoc.service /etc/systemd/system
systemctl enable scodoc.service systemctl enable scodoc.service
fi fi
@ -232,8 +232,8 @@ echo -n "Mises a jour hebdomadaires (tres recommande) ? (y/n) [y] "
read ans read ans
if [ "$(norm_ans "$ans")" != 'N' ] if [ "$(norm_ans "$ans")" != 'N' ]
then then
cp $SCODOC_DIR/config/etc/scodoc-updater.service /etc/systemd/system cp "$SCODOC_DIR"/config/etc/scodoc-updater.service /etc/systemd/system
cp $SCODOC_DIR/config/etc/scodoc-updater.timer /etc/systemd/system cp "$SCODOC_DIR"/config/etc/scodoc-updater.timer /etc/systemd/system
systemctl enable scodoc-updater.timer systemctl enable scodoc-updater.timer
systemctl start scodoc-updater.timer systemctl start scodoc-updater.timer
fi fi

0
config/migre-7-a-8.sh Normal file → Executable file
View File

View File

@ -8,7 +8,7 @@ PG_DUMPFILE=$1
# Check locale of installation. If invalid, reinitialize all system # Check locale of installation. If invalid, reinitialize all system
is_latin1=$(psql -l | grep postgres | grep iso88591 | wc -l) is_latin1=$(psql -l | grep postgres | grep iso88591 | wc -l)
if [ $is_latin1 -gt 1 ] if [ "$is_latin1" -gt 1 ]
then then
echo "Recreating postgres cluster using UTF-8" echo "Recreating postgres cluster using UTF-8"
@ -20,8 +20,8 @@ fi
# Drop all current ScoDoc databases, if any: # Drop all current ScoDoc databases, if any:
for f in $(psql -l --no-align --field-separator . | grep SCO | cut -f 1 -d.); do for f in $(psql -l --no-align --field-separator . | grep SCO | cut -f 1 -d.); do
echo dropping $f echo dropping "$f"
dropdb $f dropdb "$f"
done done
echo "Restoring postgres data..." echo "Restoring postgres data..."
psql -f "$PG_DUMPFILE" postgres psql -f "$PG_DUMPFILE" postgres

View File

@ -57,7 +57,7 @@ then
echo "Opening tgz archive..." echo "Opening tgz archive..."
tmp=$(mktemp -d) tmp=$(mktemp -d)
chmod a+rx "$tmp" chmod a+rx "$tmp"
cd "$tmp" || terminate "directory error" cd "$tmp" || { echo "Fatal error: directory not available"; exit 1; }
tar xfz "$SRC" tar xfz "$SRC"
SRC=$(ls -1d "$tmp"/*) SRC=$(ls -1d "$tmp"/*)
IS_TMP=1 IS_TMP=1
@ -83,18 +83,18 @@ su -c "$SCODOC_DIR/config/psql_restore_databases.sh $PG_DUMPFILE" postgres
# #
echo Copying data files... echo Copying data files...
rm -rf "${INSTANCE_DIR:?}/var" rm -rf "$INSTANCE_DIR/var"
$COPY "$SRC/var" "$INSTANCE_DIR" $COPY "$SRC/var" "$INSTANCE_DIR"
if [ ! -e "${SCODOC_VAR_DIR:?}/config/" ] if [ ! -e "${SCODOC_VAR_DIR}/config/" ]
then then
mkdir "${SCODOC_VAR_DIR:?}/config/" mkdir "${SCODOC_VAR_DIR}/config/"
chown www-data.www-data "${SCODOC_VAR_DIR:?}/config/" chown www-data.www-data "${SCODOC_VAR_DIR}/config/"
chmod 775 "${SCODOC_VAR_DIR:?}/config/" chmod 775 "${SCODOC_VAR_DIR}/config/"
fi fi
rm -rf "${SCODOC_DIR:?}/config/depts" rm -rf "$SCODOC_DIR/config/depts"
if [ -e "${SRC:?}/depts" ] if [ -e "$SRC/depts" ]
then then
# legacy depts => move them to var # legacy depts => move them to var
$COPY "$SRC/depts" "${SCODOC_VAR_DIR}/config/" $COPY "$SRC/depts" "${SCODOC_VAR_DIR}/config/"
@ -107,7 +107,7 @@ then
$COPY "$SRC/photos" "${SCODOC_VAR_DIR}/" $COPY "$SRC/photos" "${SCODOC_VAR_DIR}/"
fi fi
rm -rf "${SCODOC_DIR:?}/logos" rm -rf "$SCODOC_DIR/logos"
$COPY "$SRC/logos" "$SCODOC_DIR/" $COPY "$SRC/logos" "$SCODOC_DIR/"
mv "$SCODOC_DIR/config/scodoc_config.py" "$SCODOC_DIR/config/scodoc_config.py.$(date +%Y%m%d-%H%M%S)" mv "$SCODOC_DIR/config/scodoc_config.py" "$SCODOC_DIR/config/scodoc_config.py.$(date +%Y%m%d-%H%M%S)"
@ -119,7 +119,7 @@ then
iconv -f iso8859-15 -t utf-8 "$SCODOC_DIR/config/scodoc_config.py.orig" > "$SCODOC_DIR/config/scodoc_config.py" iconv -f iso8859-15 -t utf-8 "$SCODOC_DIR/config/scodoc_config.py.orig" > "$SCODOC_DIR/config/scodoc_config.py"
fi fi
rm -rf "${INSTANCE_DIR:?}/log" rm -rf "$INSTANCE_DIR/log"
$COPY "$SRC/log" "$INSTANCE_DIR/" $COPY "$SRC/log" "$INSTANCE_DIR/"
# Fix file ownership and access rights # Fix file ownership and access rights
@ -130,13 +130,13 @@ chown -R www-data.root "$SCODOC_DIR"
chmod -R 775 "$SCODOC_DIR" chmod -R 775 "$SCODOC_DIR"
# Remove tmp directory # Remove tmp directory
if [ "$IS_TMP" = "1" ] if [ $IS_TMP = "1" ]
then then
rm -rf "${tmp}" rm -rf "$tmp"
fi fi
# Mise a jour BD ScoDoc # Mise a jour BD ScoDoc
cd ${SCODOC_DIR:?}/config || terminate "no config directory" cd "$SCODOC_DIR"/config || { echo "Fatal error: invalid directory"; exit 2; }
./upgrade.sh ./upgrade.sh
# #

View File

@ -10,6 +10,8 @@
# #
source utils.sh source utils.sh
check_uid_root "$0"
# Destination directory # Destination directory
if [ ! $# -eq 1 ] if [ ! $# -eq 1 ]
then then
@ -34,9 +36,6 @@ fi
INSTANCE_DIR=/opt/scodoc INSTANCE_DIR=/opt/scodoc
SCODOC_DIR="$INSTANCE_DIR/Products/ScoDoc" SCODOC_DIR="$INSTANCE_DIR/Products/ScoDoc"
source utils.sh
check_uid_root "$0"
echo "Stopping ScoDoc..." echo "Stopping ScoDoc..."
scodocctl stop scodocctl stop
@ -44,9 +43,9 @@ scodocctl stop
echo "Dumping SQL database..." echo "Dumping SQL database..."
chown postgres "$DEST" chown postgres "$DEST"
su -c "pg_dumpall > \"$DEST\"/scodoc.dump.txt" postgres su -c "pg_dumpall > \"$DEST\"/scodoc.dump.txt" postgres
if [ ! "$?" -eq 0 ] if [ ! $? -eq 0 ]
then then
printf "Error dumping postgresql database\nPlease check that SQL server is running\nAborting." printf "Error dumping postgresql database\nPlease check that SQL server is running\nAborting.\n"
exit 1 exit 1
fi fi
chown root "$DEST" chown root "$DEST"
@ -86,6 +85,6 @@ cp -rp "$INSTANCE_DIR/log" "$DEST"
echo echo
echo "Archiving backup files in a $DEST.tgz..." echo "Archiving backup files in a $DEST.tgz..."
base=$(basename "$DEST") base=$(basename "$DEST")
(cd "$DEST"/.. || terminate "directory error"; tar cfz "$DEST".tgz "$base") (cd "$DEST"/..; tar cfz "$DEST".tgz "$base")
echo "Done (you can copy " "$DEST"".tgz to destination machine)." echo "Done (you can copy " "$DEST"".tgz to destination machine)."

View File

@ -0,0 +1,128 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
#
# Configuration globale de ScoDoc (version juin 2009)
# Ce fichier est copié dans /opt/scodoc/var/scodoc/config
# par les scripts d'installation/mise à jour.
# La plupart des réglages sont stoqués en base de donnée et accessibles via le web
# (pages de paramètres ou préférences).
# Les valeurs indiquées ici sont les valeurs initiales que prendront
# les paramètres lors de la création d'un nouveau département,
# elles ne sont plus utilisées ensuite.
# Nota: il y a aussi des réglages dans sco_utils.py, mais ils nécessitent
# souvent de comprendre le code qui les utilise pour ne pas faire d'erreur: attention.
class CFG:
pass
CONFIG = CFG()
# set to 1 if you want to require INE:
# CONFIG.always_require_ine = 0
# The base URL, use only if you are behind a proxy
# eg "https://scodoc.example.net/ScoDoc"
# CONFIG.ABSOLUTE_URL = ""
# -----------------------------------------------------
# -------------- Documents PDF
# -----------------------------------------------------
# Taille du l'image logo: largeur/hauteur (ne pas oublier le . !!!)
# W/H XXX provisoire: utilisera PIL pour connaitre la taille de l'image
# CONFIG.LOGO_FOOTER_ASPECT = 326 / 96.0
# Taille dans le document en millimetres
# CONFIG.LOGO_FOOTER_HEIGHT = 10
# Proportions logo (donné ici pour IUTV)
# CONFIG.LOGO_HEADER_ASPECT = 549 / 346.0
# Taille verticale dans le document en millimetres
# CONFIG.LOGO_HEADER_HEIGHT = 28
# Pied de page PDF : un format Python, %(xxx)s est remplacé par la variable xxx.
# Les variables définies sont:
# day : Day of the month as a decimal number [01,31]
# month : Month as a decimal number [01,12].
# year : Year without century as a decimal number [00,99].
# Year : Year with century as a decimal number.
# hour : Hour (24-hour clock) as a decimal number [00,23].
# minute: Minute as a decimal number [00,59].
#
# server_url: URL du serveur ScoDoc
# scodoc_name: le nom du logiciel (ScoDoc actuellement, voir VERSION.py)
# CONFIG.DEFAULT_PDF_FOOTER_TEMPLATE = "Edité par %(scodoc_name)s le %(day)s/%(month)s/%(year)s à %(hour)sh%(minute)s sur %(server_url)s"
#
# ------------- Calcul bonus modules optionnels (sport, culture...) -------------
#
# CONFIG.compute_bonus = bonus_sport.bonus_iutv
# Mettre "bonus_demo" pour logguer des informations utiles au developpement...
#
# ------------- Capitalisation des UEs -------------
# Deux écoles:
# - règle "DUT": capitalisation des UE obtenues avec moyenne UE >= 10 ET de toutes les UE
# des semestres validés (ADM, ADC, AJ). (conforme à l'arrêté d'août 2005)
#
# - règle "LMD": capitalisation uniquement des UE avec moy. > 10
# Si vrai, capitalise toutes les UE des semestres validés (règle "DUT").
# CONFIG.CAPITALIZE_ALL_UES = True
#
# -----------------------------------------------------
#
# -------------- Personnalisation des pages
#
# -----------------------------------------------------
# Nom (chemin complet) d'un fichier .html à inclure juste après le <body>
# le <body> des pages ScoDoc
# CONFIG.CUSTOM_HTML_HEADER = ""
# Fichier html a inclure en fin des pages (juste avant le </body>)
# CONFIG.CUSTOM_HTML_FOOTER = ""
# Fichier .html à inclure dans la pages connexion/déconnexion (accueil)
# si on veut que ce soit différent (par défaut la même chose)
# CONFIG.CUSTOM_HTML_HEADER_CNX = CONFIG.CUSTOM_HTML_HEADER
# CONFIG.CUSTOM_HTML_FOOTER_CNX = CONFIG.CUSTOM_HTML_FOOTER
# -----------------------------------------------------
# -------------- Noms de Lycées
# -----------------------------------------------------
# Fichier de correspondance codelycee -> noms
# (chemin relatif au repertoire d'install des sources)
# CONFIG.ETABL_FILENAME = "config/etablissements.csv"
# ----------------------------------------------------
# -------------- Divers:
# ----------------------------------------------------
# True for UCAC (étudiants camerounais sans prénoms)
# CONFIG.ALLOW_NULL_PRENOM = False
# Taille max des fichiers archive etudiants (en octets)
# CONFIG.ETUD_MAX_FILE_SIZE = 10 * 1024 * 1024
# Si pas de photo et portail, publie l'url (était vrai jusqu'en oct 2016)
# CONFIG.PUBLISH_PORTAL_PHOTO_URL = False
# Si > 0: longueur minimale requise des nouveaux mots de passe
# (le test cracklib.FascistCheck s'appliquera dans tous les cas)
# CONFIG.MIN_PASSWORD_LENGTH = 0
# Ce dictionnaire est fusionné à celui de sco_codes_parcours
# pour définir les codes jury et explications associées
# CONFIG.CODES_EXPL = {
# # AJ : 'Ajourné (échec)',
# }

View File

@ -4,7 +4,7 @@
# ScoDoc: reglage du mot de passe admin Zope # ScoDoc: reglage du mot de passe admin Zope
# (in Zope terminology, an emergency user) # (in Zope terminology, an emergency user)
# #
# Doit être lancé par l'utilisateur unix root dans le repertoire .../config # Doit <EFBFBD>tre lanc<6E> par l'utilisateur unix root dans le repertoire .../config
# ^^^^^^^^^^^^^^^^^^^^^ # ^^^^^^^^^^^^^^^^^^^^^
# E. Viennet, Juin 2008, Jul 2019 # E. Viennet, Juin 2008, Jul 2019
# #
@ -22,14 +22,14 @@ fi
echo "Creation d'un utilisateur d'urgence pour ScoDoc" echo "Creation d'un utilisateur d'urgence pour ScoDoc"
echo "(utile en cas de perte de votre mot de passe admin)" echo "(utile en cas de perte de votre mot de passe admin)"
if [ ${debian_version} != "10" ] if [ "${debian_version}" != "10" ]
then then
mdir=/opt/zope213/lib/python2.7/site-packages/Zope2-2.13.21-py2.7.egg/Zope2/utilities mdir=/opt/zope213/lib/python2.7/site-packages/Zope2-2.13.21-py2.7.egg/Zope2/utilities
else else
mdir=/opt/zope213/lib/python2.7/site-packages/Zope2/utilities mdir=/opt/zope213/lib/python2.7/site-packages/Zope2/utilities
fi fi
python $mdir/zpasswd.py $SCODOC_DIR/../../access python $mdir/zpasswd.py "$SCODOC_DIR"/../../access
echo echo
echo "redemarrer scodoc pour prendre en compte le mot de passe" echo "redemarrer scodoc pour prendre en compte le mot de passe"

View File

@ -1,20 +1,20 @@
#!/bin/bash #!/bin/bash
# Upgrade ScoDoc installation using SVN # Upgrade ScoDoc installation using GIT
# SVN must be properly configured and have read access to ScoDoc repository # GIT must be properly configured and have read access to ScoDoc repository
# This script STOP and RESTART ScoDoc and should be runned as root # This script STOP and RESTART ScoDoc and should be runned as root
# #
# Upgrade also the Linux system using apt. # Upgrade also the Linux system using apt.
# #
# Script for ScoDoc 7 (Debian 7, 8, 9, 10) # Script for ScoDoc 8 (10)
# #
# E. Viennet, sep 2013, mar 2017, jun 2019, aug 2020 # E. Viennet, sep 2013, mar 2017, jun 2019, aug 2020, dec 2020
cd /opt/scodoc/Products/ScoDoc/config cd /opt/scodoc/Products/ScoDoc/config || { echo "Invalid directory"; exit 1; }
source config.sh source config.sh
source utils.sh source utils.sh
check_uid_root $0 check_uid_root "$0"
if [ -z "$SCODOC_UPGRADE_RUNNING" ] if [ -z "$SCODOC_UPGRADE_RUNNING" ]
then then
@ -31,8 +31,8 @@ fi
scodocctl stop scodocctl stop
echo echo
echo "Using SVN to update $SCODOC_DIR..." echo "Using git to update $SCODOC_DIR..."
(cd "$SCODOC_DIR"; svn update) (cd "$SCODOC_DIR"; git checkout ScoDoc8; git pull origin master)
SVNVERSION=$(cd ..; svnversion) SVNVERSION=$(cd ..; svnversion)
@ -72,13 +72,13 @@ fi
# Check that no Zope "access" file has been forgotten in the way: # Check that no Zope "access" file has been forgotten in the way:
if [ -e $SCODOC_DIR/../../access ] if [ -e "$SCODOC_DIR"/../../access ]
then then
mv $SCODOC_DIR/../../access $SCODOC_DIR/../../access.bak mv "$SCODOC_DIR"/../../access "$SCODOC_DIR"/../../access.bak
fi fi
# Fix some permissions which may have been altered in the way: # Fix some permissions which may have been altered in the way:
chsh -s /bin/sh $POSTGRES_USER # www-data, nologin in Debian 9 chsh -s /bin/sh "$POSTGRES_USER" # www-data, nologin in Debian 9
chown root.www-data "$SCODOC_DIR" # important to create .pyc chown root.www-data "$SCODOC_DIR" # important to create .pyc
chmod 775 "${SCODOC_DIR}" chmod 775 "${SCODOC_DIR}"
chmod a+r "$SCODOC_DIR"/*.py chmod a+r "$SCODOC_DIR"/*.py
@ -118,6 +118,19 @@ if [ $? -ne 0 ]
then then
/opt/zope213/bin/pip install requests /opt/zope213/bin/pip install requests
fi fi
/opt/zope213/bin/python -c "import attrdict" >& /dev/null
if [ $? -ne 0 ]
then
/opt/zope213/bin/pip install attrdict
fi
# Check that local configuration file is installed
LOCAL_CONFIG_FILENAME="/opt/scodoc/var/scodoc/config/scodoc_local.py"
if [ ! -e "$LOCAL_CONFIG_FILENAME" ]
then
cp "$SCODOC_DIR"/config/scodoc_config_tmpl.py "$LOCAL_CONFIG_FILENAME"
chmod 600 "$LOCAL_CONFIG_FILENAME"
fi
# Ensure www-data can duplicate databases (for dumps) # Ensure www-data can duplicate databases (for dumps)
su -c $'psql -c \'alter role "www-data" with CREATEDB;\'' "$POSTGRES_SUPERUSER" su -c $'psql -c \'alter role "www-data" with CREATEDB;\'' "$POSTGRES_SUPERUSER"
@ -128,7 +141,7 @@ echo "Executing post-upgrade script..."
"$SCODOC_DIR"/config/postupgrade.py "$SCODOC_DIR"/config/postupgrade.py
echo "Executing post-upgrade database script..." echo "Executing post-upgrade database script..."
su -c "$SCODOC_DIR/config/postupgrade-db.py" $POSTGRES_USER su -c "$SCODOC_DIR/config/postupgrade-db.py" "$POSTGRES_USER"
# #
echo echo

View File

@ -1,17 +1,17 @@
#!/bin/bash
# Misc utilities for ScoDoc install shell scripts # Misc utilities for ScoDoc install shell scripts
to_lower() { to_lower() {
echo $1 | tr "[:upper:]" "[:lower:]" echo "$1" | tr "[:upper:]" "[:lower:]"
} }
to_upper() { to_upper() {
echo $1 | tr "[:lower:]" "[:upper:]" echo "$1" | tr "[:lower:]" "[:upper:]"
} }
norm_ans() { norm_ans() {
x=$(to_upper $1 | tr O Y) x=$(to_upper "$1" | tr O Y)
echo ${x:0:1} echo "${x:0:1}"
} }
check_uid_root() { check_uid_root() {
@ -23,10 +23,11 @@ check_uid_root() {
} }
terminate() { terminate() {
status=${2:-1} # default: exit 1
echo echo
echo "Erreur: $1" echo "Erreur: $1"
echo echo
exit 1 exit $status
} }
# Start/stop scodoc, using sysv or systemd # Start/stop scodoc, using sysv or systemd
@ -44,7 +45,7 @@ scodocctl() {
systemctl $1 scodoc systemctl $1 scodoc
else else
echo "(using legacy SystemV)" echo "(using legacy SystemV)"
/etc/init.d/scodoc $1 /etc/init.d/scodoc "$1"
fi fi
} }
@ -54,7 +55,7 @@ init_postgres_user() { # run as root
then then
# add database user # add database user
echo "Creating postgresql user $POSTGRES_USER" echo "Creating postgresql user $POSTGRES_USER"
su -c "createuser -p $POSTGRES_PORT --no-superuser --no-createdb --no-adduser --no-createrole ${POSTGRES_USER}" $POSTGRES_SUPERUSER su -c "createuser -p $POSTGRES_PORT --no-superuser --no-createdb --no-adduser --no-createrole ${POSTGRES_USER}" "$POSTGRES_SUPERUSER"
fi fi
} }
@ -69,8 +70,8 @@ gen_passwd() {
password="" password=""
while [ "$n" -le "$PASSWORD_LENGTH" ] while [ "$n" -le "$PASSWORD_LENGTH" ]
do do
password="$password${ALLOWABLE_ASCII:$(($RANDOM%${#ALLOWABLE_ASCII})):1}" password="$password${ALLOWABLE_ASCII:$((RANDOM%${#ALLOWABLE_ASCII})):1}"
n=$((n+1)) n=$((n+1))
done done
echo $password echo "$password"
} }

View File

@ -31,8 +31,6 @@ nt = context.Notes._getNotesCache().get_NotesTable(context.Notes, formsemestre_i
""" """
import pdb
from notesdb import * from notesdb import *
from notes_log import log from notes_log import log
from sco_utils import * from sco_utils import *
@ -80,9 +78,6 @@ class FakeUser:
def has_permission(self, op, context): def has_permission(self, op, context):
return True return True
def has_role(self, role):
return True
class DummyResponse: class DummyResponse:
"""Emulation vide de Reponse http Zope""" """Emulation vide de Reponse http Zope"""
@ -109,7 +104,6 @@ class DummyRequest:
self.URL1 = self.URL self.URL1 = self.URL
self.URL0 = self.URL self.URL0 = self.URL
self.BASE0 = "localhost" self.BASE0 = "localhost"
self.REMOTE_HOST = "localhost"
self.REMOTE_ADDR = "127.0.0.1" self.REMOTE_ADDR = "127.0.0.1"
self.HTTP_REFERER = "" self.HTTP_REFERER = ""
self.REQUEST_METHOD = "get" self.REQUEST_METHOD = "get"

View File

@ -394,7 +394,7 @@ CREATE TABLE notes_formsemestre (
elt_annee_apo text -- code element annee Apogee, eg VRT1A ou V2INLA,V2INCA elt_annee_apo text -- code element annee Apogee, eg VRT1A ou V2INLA,V2INCA
) WITH OIDS; ) WITH OIDS;
-- id des utilisateurs responsables (aka directeurs des etudes) du semestre: -- id des utilsateurs responsables (aka directeurs des etudes) du semestre:
CREATE TABLE notes_formsemestre_responsables ( CREATE TABLE notes_formsemestre_responsables (
formsemestre_id text REFERENCES notes_formsemestre(formsemestre_id) ON DELETE CASCADE, formsemestre_id text REFERENCES notes_formsemestre(formsemestre_id) ON DELETE CASCADE,
responsable_id text NOT NULL, responsable_id text NOT NULL,

View File

@ -51,5 +51,5 @@ telephone; text; adresse; 1; num. telephone (fixe)
telephonemobile; text; adresse; 1; num. telephone (mobile) telephonemobile; text; adresse; 1; num. telephone (mobile)
# #
# Pas tout à fait admission: # Pas tout à fait admission:
debouche;text; admissions;1;(OBSOLETE, ne plus utiliser) situation APRES être passé par chez nous; debouche;text; admissions;1;situation APRES être passé par chez nous;

View File

@ -2,10 +2,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import pdb, os, sys, time, re, inspect import pdb, os, sys, time, re, inspect
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.Header import Header
import traceback import traceback
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.header import Header
# Simple & stupid file logguer, used only to debug # Simple & stupid file logguer, used only to debug
# (logging to SQL is done in scolog) # (logging to SQL is done in scolog)
@ -34,7 +35,7 @@ class _logguer:
if LOG_FILENAME: if LOG_FILENAME:
path = os.path.join(self.directory, LOG_FILENAME) path = os.path.join(self.directory, LOG_FILENAME)
self.file = open(path, "a") self.file = open(path, "a")
self("new _logguer (%s)" % path) self("new _logguer")
else: else:
self.file = None # logging disabled self.file = None # logging disabled
@ -88,7 +89,7 @@ def retreive_dept():
return "" return ""
try: try:
url = REQUEST.URL url = REQUEST.URL
m = re.match("^.*ScoDoc/(\w+).*$", url) m = re.match(r"^.*ScoDoc/(\w+).*$", url)
return m.group(1) return m.group(1)
except: except:
return "" return ""

View File

@ -248,7 +248,8 @@ def DBDelete(cnx, table, colid, val, commit=False):
class EditableTable: class EditableTable:
"""--- generic class: SQL table with create/edit/list/delete""" """ --- generic class: SQL table with create/edit/list/delete
"""
def __init__( def __init__(
self, self,
@ -376,11 +377,7 @@ class EditableTable:
# format value # format value
for title in vals.keys(): for title in vals.keys():
if self.input_formators.has_key(title): if self.input_formators.has_key(title):
try: vals[title] = self.input_formators[title](vals[title])
vals[title] = self.input_formators[title](vals[title])
except:
log("exception while converting %s=%s" % (title, vals[title]))
raise
DBUpdateArgs( DBUpdateArgs(
cnx, cnx,
self.table_name, self.table_name,

View File

@ -50,7 +50,7 @@ DONNEE_MANQUANTE = (
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def get_code_latex_from_modele(fichier): def get_code_latex_from_modele(fichier):
"""Lit le code latex à partir d'un modèle. Renvoie une chaine unicode. """Lit le code latex à partir d'un modèle. Renvoie une chaine unicode.
Le fichier doit contenir le chemin relatif Le fichier doit contenir le chemin relatif
vers le modele : attention pas de vérification du format d'encodage vers le modele : attention pas de vérification du format d'encodage
Le fichier doit donc etre enregistré avec le même codage que ScoDoc (utf-8) Le fichier doit donc etre enregistré avec le même codage que ScoDoc (utf-8)
@ -85,7 +85,7 @@ def get_tags_latex(code_latex):
à la lecture d'un modèle d'avis pe). à la lecture d'un modèle d'avis pe).
Ces tags sont répérés par les balises **, débutant et finissant le tag Ces tags sont répérés par les balises **, débutant et finissant le tag
et sont renvoyés sous la forme d'une liste. et sont renvoyés sous la forme d'une liste.
result: liste de chaines unicode result: liste de chaines unicode
""" """
if code_latex: if code_latex:
@ -144,7 +144,7 @@ def comp_latex_parcourstimeline(etudiant, promo, taille=17):
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def interprete_tag_latex(tag): def interprete_tag_latex(tag):
"""Découpe les tags latex de la forme S1:groupe:dut:min et renvoie si possible """Découpe les tags latex de la forme S1:groupe:dut:min et renvoie si possible
le résultat sous la forme d'un quadruplet. le résultat sous la forme d'un quadruplet.
""" """
infotag = tag.split(":") infotag = tag.split(":")
@ -164,7 +164,7 @@ def get_code_latex_avis_etudiant(
donnees_etudiant, un_avis_latex, annotationPE, footer_latex, prefs donnees_etudiant, un_avis_latex, annotationPE, footer_latex, prefs
): ):
""" """
Renvoie le code latex permettant de générer l'avis d'un étudiant en utilisant ses Renvoie le code latex permettant de générer l'avis d'un étudiant en utilisant ses
donnees_etudiant contenu dans le dictionnaire de synthèse du jury PE et en suivant un donnees_etudiant contenu dans le dictionnaire de synthèse du jury PE et en suivant un
fichier modele donné fichier modele donné
@ -228,8 +228,8 @@ def get_code_latex_avis_etudiant(
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def get_annotation_PE(context, etudid, tag_annotation_pe): def get_annotation_PE(context, etudid, tag_annotation_pe):
"""Renvoie l'annotation PE dans la liste de ces annotations ; """Renvoie l'annotation PE dans la liste de ces annotations ;
Cette annotation est reconnue par la présence d'un tag **PE** Cette annotation est reconnue par la présence d'un tag **PE**
(cf. context.get_preferences -> pe_tag_annotation_avis_latex). (cf. context.get_preferences -> pe_tag_annotation_avis_latex).
Result: chaine unicode Result: chaine unicode
@ -269,8 +269,8 @@ def get_annotation_PE(context, etudid, tag_annotation_pe):
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ): def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ):
"""Extrait du dictionnaire de synthèse du juryPE pour un étudiant donnée, """Extrait du dictionnaire de synthèse du juryPE pour un étudiant donnée,
une valeur indiquée par un champ ; une valeur indiquée par un champ ;
si champ est une liste, renvoie la liste des valeurs extraites. si champ est une liste, renvoie la liste des valeurs extraites.
Result: chaine unicode ou liste de chaines unicode Result: chaine unicode ou liste de chaines unicode
@ -322,7 +322,7 @@ def str_from_syntheseJury(donnees_etudiant, aggregat, groupe, tag_scodoc, champ)
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def get_bilanParTag(donnees_etudiant, groupe="groupe"): def get_bilanParTag(donnees_etudiant, groupe="groupe"):
"""Renvoie le code latex d'un tableau récapitulant, pour tous les tags trouvés dans """Renvoie le code latex d'un tableau récapitulant, pour tous les tags trouvés dans
les données étudiants, ses résultats. les données étudiants, ses résultats.
result: chaine unicode result: chaine unicode
""" """
@ -460,11 +460,11 @@ def get_templates_from_distrib(template="avis"):
if template in ["avis", "footer"]: if template in ["avis", "footer"]:
# pas de preference pour le template: utilise fichier du serveur # pas de preference pour le template: utilise fichier du serveur
p = os.path.join(SCO_SRCDIR, pe_local_tmpl) p = os.path.join(SCO_SRC_DIR, pe_local_tmpl)
if os.path.exists(p): if os.path.exists(p):
template_latex = get_code_latex_from_modele(p) template_latex = get_code_latex_from_modele(p)
else: else:
p = os.path.join(SCO_SRCDIR, pe_default_tmpl) p = os.path.join(SCO_SRC_DIR, pe_default_tmpl)
if os.path.exists(p): if os.path.exists(p):
template_latex = get_code_latex_from_modele(p) template_latex = get_code_latex_from_modele(p)
else: else:
@ -474,8 +474,7 @@ def get_templates_from_distrib(template="avis"):
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def table_syntheseAnnotationPE(context, syntheseJury, tag_annotation_pe): def table_syntheseAnnotationPE(context, syntheseJury, tag_annotation_pe):
"""Génère un fichier excel synthétisant les annotations PE telles qu'inscrites dans les fiches de chaque étudiant """Génère un fichier excel synthétisant les annotations PE telles qu'inscrites dans les fiches de chaque étudiant"""
"""
sT = SeqGenTable() # le fichier excel à générer sT = SeqGenTable() # le fichier excel à générer
# Les etudids des étudiants à afficher, triés par ordre alphabétiques de nom+prénom # Les etudids des étudiants à afficher, triés par ordre alphabétiques de nom+prénom

View File

@ -97,7 +97,7 @@ def print_semestres_description(sems, avec_affichage_debug=False):
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
def calcul_age(born): def calcul_age(born):
"""Calcule l'age à partir de la date de naissance sous forme d'une chaine de caractère 'jj/mm/aaaa'. """Calcule l'age à partir de la date de naissance sous forme d'une chaine de caractère 'jj/mm/aaaa'.
Aucun test de validité sur le format de la date n'est fait. Aucun test de validité sur le format de la date n'est fait.
""" """
if not isinstance(born, str) or born == "": if not isinstance(born, str) or born == "":
@ -122,8 +122,7 @@ def remove_accents(input_unicode_str):
def escape_for_latex(s): def escape_for_latex(s):
"""Protège les caractères pour inclusion dans du source LaTeX """Protège les caractères pour inclusion dans du source LaTeX"""
"""
if not s: if not s:
return "" return ""
conv = { conv = {
@ -162,8 +161,7 @@ def list_directory_filenames(path):
def add_local_file_to_zip(zipfile, ziproot, pathname, path_in_zip): def add_local_file_to_zip(zipfile, ziproot, pathname, path_in_zip):
"""Read pathname server file and add content to zip under path_in_zip """Read pathname server file and add content to zip under path_in_zip"""
"""
rooted_path_in_zip = os.path.join(ziproot, path_in_zip) rooted_path_in_zip = os.path.join(ziproot, path_in_zip)
data = open(pathname).read() data = open(pathname).read()
zipfile.writestr(rooted_path_in_zip, data) zipfile.writestr(rooted_path_in_zip, data)
@ -177,7 +175,7 @@ def add_pe_stuff_to_zip(context, zipfile, ziproot):
Also copy logos Also copy logos
""" """
PE_AUX_DIR = os.path.join(SCO_SRCDIR, "config/doc_poursuites_etudes") PE_AUX_DIR = os.path.join(SCO_SRC_DIR, "config/doc_poursuites_etudes")
distrib_dir = os.path.join(PE_AUX_DIR, "distrib") distrib_dir = os.path.join(PE_AUX_DIR, "distrib")
distrib_pathnames = list_directory_filenames( distrib_pathnames = list_directory_filenames(
distrib_dir distrib_dir

View File

@ -32,10 +32,9 @@
Il suffit d'appeler abs_notify() après chaque ajout d'absence. Il suffit d'appeler abs_notify() après chaque ajout d'absence.
""" """
from email.MIMEMultipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.MIMEText import MIMEText from email.mime.text import MIMEText
from email.Header import Header from email.header import Header
from email import Encoders
from notesdb import * from notesdb import *
from sco_utils import * from sco_utils import *
@ -47,7 +46,7 @@ import sco_formsemestre
def abs_notify(context, etudid, date): def abs_notify(context, etudid, date):
"""Check if notifications are requested and send them """Check if notifications are requested and send them
Considère le nombre d'absence dans le semestre courant Considère le nombre d'absence dans le semestre courant
(s'il n'y a pas de semestre courant, ne fait rien, (s'il n'y a pas de semestre courant, ne fait rien,
car l'etudiant n'est pas inscrit au moment de l'absence!). car l'etudiant n'est pas inscrit au moment de l'absence!).
""" """
@ -64,8 +63,7 @@ def abs_notify(context, etudid, date):
def do_abs_notify(context, sem, etudid, date, nbabs, nbabsjust): def do_abs_notify(context, sem, etudid, date, nbabs, nbabsjust):
"""Given new counts of absences, check if notifications are requested and send them. """Given new counts of absences, check if notifications are requested and send them."""
"""
# prefs fallback to global pref if sem is None: # prefs fallback to global pref if sem is None:
if sem: if sem:
formsemestre_id = sem["formsemestre_id"] formsemestre_id = sem["formsemestre_id"]
@ -131,8 +129,7 @@ def abs_notify_send(
def abs_notify_get_destinations(context, sem, prefs, etudid, date, nbabs, nbabsjust): def abs_notify_get_destinations(context, sem, prefs, etudid, date, nbabs, nbabsjust):
"""Returns set of destination emails to be notified """Returns set of destination emails to be notified"""
"""
formsemestre_id = sem["formsemestre_id"] formsemestre_id = sem["formsemestre_id"]
destinations = [] # list of email address to notify destinations = [] # list of email address to notify
@ -176,8 +173,8 @@ def abs_notify_is_above_threshold(context, etudid, nbabs, nbabsjust, formsemestr
nbabs: nombre d'absence (de tous types, unité de compte = demi-journée) nbabs: nombre d'absence (de tous types, unité de compte = demi-journée)
nbabsjust: nombre d'absences justifiées nbabsjust: nombre d'absences justifiées
(nbabs > abs_notify_abs_threshold) (nbabs > abs_notify_abs_threshold)
(nbabs - nbabs_last_notified) > abs_notify_abs_increment (nbabs - nbabs_last_notified) > abs_notify_abs_increment
""" """
abs_notify_abs_threshold = context.get_preference( abs_notify_abs_threshold = context.get_preference(
@ -282,8 +279,7 @@ def retreive_current_formsemestre(context, etudid, cur_date):
def mod_with_evals_at_date(context, date_abs, etudid): def mod_with_evals_at_date(context, date_abs, etudid):
"""Liste des moduleimpls avec des evaluations a la date indiquée """Liste des moduleimpls avec des evaluations a la date indiquée"""
"""
req = """SELECT m.* FROM notes_moduleimpl m, notes_evaluation e, notes_moduleimpl_inscription i req = """SELECT m.* FROM notes_moduleimpl m, notes_evaluation e, notes_moduleimpl_inscription i
WHERE m.moduleimpl_id = e.moduleimpl_id AND e.moduleimpl_id = i.moduleimpl_id WHERE m.moduleimpl_id = e.moduleimpl_id AND e.moduleimpl_id = i.moduleimpl_id
AND i.etudid = %(etudid)s AND e.jour = %(date_abs)s""" AND i.etudid = %(etudid)s AND e.jour = %(date_abs)s"""

View File

@ -53,7 +53,8 @@ def doSignaleAbsence(
description=None, description=None,
REQUEST=None, REQUEST=None,
): # etudid implied ): # etudid implied
"""Signalement d'une absence""" """Signalement d'une absence
"""
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"] etudid = etud["etudid"]
@ -123,7 +124,8 @@ def doSignaleAbsence(
def SignaleAbsenceEtud(context, REQUEST=None): # etudid implied def SignaleAbsenceEtud(context, REQUEST=None): # etudid implied
"""Formulaire individuel simple de signalement d'une absence""" """Formulaire individuel simple de signalement d'une absence
"""
# brute-force portage from very old dtml code ... # brute-force portage from very old dtml code ...
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"] etudid = etud["etudid"]
@ -160,10 +162,7 @@ def SignaleAbsenceEtud(context, REQUEST=None): # etudid implied
% etud, % etud,
"""<a href="%s/ficheEtud?etudid=%s">""" % (context.ScoURL(), etud["etudid"]), """<a href="%s/ficheEtud?etudid=%s">""" % (context.ScoURL(), etud["etudid"]),
sco_photos.etud_photo_html( sco_photos.etud_photo_html(
context, context, etudid=etudid, title="fiche de " + etud["nomprenom"], REQUEST=REQUEST
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
), ),
"""</a></td></tr></table>""", """</a></td></tr></table>""",
""" """
@ -208,7 +207,8 @@ Raison: <input type="text" name="description" size="42"/> (optionnel)
def doJustifAbsence( def doJustifAbsence(
context, datedebut, datefin, demijournee, description=None, REQUEST=None context, datedebut, datefin, demijournee, description=None, REQUEST=None
): # etudid implied ): # etudid implied
"""Justification d'une absence""" """Justification d'une absence
"""
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"] etudid = etud["etudid"]
description_abs = description description_abs = description
@ -274,7 +274,8 @@ def doJustifAbsence(
def JustifAbsenceEtud(context, REQUEST=None): # etudid implied def JustifAbsenceEtud(context, REQUEST=None): # etudid implied
"""Formulaire individuel simple de justification d'une absence""" """Formulaire individuel simple de justification d'une absence
"""
# brute-force portage from very old dtml code ... # brute-force portage from very old dtml code ...
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"] etudid = etud["etudid"]
@ -289,10 +290,7 @@ def JustifAbsenceEtud(context, REQUEST=None): # etudid implied
% etud, % etud,
"""<a href="%s/ficheEtud?etudid=%s">""" % (context.ScoURL(), etud["etudid"]), """<a href="%s/ficheEtud?etudid=%s">""" % (context.ScoURL(), etud["etudid"]),
sco_photos.etud_photo_html( sco_photos.etud_photo_html(
context, context, etudid=etudid, title="fiche de " + etud["nomprenom"], REQUEST=REQUEST
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
), ),
"""</a></td></tr></table>""", """</a></td></tr></table>""",
""" """
@ -331,7 +329,8 @@ Raison: <input type="text" name="description" size="42"/> (optionnel)
def doAnnuleAbsence( def doAnnuleAbsence(
context, datedebut, datefin, demijournee, REQUEST=None context, datedebut, datefin, demijournee, REQUEST=None
): # etudid implied ): # etudid implied
"""Annulation des absences pour une demi journée""" """Annulation des absences pour une demi journée
"""
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"] etudid = etud["etudid"]
@ -379,7 +378,8 @@ autre absence pour <b>%(nomprenom)s</b></a></li>
def AnnuleAbsenceEtud(context, REQUEST=None): # etudid implied def AnnuleAbsenceEtud(context, REQUEST=None): # etudid implied
"""Formulaire individuel simple d'annulation d'une absence""" """Formulaire individuel simple d'annulation d'une absence
"""
# brute-force portage from very old dtml code ... # brute-force portage from very old dtml code ...
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"] etudid = etud["etudid"]
@ -395,10 +395,7 @@ def AnnuleAbsenceEtud(context, REQUEST=None): # etudid implied
% etud, # " % etud, # "
"""<a href="%s/ficheEtud?etudid=%s">""" % (context.ScoURL(), etud["etudid"]), """<a href="%s/ficheEtud?etudid=%s">""" % (context.ScoURL(), etud["etudid"]),
sco_photos.etud_photo_html( sco_photos.etud_photo_html(
context, context, etudid=etudid, title="fiche de " + etud["nomprenom"], REQUEST=REQUEST
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
), ),
"""</a></td></tr></table>""", """</a></td></tr></table>""",
"""<p>A n'utiliser que suite à une erreur de saisie ou lorsqu'il s'avère que l'étudiant était en fait présent. </p> """<p>A n'utiliser que suite à une erreur de saisie ou lorsqu'il s'avère que l'étudiant était en fait présent. </p>
@ -467,7 +464,8 @@ def AnnuleAbsenceEtud(context, REQUEST=None): # etudid implied
def doAnnuleJustif( def doAnnuleJustif(
context, datedebut0, datefin0, demijournee, REQUEST=None context, datedebut0, datefin0, demijournee, REQUEST=None
): # etudid implied ): # etudid implied
"""Annulation d'une justification""" """Annulation d'une justification
"""
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"] etudid = etud["etudid"]
dates = context.DateRangeISO(datedebut0, datefin0) dates = context.DateRangeISO(datedebut0, datefin0)
@ -571,7 +569,8 @@ def formChoixSemestreGroupe(context, all=False):
def CalAbs(context, REQUEST=None): # etud implied def CalAbs(context, REQUEST=None): # etud implied
"""Calendrier des absences d un etudiant""" """Calendrier des absences d un etudiant
"""
# crude portage from 1999 DTML # crude portage from 1999 DTML
etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0]
etudid = etud["etudid"] etudid = etud["etudid"]
@ -622,10 +621,7 @@ def CalAbs(context, REQUEST=None): # etud implied
context.ScoURL(), context.ScoURL(),
etudid, etudid,
sco_photos.etud_photo_html( sco_photos.etud_photo_html(
context, context, etudid=etudid, title="fiche de " + etud["nomprenom"], REQUEST=REQUEST
etudid=etudid,
title="fiche de " + etud["nomprenom"],
REQUEST=REQUEST,
), ),
), ),
CalHTML, CalHTML,

View File

@ -28,11 +28,11 @@
"""Génération des bulletins de notes """Génération des bulletins de notes
""" """
from email.MIMEMultipart import MIMEMultipart import email
from email.MIMEText import MIMEText from email.mime.multipart import MIMEMultipart
from email.MIMEBase import MIMEBase from email.mime.text import MIMEText
from email.Header import Header from email.mime.base import MIMEBase
from email import Encoders from email.header import Header
import htmlutils, time import htmlutils, time
from reportlab.lib.colors import Color from reportlab.lib.colors import Color
@ -331,7 +331,7 @@ def formsemestre_bulletinetud_dict(
context, ue_status["formsemestre_id"] context, ue_status["formsemestre_id"]
) # > toutes notes ) # > toutes notes
u["modules_capitalized"], junk = _ue_mod_bulletin( u["modules_capitalized"], _ = _ue_mod_bulletin(
context, context,
etudid, etudid,
formsemestre_id, formsemestre_id,
@ -435,15 +435,8 @@ def _ue_mod_bulletin(context, etudid, formsemestre_id, ue_id, modimpls, nt, vers
if mod["mod_moy_txt"][:2] == "NA": if mod["mod_moy_txt"][:2] == "NA":
mod["mod_moy_txt"] = "-" mod["mod_moy_txt"] = "-"
if is_malus: if is_malus:
if mod_moy > 0: mod["mod_moy_txt"] = ""
mod["mod_moy_txt"] = fmt_note(mod_moy) mod["mod_coef_txt"] = ""
mod["mod_coef_txt"] = "Malus"
elif mod_moy < 0:
mod["mod_moy_txt"] = fmt_note(-mod_moy)
mod["mod_coef_txt"] = "Bonus"
else:
mod["mod_moy_txt"] = "-"
mod["mod_coef_txt"] = "-"
else: else:
mod["mod_coef_txt"] = fmt_coef(modimpl["module"]["coefficient"]) mod["mod_coef_txt"] = fmt_coef(modimpl["module"]["coefficient"])
if mod["mod_moy_txt"] != "NI": # ne montre pas les modules 'non inscrit' if mod["mod_moy_txt"] != "NI": # ne montre pas les modules 'non inscrit'
@ -997,7 +990,7 @@ def mail_bulletin(context, formsemestre_id, I, pdfdata, filename, recipient_addr
att = MIMEBase("application", "pdf") att = MIMEBase("application", "pdf")
att.add_header("Content-Disposition", "attachment", filename=filename) att.add_header("Content-Disposition", "attachment", filename=filename)
att.set_payload(pdfdata) att.set_payload(pdfdata)
Encoders.encode_base64(att) email.encoders.encode_base64(att)
msg.attach(att) msg.attach(att)
log("mail bulletin a %s" % msg["To"]) log("mail bulletin a %s" % msg["To"])
context.sendEmail(msg) context.sendEmail(msg)

View File

@ -48,22 +48,10 @@ Balises img: actuellement interdites.
""" """
import traceback, re import traceback, re
import sco_utils as scu
import sco_formsemestre import sco_formsemestre
import sco_pdf from sco_pdf import *
from sco_pdf import Color, Paragraph, Spacer, Table
from sco_pdf import blue, cm, mm
from sco_pdf import SU
import sco_preferences import sco_preferences
from notes_log import log from notes_log import log
from sco_permissions import ScoEtudInscrit
from sco_codes_parcours import (
UE_COLORS,
UE_DEFAULT_COLOR,
UE_ELECTIVE,
UE_SPORT,
UE_STANDARD,
)
import sco_bulletins_generator import sco_bulletins_generator
import sco_bulletins_pdf import sco_bulletins_pdf
import sco_groups import sco_groups
@ -283,6 +271,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
context = self.context context = self.context
P = [] # elems pour générer table avec gen_table (liste de dicts) P = [] # elems pour générer table avec gen_table (liste de dicts)
formsemestre_id = I["formsemestre_id"] formsemestre_id = I["formsemestre_id"]
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
prefs = context.get_preferences(formsemestre_id) prefs = context.get_preferences(formsemestre_id)
# Colonnes à afficher: # Colonnes à afficher:
@ -339,8 +328,8 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
linktmpl = ( linktmpl = (
'<span onclick="toggle_vis_ue(this);" class="toggle_ue">%s</span>&nbsp;' '<span onclick="toggle_vis_ue(this);" class="toggle_ue">%s</span>&nbsp;'
) )
minuslink = linktmpl % scu.icontag("minus_img", border="0", alt="-") minuslink = linktmpl % icontag("minus_img", border="0", alt="-")
pluslink = linktmpl % scu.icontag("plus_img", border="0", alt="+") pluslink = linktmpl % icontag("plus_img", border="0", alt="+")
# 1er ligne titres # 1er ligne titres
t = { t = {
@ -395,7 +384,9 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
P.append(t) P.append(t)
# Rangs dans les partitions: # Rangs dans les partitions:
partitions, _ = sco_groups.get_formsemestre_groups(context, formsemestre_id) partitions, partitions_etud_groups = sco_groups.get_formsemestre_groups(
context, formsemestre_id
)
for partition in partitions: for partition in partitions:
if partition["bul_show_rank"]: if partition["bul_show_rank"]:
partition_id = partition["partition_id"] partition_id = partition["partition_id"]
@ -495,16 +486,16 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
t["_css_row_class"] += " notes_bulletin_row_ue_cur" t["_css_row_class"] += " notes_bulletin_row_ue_cur"
t["_titre_help"] = "(en cours, non prise en compte)" t["_titre_help"] = "(en cours, non prise en compte)"
if prefs["bul_show_minmax"]: if prefs["bul_show_minmax"]:
t["min"] = scu.fmt_note(ue["min"]) t["min"] = fmt_note(ue["min"])
t["max"] = scu.fmt_note(ue["max"]) t["max"] = fmt_note(ue["max"])
if prefs["bul_show_moypromo"]: if prefs["bul_show_moypromo"]:
t["moy"] = scu.fmt_note(ue["moy"]).replace("NA", "-") t["moy"] = fmt_note(ue["moy"]).replace("NA", "-")
# Cas particulier des UE sport (bonus) # Cas particulier des UE sport (bonus)
if ue["type"] == UE_SPORT and not ue_descr: if ue["type"] == UE_SPORT and not ue_descr:
del t["module"] del t["module"]
del t["coef"] del t["coef"]
t["_pdf_style"].append(("SPAN", (colidx["note"], 0), (-1, 0))) t["_pdf_style"].append(("SPAN", (colidx["note"], 0), (-1, 0)))
# t["_module_colspan"] = 3 # non car bug si aucune colonne additionnelle # t["_module_colspan"] = 3 # non car bug si aucune colonne additionnelle
# UE électives # UE électives
if ue["type"] == UE_ELECTIVE: if ue["type"] == UE_ELECTIVE:
t["module"] += " <i>(élective)</i>" t["module"] += " <i>(élective)</i>"
@ -553,7 +544,8 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
rowstyle="", rowstyle="",
hidden=False, hidden=False,
): ):
"""Liste dans la table les descriptions des modules et, si version != short, des évaluations.""" """Liste dans la table les descriptions des modules et, si version != short, des évaluations.
"""
if ue_type == "cur": # UE courante non prise en compte (car capitalisee) if ue_type == "cur": # UE courante non prise en compte (car capitalisee)
pdf_style_bg = [("BACKGROUND", (0, 0), (-1, 0), self.PDF_UE_CUR_BG)] pdf_style_bg = [("BACKGROUND", (0, 0), (-1, 0), self.PDF_UE_CUR_BG)]
else: else:
@ -604,10 +596,10 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
"_pdf_style": pdf_style, "_pdf_style": pdf_style,
} }
if prefs["bul_show_minmax_mod"]: if prefs["bul_show_minmax_mod"]:
t["min"] = scu.fmt_note(mod["stats"]["min"]) t["min"] = fmt_note(mod["stats"]["min"])
t["max"] = scu.fmt_note(mod["stats"]["max"]) t["max"] = fmt_note(mod["stats"]["max"])
if prefs["bul_show_moypromo"]: if prefs["bul_show_moypromo"]:
t["moy"] = scu.fmt_note(mod["stats"]["moy"]).replace("NA", "-") t["moy"] = fmt_note(mod["stats"]["moy"]).replace("NA", "-")
P.append(t) P.append(t)
if self.version != "short": if self.version != "short":
@ -668,15 +660,12 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
t["note"] = "<i>" + e["note_txt"] + "</i>" t["note"] = "<i>" + e["note_txt"] + "</i>"
else: else:
t["_module_colspan"] = 2 t["_module_colspan"] = 2
if prefs["bul_show_minmax_eval"] or prefs["bul_show_moypromo"]: if prefs["bul_show_minmax_eval"]:
etat = sco_evaluations.do_evaluation_etat( etat = sco_evaluations.do_evaluation_etat(
self.context, e["evaluation_id"] self.context, e["evaluation_id"]
) )
if prefs["bul_show_minmax_eval"]: t["min"] = fmt_note(etat["mini"])
t["min"] = scu.fmt_note(etat["mini"]) t["max"] = fmt_note(etat["maxi"])
t["max"] = scu.fmt_note(etat["maxi"])
if prefs["bul_show_moypromo"]:
t["moy"] = scu.fmt_note(etat["moy"])
P.append(t) P.append(t)
nbeval += 1 nbeval += 1
return nbeval return nbeval

View File

@ -116,7 +116,8 @@ CODES_EXPL = {
RAT: "En attente d'un rattrapage", RAT: "En attente d'un rattrapage",
DEF: "Défaillant", DEF: "Défaillant",
} }
# Nota: ces explications sont personnalisables via le fichier de config scodoc_config.py # Nota: ces explications sont personnalisables via le fichier
# de config locale /opt/scodoc/var/scodoc/config/scodoc_local.py
# variable: CONFIG.CODES_EXP # variable: CONFIG.CODES_EXP
CODES_SEM_VALIDES = {ADM: True, ADC: True, ADJ: True} # semestre validé CODES_SEM_VALIDES = {ADM: True, ADC: True, ADJ: True} # semestre validé

View File

@ -315,7 +315,7 @@ def do_moduleimpl_moyennes(context, nt, mod):
notes.append(0.0) notes.append(0.0)
coefs.append(0.0) coefs.append(0.0)
coefs_mask.append(0) coefs_mask.append(0)
if nb_notes > 0 or formula_use_abs: if nb_notes > 0:
user_moy = compute_user_formula( user_moy = compute_user_formula(
context, context,
sem, sem,

109
sco_config.py Normal file
View File

@ -0,0 +1,109 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Configuration de ScoDoc (version 2020)
NE PAS MODIFIER localement ce fichier !
mais éditer /opt/scodoc/var/scodoc/config/scodoc_local.py
"""
from attrdict import AttrDict
import bonus_sport
CONFIG = AttrDict()
# set to 1 if you want to require INE:
CONFIG.always_require_ine = 0
# The base URL, use only if you are behind a proxy
# eg "https://scodoc.example.net/ScoDoc"
CONFIG.ABSOLUTE_URL = ""
# -----------------------------------------------------
# -------------- Documents PDF
# -----------------------------------------------------
# Taille du l'image logo: largeur/hauteur (ne pas oublier le . !!!)
# W/H XXX provisoire: utilisera PIL pour connaitre la taille de l'image
CONFIG.LOGO_FOOTER_ASPECT = 326 / 96.0
# Taille dans le document en millimetres
CONFIG.LOGO_FOOTER_HEIGHT = 10
# Proportions logo (donné ici pour IUTV)
CONFIG.LOGO_HEADER_ASPECT = 549 / 346.0
# Taille verticale dans le document en millimetres
CONFIG.LOGO_HEADER_HEIGHT = 28
# Pied de page PDF : un format Python, %(xxx)s est remplacé par la variable xxx.
# Les variables définies sont:
# day : Day of the month as a decimal number [01,31]
# month : Month as a decimal number [01,12].
# year : Year without century as a decimal number [00,99].
# Year : Year with century as a decimal number.
# hour : Hour (24-hour clock) as a decimal number [00,23].
# minute: Minute as a decimal number [00,59].
#
# server_url: URL du serveur ScoDoc
# scodoc_name: le nom du logiciel (ScoDoc actuellement, voir VERSION.py)
CONFIG.DEFAULT_PDF_FOOTER_TEMPLATE = "Edité par %(scodoc_name)s le %(day)s/%(month)s/%(year)s à %(hour)sh%(minute)s sur %(server_url)s"
#
# ------------- Calcul bonus modules optionnels (sport, culture...) -------------
#
CONFIG.compute_bonus = bonus_sport.bonus_iutv
# Mettre "bonus_demo" pour logguer des informations utiles au developpement...
# ------------- Capitalisation des UEs -------------
# Deux écoles:
# - règle "DUT": capitalisation des UE obtenues avec moyenne UE >= 10 ET de toutes les UE
# des semestres validés (ADM, ADC, AJ). (conforme à l'arrêté d'août 2005)
#
# - règle "LMD": capitalisation uniquement des UE avec moy. > 10
# Si vrai, capitalise toutes les UE des semestres validés (règle "DUT").
# CONFIG.CAPITALIZE_ALL_UES = True
# -----------------------------------------------------
# -------------- Personnalisation des pages
# -----------------------------------------------------
# Nom (chemin complet) d'un fichier .html à inclure juste après le <body>
# le <body> des pages ScoDoc
CONFIG.CUSTOM_HTML_HEADER = ""
# Fichier html a inclure en fin des pages (juste avant le </body>)
CONFIG.CUSTOM_HTML_FOOTER = ""
# Fichier .html à inclure dans la pages connexion/déconnexion (accueil)
# si on veut que ce soit différent (par défaut la même chose)
CONFIG.CUSTOM_HTML_HEADER_CNX = CONFIG.CUSTOM_HTML_HEADER
CONFIG.CUSTOM_HTML_FOOTER_CNX = CONFIG.CUSTOM_HTML_FOOTER
# -----------------------------------------------------
# -------------- Noms de Lycées
# -----------------------------------------------------
# Fichier de correspondance codelycee -> noms
# (chemin relatif au repertoire d'install des sources)
CONFIG.ETABL_FILENAME = "config/etablissements.csv"
# ----------------------------------------------------
# -------------- Divers:
# ----------------------------------------------------
# True for UCAC (étudiants camerounais sans prénoms)
CONFIG.ALLOW_NULL_PRENOM = False
# Taille max des fichiers archive etudiants (en octets)
# CONFIG.ETUD_MAX_FILE_SIZE = 10 * 1024 * 1024
# Si pas de photo et portail, publie l'url (était vrai jusqu'en oct 2016)
CONFIG.PUBLISH_PORTAL_PHOTO_URL = False
# Si > 0: longueur minimale requise des nouveaux mots de passe
# (le test cracklib.FascistCheck s'appliquera dans tous les cas)
CONFIG.MIN_PASSWORD_LENGTH = 0
# Ce dictionnaire est fusionné à celui de sco_codes_parcours
# pour définir les codes jury et explications associées
CONFIG.CODES_EXPL = {
# AJ : 'Ajourné (échec)',
}

44
sco_config_load.py Normal file
View File

@ -0,0 +1,44 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Chargement de la configuration locale
"""
import os
import sys
import sco_utils
from sco_utils import log, SCODOC_CFG_DIR
import sco_config
# scodoc_local defines a CONFIG object
# here we check if there is a local config file
def load_local_configuration():
"""Load local configuration file (if exists)
and merge it with CONFIG.
"""
# this path should be synced with upgrade.sh
LOCAL_CONFIG_FILENAME = os.path.join(SCODOC_CFG_DIR, "scodoc_local.py")
LOCAL_CONFIG = None
if os.path.exists(LOCAL_CONFIG_FILENAME):
if not SCODOC_CFG_DIR in sys.path:
sys.path.insert(1, SCODOC_CFG_DIR)
try:
from scodoc_local import CONFIG as LOCAL_CONFIG
log("imported %s" % LOCAL_CONFIG_FILENAME)
except ImportError:
log("Error: can't import %s" % LOCAL_CONFIG_FILENAME)
del sys.path[1]
if LOCAL_CONFIG is None:
return
# Now merges local config in our CONFIG
for x in [x for x in dir(LOCAL_CONFIG) if x[0] != "_"]:
v = getattr(LOCAL_CONFIG, x)
if not v in sco_config.CONFIG:
log("Warning: local config setting unused parameter %s (skipped)" % x)
else:
if v != sco_config.CONFIG[x]:
log("Setting parameter %s from %s" % (x, LOCAL_CONFIG_FILENAME))
sco_config.CONFIG[x] = v

View File

@ -28,14 +28,12 @@
""" """
Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudiant Rapport (table) avec dernier semestre fréquenté et débouché de chaque étudiant
""" """
from types import StringType
import safehtml import safehtml
import sco_utils as scu from notesdb import *
import notesdb from sco_utils import *
from notes_log import log from notes_log import log
import VERSION
from sco_exceptions import AccessDenied
from scolog import logdb from scolog import logdb
from gen_tables import GenTable from gen_tables import GenTable
import sco_formsemestre import sco_formsemestre
@ -44,7 +42,8 @@ import sco_tag_module
def report_debouche_date(context, start_year=None, format="html", REQUEST=None): def report_debouche_date(context, start_year=None, format="html", REQUEST=None):
"""Rapport (table) pour les débouchés des étudiants sortis à partir de l'année indiquée.""" """Rapport (table) pour les débouchés des étudiants sortis à partir de l'année indiquée.
"""
if not start_year: if not start_year:
return report_debouche_ask_date(context, REQUEST=REQUEST) return report_debouche_ask_date(context, REQUEST=REQUEST)
if format == "xls": if format == "xls":
@ -55,8 +54,8 @@ def report_debouche_date(context, start_year=None, format="html", REQUEST=None):
etudids = get_etudids_with_debouche(context, start_year) etudids = get_etudids_with_debouche(context, start_year)
tab = table_debouche_etudids(context, etudids, keep_numeric=keep_numeric) tab = table_debouche_etudids(context, etudids, keep_numeric=keep_numeric)
tab.filename = scu.make_filename("debouche_scodoc_%s" % start_year) tab.filename = make_filename("debouche_scodoc_%s" % start_year)
tab.origin = "Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "" tab.origin = "Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + ""
tab.caption = "Récapitulatif débouchés à partir du 1/1/%s." % start_year tab.caption = "Récapitulatif débouchés à partir du 1/1/%s." % start_year
tab.base_url = "%s?start_year=%s" % (REQUEST.URL0, start_year) tab.base_url = "%s?start_year=%s" % (REQUEST.URL0, start_year)
return tab.make_page( return tab.make_page(
@ -78,7 +77,7 @@ def get_etudids_with_debouche(context, start_year):
start_date = str(start_year) + "-01-01" start_date = str(start_year) + "-01-01"
# Recupere tous les etudid avec un debouché renseigné et une inscription dans un semestre # Recupere tous les etudid avec un debouché renseigné et une inscription dans un semestre
# posterieur à la date de depart: # posterieur à la date de depart:
# r = notesdb.SimpleDictFetch(context, # r = SimpleDictFetch(context,
# """SELECT DISTINCT i.etudid # """SELECT DISTINCT i.etudid
# FROM notes_formsemestre_inscription i, admissions adm, notes_formsemestre s # FROM notes_formsemestre_inscription i, admissions adm, notes_formsemestre s
# WHERE adm.debouche is not NULL # WHERE adm.debouche is not NULL
@ -87,7 +86,7 @@ def get_etudids_with_debouche(context, start_year):
# """, # """,
# {'start_date' : start_date }) # {'start_date' : start_date })
r = notesdb.SimpleDictFetch( r = SimpleDictFetch(
context, context,
"""SELECT DISTINCT i.etudid """SELECT DISTINCT i.etudid
FROM notes_formsemestre_inscription i, notes_formsemestre s, itemsuivi it FROM notes_formsemestre_inscription i, notes_formsemestre s, itemsuivi it
@ -101,7 +100,8 @@ def get_etudids_with_debouche(context, start_year):
def table_debouche_etudids(context, etudids, keep_numeric=True): def table_debouche_etudids(context, etudids, keep_numeric=True):
"""Rapport pour ces etudiants""" """Rapport pour ces etudiants
"""
L = [] L = []
for etudid in etudids: for etudid in etudids:
etud = context.getEtudInfo(filled=1, etudid=etudid)[0] etud = context.getEtudInfo(filled=1, etudid=etudid)[0]
@ -122,7 +122,7 @@ def table_debouche_etudids(context, etudids, keep_numeric=True):
"_prenom_target": "ficheEtud?etudid=" + etud["etudid"], "_prenom_target": "ficheEtud?etudid=" + etud["etudid"],
"_nom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]), "_nom_td_attrs": 'id="%s" class="etudinfo"' % (etud["etudid"]),
# 'debouche' : etud['debouche'], # 'debouche' : etud['debouche'],
"moy": scu.fmt_note(nt.get_etud_moy_gen(etudid), keep_numeric=keep_numeric), "moy": fmt_note(nt.get_etud_moy_gen(etudid), keep_numeric=keep_numeric),
"rang": nt.get_etud_rang(etudid), "rang": nt.get_etud_rang(etudid),
"effectif": len(nt.T), "effectif": len(nt.T),
"semestre_id": last_sem["semestre_id"], "semestre_id": last_sem["semestre_id"],
@ -189,7 +189,8 @@ def table_debouche_etudids(context, etudids, keep_numeric=True):
def report_debouche_ask_date(context, REQUEST=None): def report_debouche_ask_date(context, REQUEST=None):
"""Formulaire demande date départ""" """Formulaire demande date départ
"""
return ( return (
context.sco_header(REQUEST) context.sco_header(REQUEST)
+ """<form method="GET"> + """<form method="GET">
@ -222,17 +223,14 @@ def report_debouche_ask_date(context, REQUEST=None):
# admission_edit(cnx, adm) # admission_edit(cnx, adm)
_itemsuiviEditor = notesdb.EditableTable( _itemsuiviEditor = EditableTable(
"itemsuivi", "itemsuivi",
"itemsuivi_id", "itemsuivi_id",
("itemsuivi_id", "etudid", "item_date", "situation"), ("itemsuivi_id", "etudid", "item_date", "situation"),
sortkey="item_date desc", sortkey="item_date desc",
convert_null_outputs_to_empty=True, convert_null_outputs_to_empty=True,
output_formators={ output_formators={"situation": safehtml.HTML2SafeHTML, "item_date": DateISOtoDMY},
"situation": safehtml.HTML2SafeHTML, input_formators={"item_date": DateDMYtoISO},
"item_date": notesdb.DateISOtoDMY,
},
input_formators={"item_date": notesdb.DateDMYtoISO},
) )
_itemsuivi_create = _itemsuiviEditor.create _itemsuivi_create = _itemsuiviEditor.create
@ -242,7 +240,8 @@ _itemsuivi_edit = _itemsuiviEditor.edit
class ItemSuiviTag(sco_tag_module.ScoTag): class ItemSuiviTag(sco_tag_module.ScoTag):
"""Les tags sur les items""" """Les tags sur les items
"""
tag_table = "itemsuivi_tags" # table (tag_id, title) tag_table = "itemsuivi_tags" # table (tag_id, title)
assoc_table = "itemsuivi_tags_assoc" # table (tag_id, object_id) assoc_table = "itemsuivi_tags_assoc" # table (tag_id, object_id)
@ -260,7 +259,8 @@ def itemsuivi_get(cnx, itemsuivi_id, ignore_errors=False):
def itemsuivi_suppress(context, itemsuivi_id, REQUEST=None): def itemsuivi_suppress(context, itemsuivi_id, REQUEST=None):
"""Suppression d'un item""" """Suppression d'un item
"""
if not context.can_edit_suivi(REQUEST): if not context.can_edit_suivi(REQUEST):
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
cnx = context.GetDBConnexion() cnx = context.GetDBConnexion()
@ -285,7 +285,7 @@ def itemsuivi_create(
log("created itemsuivi %s for %s" % (itemsuivi_id, etudid)) log("created itemsuivi %s for %s" % (itemsuivi_id, etudid))
item = itemsuivi_get(cnx, itemsuivi_id) item = itemsuivi_get(cnx, itemsuivi_id)
if format == "json": if format == "json":
return scu.sendJSON(REQUEST, item) return sendJSON(REQUEST, item)
return item return item
@ -313,7 +313,7 @@ def itemsuivi_set_situation(context, object, value, REQUEST=None):
item = itemsuivi_get(cnx, itemsuivi_id) item = itemsuivi_get(cnx, itemsuivi_id)
item["situation"] = situation item["situation"] = situation
_itemsuivi_edit(cnx, item) _itemsuivi_edit(cnx, item)
return situation or scu.IT_SITUATION_MISSING_STR return situation or IT_SITUATION_MISSING_STR
def itemsuivi_list_etud(context, etudid, format=None, REQUEST=None): def itemsuivi_list_etud(context, etudid, format=None, REQUEST=None):
@ -323,13 +323,13 @@ def itemsuivi_list_etud(context, etudid, format=None, REQUEST=None):
for it in items: for it in items:
it["tags"] = ", ".join(itemsuivi_tag_list(context, it["itemsuivi_id"])) it["tags"] = ", ".join(itemsuivi_tag_list(context, it["itemsuivi_id"]))
if format == "json": if format == "json":
return scu.sendJSON(REQUEST, items) return sendJSON(REQUEST, items)
return items return items
def itemsuivi_tag_list(context, itemsuivi_id): def itemsuivi_tag_list(context, itemsuivi_id):
"""les noms de tags associés à cet item""" """les noms de tags associés à cet item"""
r = notesdb.SimpleDictFetch( r = SimpleDictFetch(
context, context,
"""SELECT t.title """SELECT t.title
FROM itemsuivi_tags_assoc a, itemsuivi_tags t FROM itemsuivi_tags_assoc a, itemsuivi_tags t
@ -344,17 +344,17 @@ def itemsuivi_tag_list(context, itemsuivi_id):
def itemsuivi_tag_search(context, term, REQUEST=None): def itemsuivi_tag_search(context, term, REQUEST=None):
"""List all used tag names (for auto-completion)""" """List all used tag names (for auto-completion)"""
# restrict charset to avoid injections # restrict charset to avoid injections
if not scu.ALPHANUM_EXP.match(term.decode(scu.SCO_ENCODING)): if not ALPHANUM_EXP.match(term.decode(SCO_ENCODING)):
data = [] data = []
else: else:
r = notesdb.SimpleDictFetch( r = SimpleDictFetch(
context, context,
"SELECT title FROM itemsuivi_tags WHERE title LIKE %(term)s", "SELECT title FROM itemsuivi_tags WHERE title LIKE %(term)s",
{"term": term + "%"}, {"term": term + "%"},
) )
data = [x["title"] for x in r] data = [x["title"] for x in r]
return scu.sendJSON(REQUEST, data) return sendJSON(REQUEST, data)
def itemsuivi_tag_set(context, itemsuivi_id="", taglist=[], REQUEST=None): def itemsuivi_tag_set(context, itemsuivi_id="", taglist=[], REQUEST=None):
@ -372,7 +372,7 @@ def itemsuivi_tag_set(context, itemsuivi_id="", taglist=[], REQUEST=None):
# log('itemsuivi_tag_set: itemsuivi_id=%s taglist=%s' % (itemsuivi_id, taglist)) # log('itemsuivi_tag_set: itemsuivi_id=%s taglist=%s' % (itemsuivi_id, taglist))
# Sanity check: # Sanity check:
cnx = context.GetDBConnexion() cnx = context.GetDBConnexion()
_ = itemsuivi_get(cnx, itemsuivi_id) item = itemsuivi_get(cnx, itemsuivi_id)
newtags = set(taglist) newtags = set(taglist)
oldtags = set(itemsuivi_tag_list(context, itemsuivi_id)) oldtags = set(itemsuivi_tag_list(context, itemsuivi_id))

View File

@ -50,13 +50,13 @@ pg_dump SCORT | psql ANORT
import fcntl import fcntl
import subprocess import subprocess
import requests import requests
from email.MIMEMultipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.MIMEText import MIMEText from email.mime.text import MIMEText
from email.MIMEBase import MIMEBase from email.mime.base import MIMEBase
from email.Header import Header from email.header import Header
from email import Encoders
from notesdb import * from notesdb import *
import sco_utils
from sco_utils import * from sco_utils import *
from notes_log import log from notes_log import log
@ -64,8 +64,7 @@ SCO_DUMP_LOCK = "/tmp/scodump.lock"
def sco_dump_and_send_db(context, REQUEST=None): def sco_dump_and_send_db(context, REQUEST=None):
"""Dump base de données du département courant et l'envoie anonymisée pour debug """Dump base de données du département courant et l'envoie anonymisée pour debug"""
"""
H = [context.sco_header(REQUEST, page_title="Assistance technique")] H = [context.sco_header(REQUEST, page_title="Assistance technique")]
# get currect (dept) DB name: # get currect (dept) DB name:
cursor = SimpleQuery(context, "SELECT current_database()", {}) cursor = SimpleQuery(context, "SELECT current_database()", {})
@ -150,9 +149,8 @@ def _duplicate_db(db_name, ano_db_name):
def _anonymize_db(ano_db_name): def _anonymize_db(ano_db_name):
"""Anonymize a departement database """Anonymize a departement database"""
""" cmd = os.path.join(SCO_TOOLS_DIR, "anonymize_db.py")
cmd = os.path.join(SCO_CONFIG_DIR, "anonymize_db.py")
log("_anonymize_db: {}".format(cmd)) log("_anonymize_db: {}".format(cmd))
try: try:
out = subprocess.check_output([cmd, ano_db_name]) out = subprocess.check_output([cmd, ano_db_name])
@ -171,8 +169,7 @@ def _get_scodoc_serial(context):
def _send_db(context, REQUEST, ano_db_name): def _send_db(context, REQUEST, ano_db_name):
"""Dump this (anonymized) database and send it to tech support """Dump this (anonymized) database and send it to tech support"""
"""
log("dumping anonymized database {}".format(ano_db_name)) log("dumping anonymized database {}".format(ano_db_name))
try: try:
data = subprocess.check_output("pg_dump {} | gzip".format(ano_db_name), shell=1) data = subprocess.check_output("pg_dump {} | gzip".format(ano_db_name), shell=1)
@ -195,7 +192,7 @@ def _send_db(context, REQUEST, ano_db_name):
"nomcomplet" "nomcomplet"
], ],
"sco_version": SCOVERSION, "sco_version": SCOVERSION,
"sco_subversion": get_svn_version(SCO_CONFIG_DIR), "sco_fullversion": sco_utils.get_scodoc_version(),
}, },
) )
return r return r

View File

@ -27,23 +27,15 @@
"""Evaluations """Evaluations
""" """
import time
import urllib
import operator
import datetime
from notes_log import log, logCallStack from notes_log import log, logCallStack
import sco_utils as scu from sco_utils import *
from notesdb import ScoDocCursor from notesdb import *
from sco_exceptions import AccessDenied, ScoValueError
import VERSION
from gen_tables import GenTable from gen_tables import GenTable
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 ZAbsences
import sco_evaluations
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# #
@ -55,7 +47,7 @@ def notes_moyenne_median_mini_maxi(notes):
notes = [ notes = [
x x
for x in notes for x in notes
if (x != None) and (x != scu.NOTES_NEUTRALISE) and (x != scu.NOTES_ATTENTE) if (x != None) and (x != NOTES_NEUTRALISE) and (x != NOTES_ATTENTE)
] ]
n = len(notes) n = len(notes)
if not n: if not n:
@ -144,8 +136,8 @@ def do_evaluation_etat(
NotesDB = context._notes_getall(evaluation_id) # { etudid : value } NotesDB = context._notes_getall(evaluation_id) # { etudid : value }
notes = [x["value"] for x in NotesDB.values()] notes = [x["value"] for x in NotesDB.values()]
nb_abs = len([x for x in notes if x is None]) nb_abs = len([x for x in notes if x is None])
nb_neutre = len([x for x in notes if x == scu.NOTES_NEUTRALISE]) nb_neutre = len([x for x in notes if x == NOTES_NEUTRALISE])
nb_att = len([x for x in notes if x == scu.NOTES_ATTENTE]) nb_att = len([x for x in notes if x == NOTES_ATTENTE])
moy_num, median_num, mini_num, maxi_num = notes_moyenne_median_mini_maxi(notes) moy_num, median_num, mini_num, maxi_num = notes_moyenne_median_mini_maxi(notes)
if moy_num is None: if moy_num is None:
median, moy = "", "" median, moy = "", ""
@ -153,10 +145,10 @@ def do_evaluation_etat(
mini, maxi = "", "" mini, maxi = "", ""
mini_num, maxi_num = None, None mini_num, maxi_num = None, None
else: else:
median = scu.fmt_note(median_num) median = fmt_note(median_num)
moy = scu.fmt_note(moy_num) moy = fmt_note(moy_num)
mini = scu.fmt_note(mini_num) mini = fmt_note(mini_num)
maxi = scu.fmt_note(maxi_num) maxi = fmt_note(maxi_num)
# cherche date derniere modif note # cherche date derniere modif note
if len(NotesDB): if len(NotesDB):
t = [x["date"] for x in NotesDB.values()] t = [x["date"] for x in NotesDB.values()]
@ -167,7 +159,7 @@ def do_evaluation_etat(
E = context.do_evaluation_list(args={"evaluation_id": evaluation_id})[0] E = context.do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
Mod = context.do_module_list(args={"module_id": M["module_id"]})[0] Mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
is_malus = Mod["module_type"] == scu.MODULE_MALUS # True si module de malus is_malus = Mod["module_type"] == MODULE_MALUS # True si module de malus
formsemestre_id = M["formsemestre_id"] formsemestre_id = M["formsemestre_id"]
# Si partition_id is None, prend 'all' ou bien la premiere: # Si partition_id is None, prend 'all' ou bien la premiere:
if partition_id is None: if partition_id is None:
@ -194,8 +186,8 @@ def do_evaluation_etat(
# On considere une note "manquante" lorsqu'elle n'existe pas # On considere une note "manquante" lorsqu'elle n'existe pas
# ou qu'elle est en attente (ATT) # ou qu'elle est en attente (ATT)
GrNbMissing = scu.DictDefault() # group_id : nb notes manquantes GrNbMissing = DictDefault() # group_id : nb notes manquantes
GrNotes = scu.DictDefault(defaultvalue=[]) # group_id: liste notes valides GrNotes = DictDefault(defaultvalue=[]) # group_id: liste notes valides
TotalNbMissing = 0 TotalNbMissing = 0
TotalNbAtt = 0 TotalNbAtt = 0
groups = {} # group_id : group groups = {} # group_id : group
@ -209,14 +201,14 @@ def do_evaluation_etat(
isMissing = False isMissing = False
if NotesDB.has_key(i["etudid"]): if NotesDB.has_key(i["etudid"]):
val = NotesDB[i["etudid"]]["value"] val = NotesDB[i["etudid"]]["value"]
if val == scu.NOTES_ATTENTE: if val == NOTES_ATTENTE:
isMissing = True isMissing = True
TotalNbAtt += 1 TotalNbAtt += 1
if group: if group:
GrNotes[group["group_id"]].append(val) GrNotes[group["group_id"]].append(val)
else: else:
if group: if group:
_ = GrNotes[group["group_id"]] # create group junk = GrNotes[group["group_id"]] # create group
isMissing = True isMissing = True
if isMissing: if isMissing:
TotalNbMissing += 1 TotalNbMissing += 1
@ -227,7 +219,7 @@ def do_evaluation_etat(
gr_incomplets.sort() gr_incomplets.sort()
if ( if (
(TotalNbMissing > 0) (TotalNbMissing > 0)
and (E["evaluation_type"] != scu.EVALUATION_RATTRAPAGE) and (E["evaluation_type"] != EVALUATION_RATTRAPAGE)
and not is_malus and not is_malus
): ):
complete = False complete = False
@ -252,15 +244,15 @@ def do_evaluation_etat(
"group_id": group_id, "group_id": group_id,
"group_name": groups[group_id]["group_name"], "group_name": groups[group_id]["group_name"],
"gr_moy_num": gr_moy, "gr_moy_num": gr_moy,
"gr_moy": scu.fmt_note(gr_moy), "gr_moy": fmt_note(gr_moy),
"gr_median_num": gr_median, "gr_median_num": gr_median,
"gr_median": scu.fmt_note(gr_median), "gr_median": fmt_note(gr_median),
"gr_mini": scu.fmt_note(gr_mini), "gr_mini": fmt_note(gr_mini),
"gr_maxi": scu.fmt_note(gr_maxi), "gr_maxi": fmt_note(gr_maxi),
"gr_mini_num": gr_mini, "gr_mini": gr_mini,
"gr_maxi_num": gr_maxi, "gr_maxi": gr_maxi,
"gr_nb_notes": len(notes), "gr_nb_notes": len(notes),
"gr_nb_att": len([x for x in notes if x == scu.NOTES_ATTENTE]), "gr_nb_att": len([x for x in notes if x == NOTES_ATTENTE]),
} }
) )
gr_moyennes.sort(key=operator.itemgetter("group_name")) gr_moyennes.sort(key=operator.itemgetter("group_name"))
@ -345,7 +337,7 @@ def do_evaluation_list_in_sem(context, formsemestre_id):
'visibulletin': 1} ] 'visibulletin': 1} ]
""" """
req = "select E.* from notes_evaluation E, notes_moduleimpl MI where MI.formsemestre_id = %(formsemestre_id)s and MI.moduleimpl_id = E.moduleimpl_id order by moduleimpl_id, numero desc, jour desc, heure_debut desc" req = "select E.* from notes_evaluation E, notes_moduleimpl MI where MI.formsemestre_id = %(formsemestre_id)s and MI.moduleimpl_id = E.moduleimpl_id"
cnx = context.GetDBConnexion() cnx = context.GetDBConnexion()
cursor = cnx.cursor(cursor_factory=ScoDocCursor) cursor = cnx.cursor(cursor_factory=ScoDocCursor)
cursor.execute(req, {"formsemestre_id": formsemestre_id}) cursor.execute(req, {"formsemestre_id": formsemestre_id})
@ -388,7 +380,7 @@ def _eval_etat(evals):
nb_evals_en_cours += 1 nb_evals_en_cours += 1
dates.append(e["etat"]["last_modif"]) dates.append(e["etat"]["last_modif"])
dates = scu.sort_dates(dates) dates = sort_dates(dates)
if len(dates): if len(dates):
last_modif = dates[-1] # date de derniere modif d'une note dans un module last_modif = dates[-1] # date de derniere modif d'une note dans un module
@ -519,18 +511,18 @@ def evaluation_date_first_completion(context, evaluation_id):
if not etat["evalcomplete"]: if not etat["evalcomplete"]:
return None return None
# XXX inachevé ou à revoir ? E = context.do_evaluation_list(args={"evaluation_id": evaluation_id})[0]
M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0]
formsemestre_id = M["formsemestre_id"]
# Il faut considerer les inscriptions au semestre # Il faut considerer les inscriptions au semestre
# (pour avoir l'etat et le groupe) et aussi les inscriptions # (pour avoir l'etat et le groupe) et aussi les inscriptions
# au module (pour gerer les modules optionnels correctement) # au module (pour gerer les modules optionnels correctement)
# E = context.do_evaluation_list(args={"evaluation_id": evaluation_id})[0] insem = context.do_formsemestre_inscription_listinscrits(formsemestre_id)
# M = context.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] insmod = context.do_moduleimpl_inscription_list(moduleimpl_id=E["moduleimpl_id"])
# formsemestre_id = M["formsemestre_id"] insmodset = set([x["etudid"] for x in insmod])
# insem = context.do_formsemestre_inscription_listinscrits(formsemestre_id)
# insmod = context.do_moduleimpl_inscription_list(moduleimpl_id=E["moduleimpl_id"])
# insmodset = set([x["etudid"] for x in insmod])
# retire de insem ceux qui ne sont pas inscrits au module # retire de insem ceux qui ne sont pas inscrits au module
# ins = [i for i in insem if i["etudid"] in insmodset] ins = [i for i in insem if i["etudid"] in insmodset]
notes = context._notes_getall(evaluation_id, filter_suppressed=False).values() notes = context._notes_getall(evaluation_id, filter_suppressed=False).values()
notes_log = context._notes_getall( notes_log = context._notes_getall(
@ -568,8 +560,8 @@ def formsemestre_evaluations_delai_correction(
for e in evals: for e in evals:
M = context.do_moduleimpl_list(moduleimpl_id=e["moduleimpl_id"])[0] M = context.do_moduleimpl_list(moduleimpl_id=e["moduleimpl_id"])[0]
Mod = context.do_module_list(args={"module_id": M["module_id"]})[0] Mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
if (e["evaluation_type"] != scu.EVALUATION_NORMALE) or ( if (e["evaluation_type"] != EVALUATION_NORMALE) or (
Mod["module_type"] == scu.MODULE_MALUS Mod["module_type"] == MODULE_MALUS
): ):
continue continue
e["date_first_complete"] = evaluation_date_first_completion( e["date_first_complete"] = evaluation_date_first_completion(
@ -620,8 +612,8 @@ def formsemestre_evaluations_delai_correction(
caption="Correction des évaluations du semestre", caption="Correction des évaluations du semestre",
preferences=context.get_preferences(formsemestre_id), preferences=context.get_preferences(formsemestre_id),
base_url="%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id), base_url="%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id),
origin="Généré par %s le " % VERSION.SCONAME + scu.timedate_human_repr() + "", origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "",
filename=scu.make_filename("evaluations_delais_" + sem["titreannee"]), filename=make_filename("evaluations_delais_" + sem["titreannee"]),
) )
return tab.make_page(context, format=format, REQUEST=REQUEST) return tab.make_page(context, format=format, REQUEST=REQUEST)
@ -735,6 +727,7 @@ def evaluation_describe(context, evaluation_id="", edit_in_place=True, REQUEST=N
M = context.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] M = context.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
Mod = context.do_module_list(args={"module_id": M["module_id"]})[0] Mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
formsemestre_id = M["formsemestre_id"] formsemestre_id = M["formsemestre_id"]
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
u = context.Users.user_info(M["responsable_id"]) u = context.Users.user_info(M["responsable_id"])
resp = u["prenomnom"] resp = u["prenomnom"]
nomcomplet = u["nomcomplet"] nomcomplet = u["nomcomplet"]
@ -754,13 +747,13 @@ def evaluation_describe(context, evaluation_id="", edit_in_place=True, REQUEST=N
etit = E["description"] or "" etit = E["description"] or ""
if etit: if etit:
etit = ' "' + etit + '"' etit = ' "' + etit + '"'
if Mod["module_type"] == scu.MODULE_MALUS: if Mod["module_type"] == MODULE_MALUS:
etit += ' <span class="eval_malus">(points de malus)</span>' etit += ' <span class="eval_malus">(points de malus)</span>'
H = [ H = [
'<span class="eval_title">Evaluation%s</span><p><b>Module : %s</b></p>' '<span class="eval_title">Evaluation%s</span><p><b>Module : %s</b></p>'
% (etit, mod_descr) % (etit, mod_descr)
] ]
if Mod["module_type"] == scu.MODULE_MALUS: if Mod["module_type"] == MODULE_MALUS:
# Indique l'UE # Indique l'UE
ue = context.do_ue_list(args={"ue_id": Mod["ue_id"]})[0] ue = context.do_ue_list(args={"ue_id": Mod["ue_id"]})[0]
H.append("<p><b>UE : %(acronyme)s</b></p>" % ue) H.append("<p><b>UE : %(acronyme)s</b></p>" % ue)
@ -811,9 +804,9 @@ def evaluation_create_form(
moduleimpl_id = the_eval["moduleimpl_id"] moduleimpl_id = the_eval["moduleimpl_id"]
# #
M = context.do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0] M = context.do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0]
is_malus = M["module"]["module_type"] == scu.MODULE_MALUS # True si module de malus is_malus = M["module"]["module_type"] == MODULE_MALUS # True si module de malus
formsemestre_id = M["formsemestre_id"] formsemestre_id = M["formsemestre_id"]
min_note_max = scu.NOTES_PRECISION # le plus petit bareme possible min_note_max = NOTES_PRECISION # le plus petit bareme possible
if not readonly: if not readonly:
try: try:
context._evaluation_check_write_access(REQUEST, moduleimpl_id=moduleimpl_id) context._evaluation_check_write_access(REQUEST, moduleimpl_id=moduleimpl_id)
@ -860,16 +853,17 @@ def evaluation_create_form(
# Note maximale actuelle dans cette eval ? # Note maximale actuelle dans cette eval ?
etat = do_evaluation_etat(context, evaluation_id) etat = do_evaluation_etat(context, evaluation_id)
if etat["maxi_num"] is not None: if etat["maxi_num"] is not None:
min_note_max = max(scu.NOTES_PRECISION, etat["maxi_num"]) min_note_max = max(NOTES_PRECISION, etat["maxi_num"])
else: else:
min_note_max = scu.NOTES_PRECISION min_note_max = NOTES_PRECISION
# #
if min_note_max > scu.NOTES_PRECISION: if min_note_max > NOTES_PRECISION:
min_note_max_str = scu.fmt_note(min_note_max) min_note_max_str = fmt_note(min_note_max)
else: else:
min_note_max_str = "0" min_note_max_str = "0"
# #
Mod = context.do_module_list(args={"module_id": M["module_id"]})[0] Mod = context.do_module_list(args={"module_id": M["module_id"]})[0]
sem = sco_formsemestre.get_formsemestre(context, M["formsemestre_id"])
# #
help = """<div class="help"><p class="help"> help = """<div class="help"><p class="help">
Le coefficient d'une évaluation n'est utilisé que pour pondérer les évaluations au sein d'un module. Le coefficient d'une évaluation n'est utilisé que pour pondérer les évaluations au sein d'un module.
@ -979,7 +973,7 @@ def evaluation_create_form(
"title": "Notes de 0 à", "title": "Notes de 0 à",
"explanation": "barème (note max actuelle: %s)" % min_note_max_str, "explanation": "barème (note max actuelle: %s)" % min_note_max_str,
"allow_null": False, "allow_null": False,
"max_value": scu.NOTES_MAX, "max_value": NOTES_MAX,
"min_value": min_note_max, "min_value": min_note_max,
}, },
), ),
@ -1014,7 +1008,7 @@ def evaluation_create_form(
{ {
"input_type": "menu", "input_type": "menu",
"title": "Modalité", "title": "Modalité",
"allowed_values": (scu.EVALUATION_NORMALE, scu.EVALUATION_RATTRAPAGE), "allowed_values": (EVALUATION_NORMALE, EVALUATION_RATTRAPAGE),
"type": "int", "type": "int",
"labels": ("Normale", "Rattrapage"), "labels": ("Normale", "Rattrapage"),
}, },

View File

@ -644,12 +644,6 @@ def formsemestre_description_table(
ModInscrits = context.do_moduleimpl_inscription_list( ModInscrits = context.do_moduleimpl_inscription_list(
moduleimpl_id=M["moduleimpl_id"] moduleimpl_id=M["moduleimpl_id"]
) )
enseignants = ", ".join(
[
context.Users.user_info(m["ens_id"], REQUEST)["nomprenom"]
for m in M["ens"]
]
)
l = { l = {
"UE": M["ue"]["acronyme"], "UE": M["ue"]["acronyme"],
"Code": M["module"]["code"], "Code": M["module"]["code"],
@ -658,8 +652,6 @@ def formsemestre_description_table(
"Inscrits": len(ModInscrits), "Inscrits": len(ModInscrits),
"Responsable": context.Users.user_info(M["responsable_id"])["nomprenom"], "Responsable": context.Users.user_info(M["responsable_id"])["nomprenom"],
"_Responsable_class": "scotext", "_Responsable_class": "scotext",
"Enseignants": enseignants,
"_Enseignants_class": "scotext",
"Coef.": M["module"]["coefficient"], "Coef.": M["module"]["coefficient"],
# 'ECTS' : M['module']['ects'], # 'ECTS' : M['module']['ects'],
# Lien sur titre -> module # Lien sur titre -> module
@ -697,7 +689,7 @@ def formsemestre_description_table(
columns_ids = ["UE", "Code", "Module", "Coef."] columns_ids = ["UE", "Code", "Module", "Coef."]
if context.get_preference("bul_show_ects", formsemestre_id): if context.get_preference("bul_show_ects", formsemestre_id):
columns_ids += ["ects"] columns_ids += ["ects"]
columns_ids += ["Inscrits", "Responsable", "Enseignants"] columns_ids += ["Inscrits", "Responsable"]
if with_evals: if with_evals:
columns_ids += [ columns_ids += [
"jour", "jour",
@ -907,12 +899,6 @@ def formsemestre_status(context, formsemestre_id=None, REQUEST=None):
<th class="resp">Responsable</th> <th class="resp">Responsable</th>
<th class="evals">Evaluations</th></tr>""" <th class="evals">Evaluations</th></tr>"""
) )
mails_enseignants = set(
[
context.Users.user_info(ens_id, REQUEST)["email"]
for ens_id in sem["responsables"]
]
) # adr. mail des enseignants
for M in Mlist: for M in Mlist:
Mod = M["module"] Mod = M["module"]
ModDescr = ( ModDescr = (
@ -929,12 +915,6 @@ def formsemestre_status(context, formsemestre_id=None, REQUEST=None):
ModInscrits = context.do_moduleimpl_inscription_list( ModInscrits = context.do_moduleimpl_inscription_list(
moduleimpl_id=M["moduleimpl_id"] moduleimpl_id=M["moduleimpl_id"]
) )
mails_enseignants.add(
context.Users.user_info(M["responsable_id"], REQUEST)["email"]
)
mails_enseignants |= set(
[context.Users.user_info(m["ens_id"], REQUEST)["email"] for m in M["ens"]]
)
ue = M["ue"] ue = M["ue"]
if prev_ue_id != ue["ue_id"]: if prev_ue_id != ue["ue_id"]:
prev_ue_id = ue["ue_id"] prev_ue_id = ue["ue_id"]
@ -1059,11 +1039,5 @@ def formsemestre_status(context, formsemestre_id=None, REQUEST=None):
) )
# --- LISTE DES ETUDIANTS # --- LISTE DES ETUDIANTS
H += ['<div id="groupes">', context.make_listes_sem(sem, REQUEST), "</div>"] H += ['<div id="groupes">', context.make_listes_sem(sem, REQUEST), "</div>"]
# --- Lien mail enseignants:
adrlist = list(mails_enseignants - set([""]))
if adrlist:
H.append(
'<p><a class="stdlink" href="mailto:?cc=%s">Courrier aux %d enseignants du semestre</a></p>'
% (",".join(adrlist), len(adrlist))
)
return "".join(H) + context.sco_footer(REQUEST) return "".join(H) + context.sco_footer(REQUEST)

View File

@ -431,10 +431,10 @@ def groups_table(
format: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf format: csv, json, xml, xls, allxls, xlsappel, moodlecsv, pdf
Si with_codes, ajoute 4 colonnes avec les codes etudid, NIP, INE et etape Si with_codes, ajoute 4 colonnes avec les codes etudid, NIP, INE et etape
""" """
# log( log(
# "enter groups_table %s: %s" "enter groups_table %s: %s"
# % (groups_infos.members[0]["nom"], groups_infos.members[0].get("etape", "-")) % (groups_infos.members[0]["nom"], groups_infos.members[0].get("etape", "-"))
# ) )
authuser = REQUEST.AUTHENTICATED_USER authuser = REQUEST.AUTHENTICATED_USER
with_codes = int(with_codes) with_codes = int(with_codes)
@ -798,11 +798,6 @@ def tab_absences_html(context, groups_infos, etat=None, REQUEST=None):
"<h3>Absences</h3>", "<h3>Absences</h3>",
'<ul class="ul_abs">', '<ul class="ul_abs">',
"<li>", "<li>",
form_choix_saisie_semaine(
context, groups_infos, REQUEST=REQUEST
), # Ajout Le Havre
"</li>",
"<li>",
form_choix_jour_saisie_hebdo(context, groups_infos, REQUEST=REQUEST), form_choix_jour_saisie_hebdo(context, groups_infos, REQUEST=REQUEST),
"</li>", "</li>",
"""<li><a class="stdlink" href="Absences/EtatAbsencesGr?%s&amp;debut=%s&amp;fin=%s">Etat des absences du groupe</a></li>""" """<li><a class="stdlink" href="Absences/EtatAbsencesGr?%s&amp;debut=%s&amp;fin=%s">Etat des absences du groupe</a></li>"""
@ -893,37 +888,6 @@ def form_choix_jour_saisie_hebdo(context, groups_infos, REQUEST=None):
return "\n".join(FA) return "\n".join(FA)
# Ajout Le Havre
# Formulaire saisie absences semaine
def form_choix_saisie_semaine(context, groups_infos, REQUEST=None):
authuser = REQUEST.AUTHENTICATED_USER
if not authuser.has_permission(ScoAbsChange, context):
return ""
sem = groups_infos.formsemestre
# construit l'URL "destination"
# (a laquelle on revient apres saisie absences)
query_args = cgi.parse_qs(REQUEST.QUERY_STRING)
moduleimpl_id = query_args.get("moduleimpl_id", [""])[0]
if "head_message" in query_args:
del query_args["head_message"]
destination = "%s?%s" % (REQUEST.URL, urllib.urlencode(query_args, True))
destination = destination.replace(
"%", "%%"
) # car ici utilisee dans un format string !
DateJour = time.strftime("%d/%m/%Y")
datelundi = ZAbsences.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)
FA.append('<input type="hidden" name="moduleimpl_id" value="%s"/>' % moduleimpl_id)
FA.append('<input type="hidden" name="destination" value="%s"/>' % destination)
FA.append(groups_infos.get_form_elem())
FA.append('<input type="submit" class="button" value="Saisie à la semaine" />')
FA.append("</form>")
return "\n".join(FA)
def export_groups_as_moodle_csv(context, formsemestre_id=None, REQUEST=None): def export_groups_as_moodle_csv(context, formsemestre_id=None, REQUEST=None):
"""Export all students/groups, in a CSV format suitable for Moodle """Export all students/groups, in a CSV format suitable for Moodle
Each (student,group) will be listed on a separate line Each (student,group) will be listed on a separate line

View File

@ -39,8 +39,7 @@ TITLES = ("user_name", "nom", "prenom", "email", "roles", "dept")
def generate_excel_sample(): def generate_excel_sample():
"""generates an excel document suitable to import users """generates an excel document suitable to import users"""
"""
style = sco_excel.Excel_MakeStyle(bold=True) style = sco_excel.Excel_MakeStyle(bold=True)
titles = TITLES titles = TITLES
titlesStyles = [style] * len(titles) titlesStyles = [style] * len(titles)
@ -105,7 +104,7 @@ def import_users(U, auth_dept="", context=None):
- créer utilisateur et mettre le mot de passe - créer utilisateur et mettre le mot de passe
- envoyer mot de passe par mail - envoyer mot de passe par mail
En cas d'erreur: supprimer tous les utilisateurs que l'on vient de créer. En cas d'erreur: supprimer tous les utilisateurs que l'on vient de créer.
""" """
created = [] # liste de uid créés created = [] # liste de uid créés
try: try:
@ -164,11 +163,11 @@ def generate_password():
return "".join(RNG.sample(l, PASSLEN)) return "".join(RNG.sample(l, PASSLEN))
from email.MIMEMultipart import MIMEMultipart import email
from email.MIMEText import MIMEText from email.mime.multipart import MIMEMultipart
from email.MIMEBase import MIMEBase from email.mime.text import MIMEText
from email.Header import Header from email.mime.base import MIMEBase
from email import Encoders from email.header import Header
def mail_password(u, context=None, reset=False): def mail_password(u, context=None, reset=False):

View File

@ -262,11 +262,7 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No
H.append( H.append(
"""Afficher les groupes de&nbsp;<select name="partition_id" onchange="document.f.submit();">""" """Afficher les groupes de&nbsp;<select name="partition_id" onchange="document.f.submit();">"""
) )
been_selected = False
for partition in partitions: for partition in partitions:
if not partition_id and not been_selected:
selected = "selected"
been_selected = True
if partition["partition_id"] == partition_id: if partition["partition_id"] == partition_id:
selected = "selected" selected = "selected"
else: else:
@ -341,11 +337,12 @@ def moduleimpl_status(context, moduleimpl_id=None, partition_id=None, REQUEST=No
% etat["last_modif"].strftime("%d/%m/%Y à %Hh%M") % etat["last_modif"].strftime("%d/%m/%Y à %Hh%M")
) )
H.append('<span class="evalindex_cont">') H.append('<span class="evalindex_cont">')
if has_expression or True: if has_expression:
H.append( H.append(
"""<span class="evalindex" title="Indice dans les vecteurs (formules)">%2d</span>""" """<span class="evalindex" title="Indice dans les vecteurs (formules)">%02d</span>"""
% eval_index % eval_index
) )
# Fleches: # Fleches:
H.append('<span class="eval_arrows_chld">') H.append('<span class="eval_arrows_chld">')
if eval_index != (len(ModEvals) - 1) and caneditevals: if eval_index != (len(ModEvals) - 1) and caneditevals:

View File

@ -33,11 +33,9 @@ from cStringIO import StringIO
import datetime, re import datetime, re
import time import time
from stripogram import html2text, html2safehtml from stripogram import html2text, html2safehtml
from email.MIMEMultipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.MIMEText import MIMEText from email.mime.text import MIMEText
from email.Header import Header from email.header import Header
from email import Encoders
from notesdb import * from notesdb import *
from notes_log import log from notes_log import log
@ -252,8 +250,7 @@ def scolar_news_summary_rss(context, title, sco_url, n=5):
def _send_news_by_mail(context, n): def _send_news_by_mail(context, n):
"""Notify by email """Notify by email"""
"""
infos = _get_formsemestre_infos_from_news(context, n) infos = _get_formsemestre_infos_from_news(context, n)
formsemestre_id = infos.get("formsemestre_id", None) formsemestre_id = infos.get("formsemestre_id", None)
prefs = context.get_preferences(formsemestre_id=formsemestre_id) prefs = context.get_preferences(formsemestre_id=formsemestre_id)

View File

@ -53,7 +53,7 @@ from PIL import Image as PILImage
from cStringIO import StringIO from cStringIO import StringIO
import glob import glob
from sco_utils import CONFIG, SCO_SRCDIR from sco_utils import CONFIG, SCO_SRC_DIR
from notes_log import log from notes_log import log
import scolars import scolars
@ -62,7 +62,7 @@ from scolog import logdb
# Full paths on server's filesystem. Something like "/opt/scodoc/var/scodoc/photos" # Full paths on server's filesystem. Something like "/opt/scodoc/var/scodoc/photos"
PHOTO_DIR = os.path.join(os.environ["INSTANCE_HOME"], "var", "scodoc", "photos") PHOTO_DIR = os.path.join(os.environ["INSTANCE_HOME"], "var", "scodoc", "photos")
ICONS_DIR = os.path.join(SCO_SRCDIR, "static", "icons") ICONS_DIR = os.path.join(SCO_SRC_DIR, "static", "icons")
UNKNOWN_IMAGE_PATH = os.path.join(ICONS_DIR, "unknown.jpg") UNKNOWN_IMAGE_PATH = os.path.join(ICONS_DIR, "unknown.jpg")
UNKNOWN_IMAGE_URL = "get_photo_image?etudid=" # with empty etudid => unknown face image UNKNOWN_IMAGE_URL = "get_photo_image?etudid=" # with empty etudid => unknown face image
IMAGE_EXT = ".jpg" IMAGE_EXT = ".jpg"

View File

@ -37,9 +37,9 @@ import datetime
import sco_utils import sco_utils
from sco_utils import ScoEtudInscrit, log, ScoValueError, DictDefault from sco_utils import ScoEtudInscrit, log, ScoValueError, DictDefault
from sco_utils import SCO_TMPDIR, SCO_ENCODING from sco_utils import SCO_TMP_DIR, SCO_ENCODING
SCO_CACHE_ETAPE_FILENAME = os.path.join(SCO_TMPDIR, "last_etapes.xml") SCO_CACHE_ETAPE_FILENAME = os.path.join(SCO_TMP_DIR, "last_etapes.xml")
def has_portal(context): def has_portal(context):

View File

@ -49,7 +49,7 @@ Chaque parametre est défini dans la base de données SQL par:
Au niveau du code interface, on défini pour chaque préférence: Au niveau du code interface, on défini pour chaque préférence:
- name (clé) - name (clé)
- title : titre en français - title : titre en français
- initvalue : valeur initiale, chargée depuis config/scodoc_config.py - initvalue : valeur initiale
- explanation: explication en français - explanation: explication en français
- size: longueur du chap texte - size: longueur du chap texte
- input_type: textarea,separator,... type de widget TrivialFormulator a utiliser - input_type: textarea,separator,... type de widget TrivialFormulator a utiliser
@ -90,13 +90,6 @@ sinon, elle ne concerne que le semestre indiqué.
- editer les preferences d'un semestre: - editer les preferences d'un semestre:
sem_preferences(context,formsemestre_id).edit() sem_preferences(context,formsemestre_id).edit()
* Valeurs par défaut:
On a deux valeurs par défaut possibles:
- via le fichier scodoc_config.py, qui peut être modifié localement.
- si rien dans scodoc_config.py, la valeur définie par
sco_preferences.py est utilisée (ne pas modifier ce fichier).
* Implémentation: sco_preferences.py * Implémentation: sco_preferences.py
PREF_CATEGORIES : définition des catégories de préférences (pour PREF_CATEGORIES : définition des catégories de préférences (pour

View File

@ -764,17 +764,12 @@ def _list_notes_evals(context, evals, etudid):
""" """
L = [] L = []
for e in evals: for e in evals:
if ( if e["etat"]["evalcomplete"]:
e["etat"]["evalcomplete"]
or e["etat"]["evalattente"]
or e["publish_incomplete"]
):
NotesDB = context._notes_getall(e["evaluation_id"]) NotesDB = context._notes_getall(e["evaluation_id"])
if NotesDB.has_key(etudid): if NotesDB.has_key(etudid):
val = NotesDB[etudid]["value"] val = NotesDB[etudid]["value"]
else: else:
# Note manquante mais prise en compte immédiate: affiche ATT val = None
val = NOTES_ATTENTE
val_fmt = fmt_note(val, keep_numeric=True) val_fmt = fmt_note(val, keep_numeric=True)
L.append(val_fmt) L.append(val_fmt)
return L return L
@ -783,15 +778,9 @@ def _list_notes_evals(context, evals, etudid):
def _list_notes_evals_titles(context, codemodule, evals): def _list_notes_evals_titles(context, codemodule, evals):
"""Liste des titres des evals completes""" """Liste des titres des evals completes"""
L = [] L = []
eval_index = len(evals) - 1
for e in evals: for e in evals:
if ( if e["etat"]["evalcomplete"]:
e["etat"]["evalcomplete"] L.append(codemodule + "-" + DateISOtoDMY(e["jour"]))
or e["etat"]["evalattente"]
or e["publish_incomplete"]
):
L.append(codemodule + "-" + str(eval_index) + "-" + e["jour"].isoformat())
eval_index -= 1
return L return L
@ -799,11 +788,7 @@ def _list_notes_evals_stats(context, evals, key):
"""Liste des stats (moy, ou rien!) des evals completes""" """Liste des stats (moy, ou rien!) des evals completes"""
L = [] L = []
for e in evals: for e in evals:
if ( if e["etat"]["evalcomplete"]:
e["etat"]["evalcomplete"]
or e["etat"]["evalattente"]
or e["publish_incomplete"]
):
if key == "moy": if key == "moy":
val = e["etat"]["moy_num"] val = e["etat"]["moy_num"]
L.append(fmt_note(val, keep_numeric=True)) L.append(fmt_note(val, keep_numeric=True))

View File

@ -28,8 +28,6 @@
"""Synchronisation des listes d'étudiants avec liste portail (Apogée) """Synchronisation des listes d'étudiants avec liste portail (Apogée)
""" """
import time
import pprint
from sco_utils import ScoEtudInscrit, annee_scolaire_debut, log, ScoValueError from sco_utils import ScoEtudInscrit, annee_scolaire_debut, log, ScoValueError
from notesdb import ScoDocCursor from notesdb import ScoDocCursor
@ -44,6 +42,8 @@ import sco_formsemestre_inscriptions
import sco_formsemestre_status import sco_formsemestre_status
from sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC from sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC
import time
# Clés utilisées pour la synchro # Clés utilisées pour la synchro
EKEY_APO = "nip" EKEY_APO = "nip"
EKEY_SCO = "code_nip" EKEY_SCO = "code_nip"
@ -695,7 +695,7 @@ def do_import_etud_admission(
"codelycee": get_opt_str(etud, "lycee"), "codelycee": get_opt_str(etud, "lycee"),
"boursier": get_opt_str(etud, "bourse"), "boursier": get_opt_str(etud, "bourse"),
} }
log("do_import_etud_admission: etud=%s" % pprint.pformat(etud)) log("do_import_etud_admission: etud=%s" % etud)
al = scolars.admission_list(cnx, args={"etudid": etudid}) al = scolars.admission_list(cnx, args={"etudid": etudid})
if not al: if not al:
scolars.admission_create(cnx, args) # -> adm_id scolars.admission_create(cnx, args) # -> adm_id

View File

@ -29,7 +29,7 @@
""" Verification version logiciel vs version "stable" sur serveur """ Verification version logiciel vs version "stable" sur serveur
N'effectue pas la mise à jour automatiquement, mais permet un affichage d'avertissement. N'effectue pas la mise à jour automatiquement, mais permet un affichage d'avertissement.
""" """
import sco_utils
from sco_utils import * from sco_utils import *
# Appel renvoyant la subversion "stable" # Appel renvoyant la subversion "stable"
@ -66,65 +66,66 @@ def is_up_to_date(context):
"""True if up_to_date """True if up_to_date
Returns status, message Returns status, message
""" """
global _LAST_UP_TO_DATE_REQUEST, _UP_TO_DATE, _UP_TO_DATE_MSG log("Warning: is_up_to_date not implemented for ScoDoc8")
if _LAST_UP_TO_DATE_REQUEST and ( return True, "unimplemented"
datetime.datetime.now() - _LAST_UP_TO_DATE_REQUEST # global _LAST_UP_TO_DATE_REQUEST, _UP_TO_DATE, _UP_TO_DATE_MSG
) < datetime.timedelta(1): # if _LAST_UP_TO_DATE_REQUEST and (
# requete deja effectuee aujourd'hui: # datetime.datetime.now() - _LAST_UP_TO_DATE_REQUEST
return _UP_TO_DATE, _UP_TO_DATE_MSG # ) < datetime.timedelta(1):
# # requete deja effectuee aujourd'hui:
# return _UP_TO_DATE, _UP_TO_DATE_MSG
last_stable_ver = get_last_stable_version() # last_stable_ver = get_last_stable_version()
cur_ver = get_svn_version(context.file_path) # in sco_utils # cur_ver = sco_utils.get_scodoc_version()
cur_ver2 = cur_ver # cur_ver2 = cur_ver
cur_ver_num = -1 # cur_ver_num = -1
# Convert versions to integers: # # Convert versions to integers:
try: # try:
# cur_ver can be "1234" or "1234M' or '1234:1245M'... # # cur_ver can be "1234" or "1234M' or '1234:1245M'...
fs = cur_ver.split(":", 1) # fs = cur_ver.split(":", 1)
if len(fs) > 1: # if len(fs) > 1:
cur_ver2 = fs[-1] # cur_ver2 = fs[-1]
m = re.match(r"([0-9]*)", cur_ver2) # m = re.match(r"([0-9]*)", cur_ver2)
if not m: # if not m:
raise ValueError( # raise ValueError(
"invalid svn version" # "invalid svn version"
) # should never occur, regexp always (maybe empty) match # ) # should never occur, regexp always (maybe empty) match
cur_ver_num = int(m.group(1)) # cur_ver_num = int(m.group(1))
except: # except:
log('Warning: no numeric subversion ! (cur_ver="%s")' % cur_ver) # log('Warning: no numeric subversion ! (cur_ver="%s")' % cur_ver)
return _UP_TO_DATE, _UP_TO_DATE_MSG # silently ignore misconfiguration ? # return _UP_TO_DATE, _UP_TO_DATE_MSG # silently ignore misconfiguration ?
try: # try:
last_stable_ver_num = int(last_stable_ver) # last_stable_ver_num = int(last_stable_ver)
except: # except:
log("Warning: last_stable_version returned by server is invalid !") # log("Warning: last_stable_version returned by server is invalid !")
return ( # return (
_UP_TO_DATE, # _UP_TO_DATE,
_UP_TO_DATE_MSG, # _UP_TO_DATE_MSG,
) # should ignore this error (maybe server is unreachable) # ) # should ignore this error (maybe server is unreachable)
# # #
if cur_ver_num < last_stable_ver_num: # if cur_ver_num < last_stable_ver_num:
_UP_TO_DATE = False # _UP_TO_DATE = False
_UP_TO_DATE_MSG = "Version %s disponible (version %s installée)" % ( # _UP_TO_DATE_MSG = "Version %s disponible (version %s installée)" % (
last_stable_ver, # last_stable_ver,
cur_ver_num, # cur_ver_num,
) # )
log( # log(
"Warning: ScoDoc installation is not up-to-date, should upgrade\n%s" # "Warning: ScoDoc installation is not up-to-date, should upgrade\n%s"
% _UP_TO_DATE_MSG # % _UP_TO_DATE_MSG
) # )
else: # else:
_UP_TO_DATE = True # _UP_TO_DATE = True
_UP_TO_DATE_MSG = "" # _UP_TO_DATE_MSG = ""
log( # log(
"ScoDoc is up-to-date (cur_ver: %s, using %s=%s)" # "ScoDoc is up-to-date (cur_ver: %s, using %s=%s)"
% (cur_ver, cur_ver2, cur_ver_num) # % (cur_ver, cur_ver2, cur_ver_num)
) # )
return _UP_TO_DATE, _UP_TO_DATE_MSG # return _UP_TO_DATE, _UP_TO_DATE_MSG
def html_up_to_date_box(context): def html_up_to_date_box(context):
""" """"""
"""
status, msg = is_up_to_date(context) status, msg = is_up_to_date(context)
if status: if status:
return "" return ""

View File

@ -186,53 +186,84 @@ def get_mention(moy):
return NOTES_MENTIONS_LABS[bisect.bisect_right(NOTES_MENTIONS_TH, moy)] return NOTES_MENTIONS_LABS[bisect.bisect_right(NOTES_MENTIONS_TH, moy)]
class DictDefault(dict): # obsolete, use collections.defaultdict
"""A dictionnary with default value for all keys
Each time a non existent key is requested, it is added to the dict.
(used in python 2.4, can't use new __missing__ method)
"""
defaultvalue = 0
def __init__(self, defaultvalue=0, kv_dict={}):
dict.__init__(self)
self.defaultvalue = defaultvalue
self.update(kv_dict)
def __getitem__(self, k):
if self.has_key(k):
return self.get(k)
value = copy.copy(self.defaultvalue)
self[k] = value
return value
class WrapDict:
"""Wrap a dict so that getitem returns '' when values are None"""
def __init__(self, adict, NoneValue=""):
self.dict = adict
self.NoneValue = NoneValue
def __getitem__(self, key):
value = self.dict[key]
if value is None:
return self.NoneValue
else:
return value
def group_by_key(d, key):
g = DictDefault(defaultvalue=[])
for e in d:
g[e[key]].append(e)
return g
# ----- Global lock for critical sections (except notes_tables caches) # ----- Global lock for critical sections (except notes_tables caches)
GSL = thread.allocate_lock() # Global ScoDoc Lock GSL = thread.allocate_lock() # Global ScoDoc Lock
if "INSTANCE_HOME" in os.environ: if "INSTANCE_HOME" in os.environ:
# ----- Repertoire "var" (local) # ----- Repertoire "var" (local)
SCODOC_VAR_DIR = os.path.join(os.environ["INSTANCE_HOME"], "var", "scodoc") SCODOC_VAR_DIR = os.path.join(os.environ["INSTANCE_HOME"], "var", "scodoc")
# ----- Repertoire "config" modifiable
# /opt/scodoc/var/scodoc/config
SCODOC_CFG_DIR = os.path.join(SCODOC_VAR_DIR, "config")
# ----- Version information # ----- Version information
SCODOC_VERSION_DIR = os.path.join(SCODOC_VAR_DIR, "config", "version") SCODOC_VERSION_DIR = os.path.join(SCODOC_CFG_DIR, "version")
# ----- Repertoire tmp # ----- Repertoire tmp
SCO_TMPDIR = os.path.join(SCODOC_VAR_DIR, "tmp") SCO_TMP_DIR = os.path.join(SCODOC_VAR_DIR, "tmp")
if not os.path.exists(SCO_TMPDIR): if not os.path.exists(SCO_TMP_DIR):
os.mkdir(SCO_TMPDIR, 0o755) os.mkdir(SCO_TMP_DIR, 0o755)
# ----- Les logos: /opt/scodoc/var/scodoc/config/logos # ----- Les logos: /opt/scodoc/var/scodoc/config/logos
SCODOC_LOGOS_DIR = os.path.join(SCODOC_VAR_DIR, "config", "logos") SCODOC_LOGOS_DIR = os.path.join(SCODOC_CFG_DIR, "logos")
# ----- Repertoire "config" (devrait s'appeler "tools"...) # Dans les sources:
SCO_CONFIG_DIR = os.path.join( SCO_SRC_DIR = os.path.join(os.environ["INSTANCE_HOME"], "Products", "ScoDoc")
os.environ["INSTANCE_HOME"], "Products", "ScoDoc", "config" # - Les outils distribués
) SCO_TOOLS_DIR = os.path.join(SCO_SRC_DIR, "config")
# ----- Lecture du fichier de configuration # ----- Lecture du fichier de configuration
SCO_SRCDIR = os.path.split(VERSION.__file__)[0] import sco_config
if SCO_SRCDIR: import sco_config_load
SCO_SRCDIR += "/"
else:
SCO_SRCDIR = "/opt/scodoc/Products/ScoDoc/" # debug mode
CONFIG = None
try:
_config_filename = SCO_SRCDIR + "config/scodoc_config.py"
_config_text = open(_config_filename).read()
except:
sys.stderr.write("sco_utils: cannot open configuration file %s" % _config_filename)
raise
try:
exec(_config_text)
except:
sys.stderr.write("sco_utils: error in configuration file %s" % _config_filename)
raise
sco_config_load.load_local_configuration()
CONFIG = sco_config.CONFIG
if hasattr(CONFIG, "CODES_EXPL"): if hasattr(CONFIG, "CODES_EXPL"):
CODES_EXPL.update( CODES_EXPL.update(
CONFIG.CODES_EXPL CONFIG.CODES_EXPL
) # permet de customiser les explications de codes ) # permet de customiser les explications de codes
if CONFIG.CUSTOM_HTML_HEADER: if CONFIG.CUSTOM_HTML_HEADER:
CUSTOM_HTML_HEADER = open(CONFIG.CUSTOM_HTML_HEADER).read() CUSTOM_HTML_HEADER = open(CONFIG.CUSTOM_HTML_HEADER).read()
else: else:
@ -297,50 +328,6 @@ JSON_MIMETYPE = "application/json"
LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "png") # remind that PIL does not read pdf LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "png") # remind that PIL does not read pdf
class DictDefault(dict): # obsolete, use collections.defaultdict
"""A dictionnary with default value for all keys
Each time a non existent key is requested, it is added to the dict.
(used in python 2.4, can't use new __missing__ method)
"""
defaultvalue = 0
def __init__(self, defaultvalue=0, kv_dict={}):
dict.__init__(self)
self.defaultvalue = defaultvalue
self.update(kv_dict)
def __getitem__(self, k):
if self.has_key(k):
return self.get(k)
value = copy.copy(self.defaultvalue)
self[k] = value
return value
class WrapDict:
"""Wrap a dict so that getitem returns '' when values are None"""
def __init__(self, adict, NoneValue=""):
self.dict = adict
self.NoneValue = NoneValue
def __getitem__(self, key):
value = self.dict[key]
if value is None:
return self.NoneValue
else:
return value
def group_by_key(d, key):
g = DictDefault(defaultvalue=[])
for e in d:
g[e[key]].append(e)
return g
# Admissions des étudiants # Admissions des étudiants
# Différents types de voies d'admission: # Différents types de voies d'admission:
# (stocké en texte libre dans la base, mais saisie par menus pour harmoniser) # (stocké en texte libre dans la base, mais saisie par menus pour harmoniser)
@ -610,15 +597,9 @@ def sendResult(REQUEST, data, name=None, format=None, force_outer_xml_tag=True):
raise ValueError("invalid format: %s" % format) raise ValueError("invalid format: %s" % format)
# Get SVN version def get_scodoc_version():
def get_svn_version(path): "return a string identifying ScoDoc version"
if os.path.exists("/usr/bin/svnversion"): return os.popen("cd %s; ./get_scodoc_version.sh -s" % SCO_TOOLS_DIR).read().strip()
try:
return os.popen("svnversion " + path).read().strip()
except:
return "non disponible (erreur de lecture)"
else:
return "non disponible"
# Simple string manipulations # Simple string manipulations
@ -791,7 +772,7 @@ def icontag(name, file_format="png", **attrs):
""" """
if ("width" not in attrs) or ("height" not in attrs): if ("width" not in attrs) or ("height" not in attrs):
if name not in ICONSIZES: if name not in ICONSIZES:
img_file = SCO_SRCDIR + "/static/icons/%s.%s" % (name, file_format) img_file = SCO_SRC_DIR + "/static/icons/%s.%s" % (name, file_format)
im = PILImage.open(img_file) im = PILImage.open(img_file)
width, height = im.size[0], im.size[1] width, height = im.size[0], im.size[1]
ICONSIZES[name] = (width, height) # cache ICONSIZES[name] = (width, height) # cache

View File

@ -28,6 +28,8 @@
"""Imports et configuration des composants Zope """Imports et configuration des composants Zope
""" """
# Allows code linting on platforms without Zope:
# pylint: disable=import-error
from OFS.SimpleItem import Item # Basic zope object from OFS.SimpleItem import Item # Basic zope object
from OFS.PropertyManager import PropertyManager # provide the 'Properties' tab with the from OFS.PropertyManager import PropertyManager # provide the 'Properties' tab with the

View File

@ -25,7 +25,7 @@
# #
############################################################################## ##############################################################################
""" Accès donnees etudiants """ Acces donnees etudiants
""" """
from sco_utils import * from sco_utils import *
@ -35,18 +35,17 @@ from TrivialFormulator import TrivialFormulator
import safehtml import safehtml
from scolog import logdb from scolog import logdb
from notes_table import * from notes_table import *
import sco_news
# XXXXXXXXX HACK: zope 2.7.7 bug turaround ? # XXXXXXXXX HACK: zope 2.7.7 bug turaround ?
import locale import locale
locale.setlocale(locale.LC_ALL, ("en_US", SCO_ENCODING)) locale.setlocale(locale.LC_ALL, ("en_US", SCO_ENCODING))
from email.MIMEMultipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.MIMEText import MIMEText from email.mime.text import MIMEText
from email.MIMEBase import MIMEBase from email.header import Header
from email.Header import Header from email.mime.base import MIMEBase
from email import Encoders
abbrvmonthsnames = [ abbrvmonthsnames = [
"Jan ", "Jan ",
@ -618,59 +617,6 @@ def make_etud_args(etudid=None, code_nip=None, REQUEST=None, raise_exc=True):
return args return args
def create_etud(context, cnx, args={}, REQUEST=None):
"""Creation d'un étudiant. génère aussi évenement et "news".
Args:
args: dict avec les attributs de l'étudiant
Returns:
etud, l'étudiant créé.
"""
# creation d'un etudiant
etudid = etudident_create(cnx, args, context=context, REQUEST=REQUEST)
# crée une adresse vide (chaque etudiant doit etre dans la table "adresse" !)
_ = adresse_create(
cnx,
{
"etudid": etudid,
"typeadresse": "domicile",
"description": "(creation individuelle)",
},
)
# event
scolar_events_create(
cnx,
args={
"etudid": etudid,
"event_date": time.strftime("%d/%m/%Y"),
"formsemestre_id": None,
"event_type": "CREATION",
},
)
# log
logdb(
REQUEST,
cnx,
method="etudident_edit_form",
etudid=etudid,
msg="creation initiale",
)
etud = scolars.etudident_list(cnx, {"etudid": etudid})[0]
context.fillEtudsInfo([etud])
etud["url"] = "ficheEtud?etudid=%(etudid)s" % etud
sco_news.add(
context,
REQUEST,
typ=sco_news.NEWS_INSCR,
object=None, # pas d'object pour ne montrer qu'un etudiant
text='Nouvel étudiant <a href="%(url)s">%(nomprenom)s</a>' % etud,
url=etud["url"],
)
return etud
# ---------- "EVENTS" # ---------- "EVENTS"
_scolar_eventsEditor = EditableTable( _scolar_eventsEditor = EditableTable(
"scolar_events", "scolar_events",
@ -766,7 +712,7 @@ appreciations_edit = _appreciationsEditor.edit
# -------- Noms des Lycées à partir du code # -------- Noms des Lycées à partir du code
def read_etablissements(): def read_etablissements():
filename = SCO_SRCDIR + "/" + CONFIG.ETABL_FILENAME filename = SCO_SRC_DIR + "/" + CONFIG.ETABL_FILENAME
log("reading %s" % filename) log("reading %s" % filename)
f = open(filename) f = open(filename)
L = [x[:-1].split(";") for x in f] L = [x[:-1].split(";") for x in f]

View File

@ -1 +0,0 @@
#

View File

@ -1,399 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Creation environnement pour test.
A utiliser avec debug.py (côté serveur).
La classe ScoFake offre un ensemble de raccourcis permettant d'écrire
facilement des tests ou de reproduire des bugs.
"""
from __future__ import print_function
from functools import wraps
import sys
import string
import collections
import pprint
import random
random.seed(12345) # tests reproductibles
from debug import REQUEST
import sco_utils
from notes_log import log
from sco_exceptions import ScoValueError
import scolars
import sco_formsemestre
import sco_formsemestre_inscriptions
import sco_formsemestre_validation
import sco_synchro_etuds
import sco_edit_formation
import sco_edit_ue
import sco_codes_parcours
import sco_saisie_notes
DEMODIR = sco_utils.SCO_SRCDIR + "/scotests/demo/"
NOMS = [x.strip() for x in open(DEMODIR + "/noms.txt").readlines()]
PRENOMS_H = [x.strip() for x in open(DEMODIR + "/prenoms-h.txt").readlines()]
PRENOMS_F = [x.strip() for x in open(DEMODIR + "/prenoms-f.txt").readlines()]
# nb: en python2, les chaines ci-dessus sont en utf8
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
return "".join(random.choice(chars) for _ in range(size))
def logging_meth(func):
@wraps(func)
def wrapper_logging_meth(self, *args, **kwargs):
r = func(self, *args, **kwargs)
self.log("%s(%s) -> \n%s" % (func.__name__, kwargs, pprint.pformat(r)))
return r
return wrapper_logging_meth
class ScoFake:
def __init__(self, context, verbose=True):
self.context = context
self.verbose = verbose
def log(self, msg):
if self.verbose:
print("ScoFake: " + str(msg), file=sys.stderr)
sys.stderr.flush()
log("ScoFake: " + str(msg))
def sexenomprenom(self):
"""un nom et un prenom au hasard,
toujours en majuscules.
"""
sexe = random.choice(("M", "F"))
if "e" in sexe.lower() or "f" in sexe.lower():
prenom = random.choice(PRENOMS_F)
else:
prenom = random.choice(PRENOMS_H)
return sexe, random.choice(NOMS).upper(), prenom.upper()
@logging_meth
def create_etud(
self,
cnx=None,
code_nip="",
nom="",
prenom="",
code_ine="",
sexe="",
etape="TST1",
email="test@localhost",
emailperso="perso@localhost",
date_naissance="01/01/2001",
lieu_naissance="Paris",
dept_naissance="75",
domicile="1, rue du test",
codepostaldomicile="75123",
villedomicile="TestCity",
paysdomicile="France",
telephone="0102030405",
typeadresse="domicile",
boursier=None,
description="etudiant test",
):
"""Crée un étudiant"""
if not cnx:
cnx = self.context.GetDBConnexion()
if code_nip == "":
code_nip = str(random.randint(10000, 99999))
if not sexe or not nom or not prenom:
r_sexe, r_nom, r_prenom = self.sexenomprenom()
if not sexe:
sexe = r_sexe
if not nom:
nom = r_nom
if not prenom:
prenom = r_prenom
etud = scolars.create_etud(self.context, cnx, args=locals(), REQUEST=REQUEST)
inscription = "2020" # pylint: disable=unused-variable
sco_synchro_etuds.do_import_etud_admission(
self.context, cnx, etud["etudid"], locals()
)
return etud
@logging_meth
def create_formation(
self,
acronyme="test",
titre="Formation test",
titre_officiel="Le titre officiel de la formation test",
type_parcours=sco_codes_parcours.ParcoursDUT.TYPE_PARCOURS,
formation_code=None,
code_specialite=None,
):
"""Crée une formation"""
if acronyme == "":
acronyme = "TEST" + str(random.randint(100000, 999999))
oid = self.context.do_formation_create(locals(), REQUEST=REQUEST)
oids = self.context.formation_list(args={"formation_id": oid})
if not oids:
raise ScoValueError("formation not created !")
return oids[0]
@logging_meth
def create_ue(
self,
formation_id=None,
acronyme=None,
numero=None,
titre="",
type=None,
ue_code=None,
ects=None,
is_external=None,
code_apogee=None,
coefficient=None,
):
"""Crée une UE"""
if numero is None:
numero = sco_edit_ue.next_ue_numero(self.context, formation_id, 0)
oid = self.context.do_ue_create(locals(), REQUEST)
oids = self.context.do_ue_list(args={"ue_id": oid})
if not oids:
raise ScoValueError("ue not created !")
return oids[0]
@logging_meth
def create_matiere(self, ue_id=None, titre=None, numero=None):
oid = self.context.do_matiere_create(locals(), REQUEST)
oids = self.context.do_matiere_list(args={"matiere_id": oid})
if not oids:
raise ScoValueError("matiere not created !")
return oids[0]
@logging_meth
def create_module(
self,
titre=None,
code=None,
heures_cours=None,
heures_td=None,
heures_tp=None,
coefficient=None,
ue_id=None,
formation_id=None,
matiere_id=None,
semestre_id=1,
numero=None,
abbrev=None,
ects=None,
code_apogee=None,
module_type=None,
):
oid = self.context.do_module_create(locals(), REQUEST)
oids = self.context.do_module_list(args={"module_id": oid})
if not oids:
raise ScoValueError("module not created ! (oid=%s)" % oid)
return oids[0]
@logging_meth
def create_formsemestre(
self,
formation_id=None,
semestre_id=None,
titre=None,
date_debut=None,
date_fin=None,
etat=None,
gestion_compensation=None,
bul_hide_xml=None,
gestion_semestrielle=None,
bul_bgcolor=None,
modalite=None,
resp_can_edit=None,
resp_can_change_ens=None,
ens_can_edit_eval=None,
elt_sem_apo=None,
elt_annee_apo=None,
etapes=None,
responsables=["bach"],
):
oid = self.context.do_formsemestre_create(locals(), REQUEST)
# oids = self.context.do_formsemestre_list(args={"formsemestre_id": oid})
oids = sco_formsemestre.do_formsemestre_list(
self.context, args={"formsemestre_id": oid}
) # API inconsistency
if not oids:
raise ScoValueError("formsemestre not created !")
return oids[0]
@logging_meth
def create_moduleimpl(
self,
module_id=None,
formsemestre_id=None,
responsable_id=None,
):
oid = self.context.do_moduleimpl_create(locals())
oids = self.context.do_moduleimpl_list(moduleimpl_id=oid) # API inconsistency
if not oids:
raise ScoValueError("moduleimpl not created !")
return oids[0]
@logging_meth
def inscrit_etudiant(self, sem, etud):
sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
self.context,
sem["formsemestre_id"],
etud["etudid"],
etat="I",
etape=etud.get("etape", None),
REQUEST=REQUEST,
method="test_inscrit_etudiant",
)
@logging_meth
def create_evaluation(
self,
moduleimpl_id=None,
jour=None,
heure_debut="8h00",
heure_fin="9h00",
description=None,
note_max=20,
coefficient=None,
visibulletin=None,
publish_incomplete=None,
evaluation_type=None,
numero=None,
REQUEST=REQUEST,
):
args = locals()
del args["self"]
oid = self.context.do_evaluation_create(**args)
oids = self.context.do_evaluation_list(args={"evaluation_id": oid})
if not oids:
raise ScoValueError("evaluation not created !")
return oids[0]
@logging_meth
def create_note(
self,
evaluation=None,
etud=None,
note=None,
comment=None,
uid="bach",
):
return sco_saisie_notes._notes_add(
self.context,
uid,
evaluation["evaluation_id"],
[(etud["etudid"], note)],
comment=comment,
)
def setup_formation(self, nb_semestre=1, nb_ue_per_semestre=2, nb_module_per_ue=2):
"""Création d'une formation, avec UE, modules et évaluations.
Formation avec `nb_semestre` comportant chacun `nb_ue_per_semestre` UE
et dans chaque UE `nb_module_per_ue` modules (on a une seule matière par UE).
Returns:
formation (dict), liste d'ue (dicts), liste de modules.
"""
f = self.create_formation(acronyme="")
ue_list = []
mod_list = []
for semestre_id in range(1, nb_semestre + 1):
for n in range(1, nb_ue_per_semestre + 1):
ue = self.create_ue(
formation_id=f["formation_id"],
acronyme="TSU%s%s" % (semestre_id, n),
titre="ue test %s%s" % (semestre_id, n),
)
ue_list.append(ue)
mat = self.create_matiere(ue_id=ue["ue_id"], titre="matière test")
for _ in range(nb_module_per_ue):
mod = self.create_module(
matiere_id=mat["matiere_id"],
semestre_id=semestre_id,
code="TSM%s" % len(mod_list),
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
mod_list.append(mod)
return f, ue_list, mod_list
def setup_formsemestre(
self,
f,
mod_list,
semestre_id=1,
date_debut="01/01/2020",
date_fin="30/06/2020",
nb_evaluations_per_module=1,
):
"""Création semestre, avec modules et évaluations."""
sem = self.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=semestre_id,
date_debut=date_debut,
date_fin=date_fin,
)
eval_list = []
for mod in mod_list:
if mod["semestre_id"] == semestre_id:
mi = self.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem["formsemestre_id"],
responsable_id="bach",
)
for e_idx in range(1, nb_evaluations_per_module + 1):
e = self.create_evaluation(
moduleimpl_id=mi["moduleimpl_id"],
jour=date_debut,
description="evaluation test %s" % e_idx,
coefficient=1.0,
)
eval_list.append(e)
return sem, eval_list
def set_etud_notes_sem(
self, sem, eval_list, etuds, notes=None, random_min=0, random_max=20
):
"""Met des notes aux étudiants indiqués des evals indiquées.
Args:
sem: dict
eval_list: list of dicts
etuds: list of dicts
notes: liste des notes (float).
Si non spécifié, tire au hasard dans `[random_min, random_max]`
"""
set_random = notes is None
for e in eval_list:
if set_random:
notes = [float(random.randint(random_min, random_max)) for _ in etuds]
for etud, note in zip(etuds, notes):
self.create_note(evaluation=e, etud=etud, note=note)
def set_code_jury(
self,
sem,
etud,
code_etat=sco_codes_parcours.ADM,
devenir=sco_codes_parcours.NEXT,
assidu=True,
):
"""Affecte décision de jury"""
sco_formsemestre_validation.formsemestre_validation_etud_manu(
self.context,
formsemestre_id=sem["formsemestre_id"],
etudid=etud["etudid"],
code_etat=code_etat,
devenir=devenir,
assidu=assidu,
REQUEST=REQUEST,
)

View File

@ -1,53 +0,0 @@
#!/bin/bash
# Lancement d'un python scodoc interactif
# dans l'environnement d'un département
# et avec chargement des scripts indiqués
# via from ... import *
#
# Si -r est utilisé, veiller à créer au préalable
# le département via l'interface web (Zope)
usage() {
echo "Usage: $0 [-r] dept [script...]"
echo "Lance un environnement interactif python/ScoDoc"
echo " -r: supprime et recrée le département (attention: efface la base !)"
exit 1
}
set -euo pipefail
cd /opt/scodoc/Products/ScoDoc || exit 2
source config/utils.sh
if [ $# -lt 1 ]
then
usage
fi
if [ "$1" = "-r" ]
then
shift
recreate_dept=1
fi
DEPT="$1"
shift
if [ "$recreate_dept" = 1 ]
then
(cd config || terminate "no config directory"; ./delete_dept.sh -n "$DEPT") || terminate "error deleting dept $DEPT"
(cd config || terminate "no config directory"; ./create_dept.sh -n "$DEPT") || terminate "error creating dept $DEPT"
# systemctl start scodoc
fi
cmd="from __future__ import print_function;from Zope2 import configure;configure('/opt/scodoc/etc/zope.conf');import Zope2; app=Zope2.app();from debug import *;context = go_dept(app, '""$DEPT""');"
for f in "$@"
do
cmd="${cmd}exec(open(\"${f}\").read());"
done
/opt/zope213/bin/python -i -c "$cmd"

View File

@ -1,66 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Test de base de ScoDoc
Création 10 étudiants, formation, semestre, inscription etudiant, creation 1 evaluation, saisie 10 notes.
Utiliser comme:
scotests/scointeractive.sh -r TEST00 scotests/test_basic.py
"""
import random
import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error
import sco_utils
G = sco_fake_gen.ScoFake(context.Notes) # pylint: disable=undefined-variable
G.verbose = False
# --- Création d'étudiants
etuds = [G.create_etud(code_nip=None) for _ in range(10)]
# --- Création d'une formation
f = G.create_formation(acronyme="")
ue = G.create_ue(formation_id=f["formation_id"], acronyme="TST1", titre="ue test")
mat = G.create_matiere(ue_id=ue["ue_id"], titre="matière test")
mod = G.create_module(
matiere_id=mat["matiere_id"],
code="TSM1",
coefficient=1.0,
titre="module test",
ue_id=ue["ue_id"], # faiblesse de l'API
formation_id=f["formation_id"], # faiblesse de l'API
)
# --- Mise place d'un semestre
sem = G.create_formsemestre(
formation_id=f["formation_id"],
semestre_id=1,
date_debut="01/01/2020",
date_fin="30/06/2020",
)
mi = G.create_moduleimpl(
module_id=mod["module_id"],
formsemestre_id=sem["formsemestre_id"],
responsable_id="bach",
)
# --- Inscription des étudiants
for etud in etuds:
G.inscrit_etudiant(sem, etud)
# --- Creation évaluation
e = G.create_evaluation(
moduleimpl_id=mi["moduleimpl_id"],
jour="01/01/2020",
description="evaluation test",
coefficient=1.0,
)
# --- Saisie notes
for etud in etuds:
nb_changed, nb_suppress, existing_decisions = G.create_note(
evaluation=e, etud=etud, note=float(random.randint(0, 20))
)

View File

@ -1,68 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Test notes bonus/malus
Création 10 étudiants, puis formation en 4 semestre.
Le premier étudiant redouble sa deuxième année.
Utiliser comme:
scotests/scointeractive.sh -r TEST00 scotests/test_bonusmalus.py
"""
import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error
import sco_utils as scu
G = sco_fake_gen.ScoFake(context.Notes) # pylint: disable=undefined-variable
G.verbose = False
# --- Création d'étudiants
etuds = [G.create_etud(code_nip=None) for _ in range(10)]
# --- Mise en place formation 1 semestre
f, ue_list, mod_list = G.setup_formation(nb_semestre=1)
# --- Ajoute module malus à la premiere matiere de la première UE
mod_malus = G.create_module(
titre="MALUS",
code="MAL",
coefficient=10,
ue_id=ue_list[0]["ue_id"],
matiere_id=mod_list[0]["matiere_id"],
formation_id=f["formation_id"],
semestre_id=1,
module_type=scu.MODULE_MALUS,
)
mod_list.append(mod_malus)
# --- Crée le semestre
semestre_id, date_debut, date_fin = (1, "01/09/2019", "15/01/2020")
sem, eval_list = G.setup_formsemestre(
f, mod_list, semestre_id=semestre_id, date_debut=date_debut, date_fin=date_fin
)
# --- Recupère le module de malus
modimpls = context.Notes.do_moduleimpl_list(formsemestre_id=sem["formsemestre_id"])
# de façon tout à fait inefficace ;-)
moduleimpl_malus = [m for m in modimpls if m["module_id"] == mod_malus["module_id"]][0]
# et l'évaluation de malus, de la même façon:
eval_malus = [
e for e in eval_list if e["moduleimpl_id"] == moduleimpl_malus["moduleimpl_id"]
][0]
eval_normales = [
e for e in eval_list if e["moduleimpl_id"] != moduleimpl_malus["moduleimpl_id"]
]
# --- Affect des malus entre -10 et +10
n = len(etuds)
malus = [((x / (n - 1.0)) * 20) - 10 for x in range(n)]
for etud, note in zip(etuds, malus):
G.create_note(evaluation=e, etud=etud, note=note)
# --- Inscrit les étudiants et affecte des notes aléatoires aux évaluations normales
for etud in etuds:
G.inscrit_etudiant(sem, etud)
G.set_etud_notes_sem(sem, eval_normales, etuds)

View File

@ -1,78 +0,0 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
"""Test jury et capitalisation
Création 10 étudiants, puis formation en 4 semestre.
Le premier étudiant redouble sa deuxième année.
Utiliser comme:
scotests/scointeractive.sh -r TEST00 scotests/test_capitalisation.py
"""
import scotests.sco_fake_gen as sco_fake_gen # pylint: disable=import-error
import sco_utils
import sco_codes_parcours
G = sco_fake_gen.ScoFake(context.Notes) # pylint: disable=undefined-variable
G.verbose = False
# --- Création d'étudiants
etuds = [G.create_etud(code_nip=None) for _ in range(10)]
# --- Mise en place formation 4 semestre
f, ue_list, mod_list = G.setup_formation(nb_semestre=4)
# --- Crée les 4 semestre et affecte des notes aléatoires
sems, evals = [], []
for semestre_id, date_debut, date_fin in [
(1, "01/09/2019", "15/01/2020"),
(2, "16/01/2020", "30/06/2020"),
(3, "01/09/2020", "15/01/2021"),
(4, "16/01/2021", "30/06/2021"),
]:
sem, eval_list = G.setup_formsemestre(
f, mod_list, semestre_id=semestre_id, date_debut=date_debut, date_fin=date_fin
)
sems.append(sem)
evals.append(eval_list) # liste des listes d'evaluations
for etud in etuds:
G.inscrit_etudiant(sem, etud)
G.set_etud_notes_sem(sem, eval_list, etuds)
# def evals_premiere_ue(eval_list):
# return
# Le premier étudiant va redoubler sa deuxième année
# on crée 2 semestres supplémentaires auxquels on n'inscrit que lui
# puis on ajuste ses notes de S3, S4 et S3bis, S4bis
etud = etuds[0] # l'étudiant redoublant
for semestre_id, date_debut, date_fin in [
(3, "01/09/2022", "15/01/2023"),
(4, "16/01/2023", "30/06/2023"),
]:
sem, eval_list = G.setup_formsemestre(
f, mod_list, semestre_id=semestre_id, date_debut=date_debut, date_fin=date_fin
)
sems.append(sem)
evals.append(eval_list) # liste des listes d'evaluations
G.inscrit_etudiant(sem, etud)
# Donne 11.5 aux evals UE1 de son premier S3:
G.set_etud_notes_sem(sems[3 - 1], evals[3 - 1][:2], [etud], [11.5])
# et 8 aux evals de l'UE2:
G.set_etud_notes_sem(sems[3 - 1], evals[3 - 1][2:], [etud], [8.0])
# et 9 en UE1 de son second S3:
# G.set_etud_notes_sem(sems[5 - 1], evals[5 - 1][:2], [etud], [9.0])
# et 12 en U2 de son second S3:
G.set_etud_notes_sem(sems[5 - 1], evals[5 - 1][2:], [etud], [12.0])
# Jury: S1/ADM, S2/ADJ, S3/AJ, S4/AJ
G.set_code_jury(sems[0], etud, code_etat=sco_codes_parcours.ADM)
G.set_code_jury(sems[1], etud, code_etat=sco_codes_parcours.ADJ)
G.set_code_jury(sems[2], etud, code_etat=sco_codes_parcours.AJ)
G.set_code_jury(sems[3], etud, code_etat=sco_codes_parcours.AJ)

View File

@ -1338,7 +1338,6 @@ span.evalindex_cont {
} }
span.evalindex { span.evalindex {
font-weight: normal; font-weight: normal;
font-size: 80%;
margin-right: 5px; margin-right: 5px;
} }
.eval_arrows_chld { .eval_arrows_chld {

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Some files were not shown because too many files have changed in this diff Show More