993 lines
36 KiB
Python
993 lines
36 KiB
Python
# -*- mode: python -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
#
|
|
# Gestion scolarite IUT
|
|
#
|
|
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
|
#
|
|
##############################################################################
|
|
|
|
"""Site ScoDoc pour plusieurs departements:
|
|
gestion de l'installation et des creation de départements.
|
|
|
|
Chaque departement est géré par un ZScolar sous ZScoDoc.
|
|
"""
|
|
|
|
import time
|
|
import datetime
|
|
import string
|
|
import glob
|
|
import re
|
|
import inspect
|
|
import urllib
|
|
import urllib2
|
|
import cgi
|
|
import xml
|
|
|
|
from cStringIO import StringIO
|
|
from zipfile import ZipFile
|
|
import os.path
|
|
import traceback
|
|
|
|
from email.MIMEMultipart import ( # pylint: disable=no-name-in-module,import-error
|
|
MIMEMultipart,
|
|
)
|
|
from email.MIMEText import MIMEText # pylint: disable=no-name-in-module,import-error
|
|
from email.MIMEBase import MIMEBase # pylint: disable=no-name-in-module,import-error
|
|
from email.Header import Header # pylint: disable=no-name-in-module,import-error
|
|
from email import Encoders # pylint: disable=no-name-in-module,import-error
|
|
|
|
from sco_zope import * # pylint: disable=unused-wildcard-import
|
|
|
|
try:
|
|
import Products.ZPsycopgDA.DA as ZopeDA
|
|
except:
|
|
import ZPsycopgDA.DA as ZopeDA # interp.py
|
|
|
|
import sco_utils as scu
|
|
import VERSION
|
|
from notes_log import log
|
|
import sco_find_etud
|
|
import sco_users
|
|
from sco_permissions import (
|
|
ScoView,
|
|
ScoEnsView,
|
|
ScoImplement,
|
|
ScoChangeFormation,
|
|
ScoObservateur,
|
|
ScoEtudInscrit,
|
|
ScoEtudChangeGroups,
|
|
ScoEtudChangeAdr,
|
|
ScoEtudSupprAnnotations,
|
|
ScoEditAllEvals,
|
|
ScoEditAllNotes,
|
|
ScoEditFormationTags,
|
|
ScoEditApo,
|
|
ScoSuperAdmin,
|
|
)
|
|
from sco_exceptions import ScoValueError, ScoLockedFormError, ScoGenError, AccessDenied
|
|
|
|
|
|
class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit):
|
|
|
|
"ZScoDoc object"
|
|
|
|
meta_type = "ZScoDoc"
|
|
security = ClassSecurityInfo()
|
|
file_path = Globals.package_home(globals())
|
|
|
|
# This is the list of the methods associated to 'tabs' in the ZMI
|
|
# Be aware that The first in the list is the one shown by default, so if
|
|
# the 'View' tab is the first, you will never see your tabs by cliquing
|
|
# on the object.
|
|
manage_options = (
|
|
({"label": "Contents", "action": "manage_main"},)
|
|
+ PropertyManager.manage_options # add the 'Properties' tab
|
|
+ ({"label": "View", "action": "index_html"},)
|
|
+ Item.manage_options # add the 'Undo' & 'Owner' tab
|
|
+ RoleManager.manage_options # add the 'Security' tab
|
|
)
|
|
|
|
def __init__(self, id, title):
|
|
"Initialise a new instance of ZScoDoc"
|
|
self.id = id
|
|
self.title = title
|
|
self.manage_addProperty("admin_password_initialized", "0", "string")
|
|
|
|
security.declareProtected(ScoView, "ScoDocURL")
|
|
|
|
def ScoDocURL(self):
|
|
"base URL for this instance (top level for ScoDoc site)"
|
|
return self.absolute_url()
|
|
|
|
def _check_admin_perm(self, REQUEST):
|
|
"""Check if user has permission to add/delete departements"""
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
if authuser.has_role("manager") or authuser.has_permission(ScoSuperAdmin, self):
|
|
return ""
|
|
else:
|
|
return """<h2>Vous n'avez pas le droit d'accéder à cette page</h2>"""
|
|
|
|
def _check_users_folder(self, REQUEST=None):
|
|
"""Vérifie UserFolder et le crée s'il le faut"""
|
|
try:
|
|
_ = self.UsersDB
|
|
return "<!-- uf ok -->"
|
|
except:
|
|
e = self._check_admin_perm(REQUEST)
|
|
if not e: # admin permissions:
|
|
self.create_users_cnx(REQUEST)
|
|
self.create_users_folder(REQUEST)
|
|
return '<div class="head_message">Création du connecteur utilisateurs réussie</div>'
|
|
else:
|
|
return """<div class="head_message">Installation non terminée: connectez vous avec les droits d'administrateur</div>"""
|
|
|
|
security.declareProtected("View", "create_users_folder")
|
|
|
|
def create_users_folder(self, REQUEST=None):
|
|
"""Create Zope user folder"""
|
|
e = self._check_admin_perm(REQUEST)
|
|
if e:
|
|
return e
|
|
|
|
if REQUEST is None:
|
|
REQUEST = {}
|
|
|
|
REQUEST.form["pgauth_connection"] = "UsersDB"
|
|
REQUEST.form["pgauth_table"] = "sco_users"
|
|
REQUEST.form["pgauth_usernameColumn"] = "user_name"
|
|
REQUEST.form["pgauth_passwordColumn"] = "passwd"
|
|
REQUEST.form["pgauth_rolesColumn"] = "roles"
|
|
|
|
add_method = self.manage_addProduct["OFSP"].manage_addexUserFolder
|
|
log("create_users_folder: in %s" % self.id)
|
|
return add_method(
|
|
authId="pgAuthSource",
|
|
propId="nullPropSource",
|
|
memberId="nullMemberSource",
|
|
groupId="nullGroupSource",
|
|
cryptoId="MD51",
|
|
# doAuth='1', doProp='1', doMember='1', doGroup='1', allDone='1',
|
|
cookie_mode=2,
|
|
session_length=500,
|
|
not_session_length=0,
|
|
REQUEST=REQUEST,
|
|
)
|
|
|
|
def _fix_users_folder(self):
|
|
"""removes docLogin and docLogout dtml methods from exUserFolder, so that we use ours.
|
|
(called each time be index_html, to fix old ScoDoc installations.)
|
|
"""
|
|
try:
|
|
self.acl_users.manage_delObjects(ids=["docLogin", "docLogout"])
|
|
except:
|
|
pass
|
|
# add missing getAuthFailedMessage (bug in exUserFolder ?)
|
|
try:
|
|
_ = self.getAuthFailedMessage
|
|
except:
|
|
log("adding getAuthFailedMessage to Zope install")
|
|
parent = self.aq_parent
|
|
from OFS.DTMLMethod import addDTMLMethod # pylint: disable=import-error
|
|
|
|
addDTMLMethod(parent, "getAuthFailedMessage", file="Identification")
|
|
|
|
security.declareProtected("View", "create_users_cnx")
|
|
|
|
def create_users_cnx(self, REQUEST=None):
|
|
"""Create Zope connector to UsersDB
|
|
|
|
Note: la connexion est fixée (SCOUSERS) (base crée par l'installeur) !
|
|
Les utilisateurs avancés pourront la changer ensuite.
|
|
"""
|
|
# ce connecteur zope - db est encore pour l'instant utilisé par exUserFolder.pgAuthSource
|
|
# (en lecture seule en principe)
|
|
oid = "UsersDB"
|
|
log("create_users_cnx: in %s" % self.id)
|
|
da = ZopeDA.Connection(
|
|
oid,
|
|
"Cnx bd utilisateurs",
|
|
scu.SCO_DEFAULT_SQL_USERS_CNX,
|
|
False,
|
|
check=1,
|
|
tilevel=2,
|
|
encoding="LATIN1",
|
|
)
|
|
self._setObject(oid, da)
|
|
|
|
security.declareProtected("View", "change_admin_user")
|
|
|
|
def change_admin_user(self, password, REQUEST=None):
|
|
"""Change password of admin user"""
|
|
# note: controle sur le role et non pas sur une permission
|
|
# (non definies au top level)
|
|
if not REQUEST.AUTHENTICATED_USER.has_role("Manager"):
|
|
log("user %s is not Manager" % REQUEST.AUTHENTICATED_USER)
|
|
log("roles=%s" % REQUEST.AUTHENTICATED_USER.getRolesInContext(self))
|
|
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
|
|
log("trying to change admin password")
|
|
# 1-- check strong password
|
|
if not sco_users.is_valid_password(password):
|
|
log("refusing weak password")
|
|
return REQUEST.RESPONSE.redirect(
|
|
"change_admin_user_form?message=Mot%20de%20passe%20trop%20simple,%20recommencez"
|
|
)
|
|
# 2-- change password for admin user
|
|
username = "admin"
|
|
acl_users = self.aq_parent.acl_users
|
|
user = acl_users.getUser(username)
|
|
r = acl_users._changeUser(
|
|
username, password, password, user.roles, user.domains
|
|
)
|
|
if not r:
|
|
# OK, set property to indicate we changed the password
|
|
log("admin password changed successfully")
|
|
self.manage_changeProperties(admin_password_initialized="1")
|
|
return r or REQUEST.RESPONSE.redirect("index_html")
|
|
|
|
security.declareProtected("View", "change_admin_user_form")
|
|
|
|
def change_admin_user_form(self, message="", REQUEST=None):
|
|
"""Form allowing to change the ScoDoc admin password"""
|
|
# note: controle sur le role et non pas sur une permission
|
|
# (non definies au top level)
|
|
if not REQUEST.AUTHENTICATED_USER.has_role("Manager"):
|
|
raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération")
|
|
H = [
|
|
self.scodoc_top_html_header(
|
|
REQUEST, page_title="ScoDoc: changement mot de passe"
|
|
)
|
|
]
|
|
if message:
|
|
H.append('<div id="message">%s</div>' % message)
|
|
H.append(
|
|
"""<h2>Changement du mot de passe administrateur (utilisateur admin)</h2>
|
|
<p>
|
|
<form action="change_admin_user" method="post"><table>
|
|
<tr><td>Nouveau mot de passe:</td><td><input type="password" size="14" name="password"/></td></tr>
|
|
<tr><td>Confirmation: </td><td><input type="password" size="14" name="password2" /></td></tr>
|
|
</table>
|
|
<input type="submit" value="Changer">
|
|
"""
|
|
)
|
|
H.append("""</body></html>""")
|
|
return "\n".join(H)
|
|
|
|
security.declareProtected("View", "list_depts")
|
|
|
|
def list_depts(self, viewable=True, format=None, REQUEST=None):
|
|
"""List departments
|
|
If viewable, list only depts viewable the current user.
|
|
"""
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
viewable = int(viewable)
|
|
return scu.sendResult(
|
|
REQUEST,
|
|
[
|
|
d.id
|
|
for d in self._list_depts()
|
|
if (not viewable) or authuser.has_permission(ScoView, d.Scolarite)
|
|
],
|
|
name="depts",
|
|
format=format,
|
|
)
|
|
|
|
def _list_depts(self, REQUEST=None): # not published
|
|
# List departments folders
|
|
# (returns a list of Zope folders containing a ZScolar instance)
|
|
folders = self.objectValues("Folder")
|
|
# select folders with Scolarite object:
|
|
r = []
|
|
for folder in folders:
|
|
try:
|
|
_ = folder.Scolarite
|
|
r.append(folder)
|
|
except:
|
|
pass
|
|
return r
|
|
|
|
security.declareProtected("View", "create_dept")
|
|
|
|
def create_dept(self, REQUEST=None, DeptId="", pass2=False):
|
|
"""Creation (ajout) d'un site departement
|
|
(instance ZScolar + dossier la contenant)
|
|
"""
|
|
e = self._check_admin_perm(REQUEST)
|
|
if e:
|
|
return e
|
|
|
|
if not DeptId:
|
|
raise ValueError("nom de departement invalide")
|
|
if not pass2:
|
|
# 1- Creation de repertoire Dept
|
|
log("creating Zope folder " + DeptId)
|
|
add_method = self.manage_addProduct["OFSP"].manage_addFolder
|
|
add_method(DeptId, title="Site dept. " + DeptId)
|
|
|
|
DeptFolder = self[DeptId]
|
|
|
|
if not pass2:
|
|
# 2- Creation du repertoire Fotos
|
|
log("creating Zope folder %s/Fotos" % DeptId)
|
|
add_method = DeptFolder.manage_addProduct["OFSP"].manage_addFolder
|
|
add_method("Fotos", title="Photos identites " + DeptId)
|
|
|
|
# 3- Creation instance ScoDoc
|
|
log("creating Zope ZScolar instance")
|
|
add_method = DeptFolder.manage_addProduct["ScoDoc"].manage_addZScolarForm
|
|
return add_method(DeptId, REQUEST=REQUEST)
|
|
|
|
security.declareProtected("View", "delete_dept")
|
|
|
|
def delete_dept(self, REQUEST=None, DeptId="", force=False):
|
|
"""Supprime un departement (de Zope seulement, ne touche pas la BD)"""
|
|
e = self._check_admin_perm(REQUEST)
|
|
if e:
|
|
return e
|
|
|
|
if not force and DeptId not in [x.id for x in self._list_depts()]:
|
|
raise ValueError("nom de departement invalide")
|
|
|
|
self.manage_delObjects(ids=[DeptId])
|
|
|
|
return (
|
|
"<p>Département "
|
|
+ DeptId
|
|
+ """ supprimé du serveur web (la base de données n'est pas affectée)!</p><p><a href="/ScoDoc">Continuer</a></p>"""
|
|
)
|
|
|
|
_top_level_css = """
|
|
<style type="text/css">
|
|
</style>"""
|
|
|
|
_html_begin = """<?xml version="1.0" encoding="%(encoding)s"?>
|
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<title>%(page_title)s</title>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=%(encoding)s" />
|
|
<meta http-equiv="Content-Style-Type" content="text/css" />
|
|
<meta name="LANG" content="fr" />
|
|
<meta name="DESCRIPTION" content="ScoDoc" />
|
|
|
|
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/css/smoothness/jquery-ui-1.10.4.custom.min.css" />
|
|
|
|
<link href="/ScoDoc/static/css/scodoc.css" rel="stylesheet" type="text/css" />
|
|
<link href="/ScoDoc/static/css/menu.css" rel="stylesheet" type="text/css" />
|
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/menu.js"></script>
|
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/sorttable.js"></script>
|
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/bubble.js"></script>
|
|
<script type="text/javascript">
|
|
window.onload=function(){enableTooltips("gtrcontent")};
|
|
</script>
|
|
|
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery.js"></script>
|
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/jQuery/jquery-migrate-1.2.0.min.js"></script>
|
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery.field.min.js"></script>
|
|
|
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min.js"></script>
|
|
|
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
|
<link type="text/css" rel="stylesheet" href="/ScoDoc/static/libjs/qtip/jquery.qtip-3.0.3.min.css" />
|
|
|
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/scodoc.js"></script>
|
|
<script language="javascript" type="text/javascript" src="/ScoDoc/static/js/etud_info.js"></script>
|
|
"""
|
|
|
|
def scodoc_top_html_header(self, REQUEST, page_title="ScoDoc"):
|
|
H = [
|
|
self._html_begin
|
|
% {"page_title": "ScoDoc: bienvenue", "encoding": scu.SCO_ENCODING},
|
|
self._top_level_css,
|
|
"""</head><body class="gtrcontent" id="gtrcontent">""",
|
|
scu.CUSTOM_HTML_HEADER_CNX,
|
|
]
|
|
return "\n".join(H)
|
|
|
|
security.declareProtected("View", "index_html")
|
|
|
|
def index_html(self, REQUEST=None, message=None):
|
|
"""Top level page for ScoDoc"""
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
deptList = self._list_depts()
|
|
self._fix_users_folder() # fix our exUserFolder
|
|
isAdmin = not self._check_admin_perm(REQUEST)
|
|
try:
|
|
admin_password_initialized = self.admin_password_initialized
|
|
except:
|
|
admin_password_initialized = "0"
|
|
if isAdmin and admin_password_initialized != "1":
|
|
return REQUEST.RESPONSE.redirect(
|
|
"ScoDoc/change_admin_user_form?message=Le%20mot%20de%20passe%20administrateur%20doit%20etre%20change%20!"
|
|
)
|
|
|
|
# Si l'URL indique que l'on est dans un folder, affiche page login du departement
|
|
try:
|
|
deptfoldername = REQUEST.URL0.split("ScoDoc")[1].split("/")[1]
|
|
if deptfoldername in [x.id for x in self._list_depts()]:
|
|
return self.index_dept(deptfoldername=deptfoldername, REQUEST=REQUEST)
|
|
except:
|
|
pass
|
|
|
|
H = [
|
|
self.scodoc_top_html_header(REQUEST, page_title="ScoDoc: bienvenue"),
|
|
self._check_users_folder(REQUEST=REQUEST), # ensure setup is done
|
|
]
|
|
if message:
|
|
H.append('<div id="message">%s</div>' % message)
|
|
|
|
if isAdmin and not message:
|
|
H.append('<div id="message">Attention: connecté comme administrateur</div>')
|
|
|
|
H.append(
|
|
"""
|
|
<div class="maindiv">
|
|
<h2>ScoDoc: gestion scolarité</h2>
|
|
"""
|
|
)
|
|
if authuser.has_role("Authenticated"):
|
|
H.append(
|
|
"""<p>Bonjour <font color="red"><b>%s</b></font>.</p>""" % str(authuser)
|
|
)
|
|
H.append(
|
|
"""<p>N'oubliez pas de vous <a href="acl_users/logout">déconnecter</a> après usage.</p>"""
|
|
)
|
|
else:
|
|
H.append(
|
|
"""<p>Ce site est <font color="red"><b>réservé au personnel autorisé</b></font></p>"""
|
|
)
|
|
H.append(self.authentication_form(destination="."))
|
|
|
|
if not deptList:
|
|
H.append("<em>aucun département existant !</em>")
|
|
# si pas de dept et pas admin, propose lien pour loger admin
|
|
if not isAdmin:
|
|
H.append(
|
|
"""<p><a href="/force_admin_authentication">Identifiez vous comme administrateur</a> (au début: nom 'admin', mot de passe 'scodoc')</p>"""
|
|
)
|
|
else:
|
|
H.append('<ul class="main">')
|
|
if isAdmin:
|
|
dest_folder = "/Scolarite"
|
|
else:
|
|
dest_folder = ""
|
|
for deptFolder in self._list_depts():
|
|
if authuser.has_permission(ScoView, deptFolder.Scolarite):
|
|
link_cls = "link_accessible"
|
|
else:
|
|
link_cls = "link_unauthorized"
|
|
# Essai de recuperer le nom du departement dans ses preferences
|
|
try:
|
|
DeptName = (
|
|
deptFolder.Scolarite.get_preference("DeptName") or deptFolder.id
|
|
)
|
|
except:
|
|
DeptName = deptFolder.id
|
|
H.append(
|
|
'<li><a class="stdlink %s" href="%s%s">Département %s</a>'
|
|
% (link_cls, deptFolder.absolute_url(), dest_folder, DeptName)
|
|
)
|
|
# check if roles are initialized in this depts, and do it if necessary
|
|
if deptFolder.Scolarite.roles_initialized == "0":
|
|
if isAdmin:
|
|
deptFolder.Scolarite._setup_initial_roles_and_permissions()
|
|
else:
|
|
H.append(" (non initialisé, connectez vous comme admin)")
|
|
H.append("</li>")
|
|
H.append("</ul>")
|
|
# Recherche etudiant
|
|
H.append(sco_find_etud.form_search_etud_in_accessible_depts(self, REQUEST))
|
|
|
|
if isAdmin:
|
|
H.append('<p><a href="scodoc_admin">Administration de ScoDoc</a></p>')
|
|
else:
|
|
H.append(
|
|
'<p><a href="%s/force_admin_authentication">Se connecter comme administrateur</a></p>'
|
|
% REQUEST.BASE0
|
|
)
|
|
|
|
H.append(
|
|
"""
|
|
<div id="scodoc_attribution">
|
|
<p><a href="%s">ScoDoc</a> est un logiciel libre de suivi de la scolarité des étudiants conçu par
|
|
E. Viennet (Université Paris 13).</p>
|
|
</div>
|
|
</div>"""
|
|
% (scu.SCO_WEBSITE,)
|
|
)
|
|
|
|
H.append("""</body></html>""")
|
|
return "\n".join(H)
|
|
|
|
def authentication_form(self, destination=""):
|
|
"""html snippet for authentication"""
|
|
return (
|
|
"""<!-- authentication_form -->
|
|
<form action="doLogin" method="post">
|
|
<input type="hidden" name="destination" value="%s"/>
|
|
<p>
|
|
<table border="0" cellpadding="3">
|
|
<tr>
|
|
<td><b>Nom:</b></td>
|
|
<td><input id="name" type="text" name="__ac_name" size="20"/></td>
|
|
</tr><tr>
|
|
<td><b>Mot de passe:</b></td>
|
|
<td><input id="password" type="password" name="__ac_password" size="20"/></td>
|
|
<td><input id="submit" name="submit" type="submit" value="OK"/></td>
|
|
</tr>
|
|
</table>
|
|
</p>
|
|
</form>"""
|
|
% destination
|
|
)
|
|
|
|
security.declareProtected("View", "index_dept")
|
|
|
|
def index_dept(self, deptfoldername="", REQUEST=None):
|
|
"""Page d'accueil departement"""
|
|
authuser = REQUEST.AUTHENTICATED_USER
|
|
try:
|
|
dept = getattr(self, deptfoldername)
|
|
if authuser.has_permission(ScoView, dept):
|
|
return REQUEST.RESPONSE.redirect("ScoDoc/%s/Scolarite" % deptfoldername)
|
|
except:
|
|
log(
|
|
"*** problem in index_dept (%s) user=%s"
|
|
% (deptfoldername, str(authuser))
|
|
)
|
|
|
|
H = [
|
|
self.standard_html_header(REQUEST),
|
|
"""<div style="margin: 1em;">
|
|
<h2>Scolarité du département %s</h2>"""
|
|
% deptfoldername,
|
|
"""<p>Ce site est
|
|
<font color="#FF0000"><b>réservé au personnel du département</b></font>.
|
|
</p>""",
|
|
self.authentication_form(destination="Scolarite"),
|
|
"""
|
|
<p>Pour quitter, <a href="acl_users/logout">logout</a></p>
|
|
<p><a href="%s">Retour à l'accueil</a></p>
|
|
</div>
|
|
"""
|
|
% self.ScoDocURL(),
|
|
self.standard_html_footer(REQUEST),
|
|
]
|
|
return "\n".join(H)
|
|
|
|
security.declareProtected("View", "doLogin")
|
|
|
|
def doLogin(self, REQUEST=None, destination=None):
|
|
"redirect to destination after login"
|
|
if destination:
|
|
return REQUEST.RESPONSE.redirect(destination)
|
|
|
|
security.declareProtected("View", "docLogin")
|
|
docLogin = DTMLFile("dtml/docLogin", globals())
|
|
security.declareProtected("View", "docLogout")
|
|
docLogout = DTMLFile("dtml/docLogout", globals())
|
|
|
|
security.declareProtected("View", "query_string_to_form_inputs")
|
|
|
|
def query_string_to_form_inputs(self, query_string=""):
|
|
"""Return html snippet representing the query string as POST form hidden inputs.
|
|
This is useful in conjonction with exUserfolder to correctly redirect the response
|
|
after authentication.
|
|
"""
|
|
H = []
|
|
for a in query_string.split("&"):
|
|
if a:
|
|
nv = a.split("=")
|
|
if len(nv) == 2:
|
|
name, value = nv
|
|
H.append(
|
|
'<input type="hidden" name="'
|
|
+ name
|
|
+ '" value="'
|
|
+ value
|
|
+ '"/>'
|
|
)
|
|
|
|
return "<!-- query string -->\n" + "\n".join(H)
|
|
|
|
security.declareProtected("View", "standard_html_header")
|
|
|
|
def standard_html_header(self, REQUEST=None):
|
|
"""Standard HTML header for pages outside depts"""
|
|
# not used in ZScolar, see sco_header
|
|
return """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
|
<html><head>
|
|
<title>ScoDoc: accueil</title>
|
|
<META http-equiv="Content-Type" content="text/html; charset=%s">
|
|
<META http-equiv="Content-Style-Type" content="text/css">
|
|
<META name="LANG" content="fr">
|
|
<META name="DESCRIPTION" content="ScoDoc: gestion scolarite">
|
|
|
|
<link HREF="/ScoDoc/static/css/scodoc.css" rel="stylesheet" type="text/css"/>
|
|
|
|
</head><body>%s""" % (
|
|
scu.SCO_ENCODING,
|
|
scu.CUSTOM_HTML_HEADER_CNX,
|
|
)
|
|
|
|
security.declareProtected("View", "standard_html_footer")
|
|
|
|
def standard_html_footer(self, REQUEST=None):
|
|
"""Le pied de page HTML de la page d'accueil."""
|
|
return """<p class="footer">
|
|
Problème de connexion (identifiant, mot de passe): <em>contacter votre responsable ou chef de département</em>.</p>
|
|
<p>Problèmes et suggestions sur le logiciel: <a href="mailto:%s">%s</a></p>
|
|
<p><em>ScoDoc est un logiciel libre développé par Emmanuel Viennet.</em></p>
|
|
</body></html>""" % (
|
|
scu.SCO_USERS_LIST,
|
|
scu.SCO_USERS_LIST,
|
|
)
|
|
|
|
# sendEmail is not used through the web
|
|
def sendEmail(self, msg):
|
|
# sends an email to the address using the mailhost, if there is one
|
|
try:
|
|
mail_host = self.MailHost
|
|
except:
|
|
log("warning: sendEmail: no MailHost found !")
|
|
return
|
|
# a failed notification shouldn't cause a Zope error on a site.
|
|
try:
|
|
mail_host.send(msg.as_string())
|
|
log("sendEmail: ok")
|
|
except Exception as e:
|
|
log("sendEmail: exception while sending message")
|
|
log(e)
|
|
pass
|
|
|
|
def sendEmailFromException(self, msg):
|
|
# Send email by hand, as it seems to be not possible to use Zope Mail Host
|
|
# from an exception handler (see https://bugs.launchpad.net/zope2/+bug/246748)
|
|
log("sendEmailFromException")
|
|
try:
|
|
p = os.popen("sendmail -t", "w") # old brute force method
|
|
p.write(msg.as_string())
|
|
exitcode = p.close()
|
|
if exitcode:
|
|
log("sendmail exit code: %s" % exitcode)
|
|
except:
|
|
log("an exception occurred sending mail")
|
|
|
|
security.declareProtected("View", "standard_error_message")
|
|
|
|
def standard_error_message(
|
|
self,
|
|
error_value=None,
|
|
error_message=None, # unused ?
|
|
error_type=None,
|
|
error_traceback=None,
|
|
error_tb=None,
|
|
**kv
|
|
):
|
|
"Recuperation des exceptions Zope"
|
|
# neat (or should I say dirty ?) hack to get REQUEST
|
|
# 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.
|
|
try:
|
|
frame = inspect.currentframe()
|
|
REQUEST = frame.f_back.f_locals["REQUEST"]
|
|
except:
|
|
REQUEST = {}
|
|
|
|
# Authentication uses exceptions, pass them up
|
|
HTTP_X_FORWARDED_FOR = REQUEST.get("HTTP_X_FORWARDED_FOR", "")
|
|
if error_type == "LoginRequired":
|
|
log("LoginRequired from %s" % HTTP_X_FORWARDED_FOR)
|
|
self.login_page = error_value
|
|
return error_value
|
|
elif error_type == "Unauthorized":
|
|
log("Unauthorized from %s" % HTTP_X_FORWARDED_FOR)
|
|
return self.acl_users.docLogin(self, REQUEST=REQUEST)
|
|
|
|
log("exception caught: %s" % error_type)
|
|
log(traceback.format_exc())
|
|
|
|
params = {
|
|
"error_type": error_type,
|
|
"error_value": error_value,
|
|
"error_tb": error_tb,
|
|
"sco_exc_mail": scu.SCO_EXC_MAIL,
|
|
"sco_dev_mail": scu.SCO_DEV_MAIL,
|
|
}
|
|
|
|
if error_type == "ScoGenError":
|
|
return "<p>" + str(error_value) + "</p>"
|
|
elif error_type in ("ScoValueError", "FormatError"):
|
|
# Not a bug, presents a gentle message to the user:
|
|
H = [
|
|
self.standard_html_header(REQUEST),
|
|
"""<h2>Erreur !</h2><p>%s</p>""" % error_value,
|
|
]
|
|
if error_value.dest_url:
|
|
H.append('<p><a href="%s">Continuer</a></p>' % error_value.dest_url)
|
|
H.append(self.standard_html_footer(REQUEST))
|
|
return "\n".join(H)
|
|
else: # Other exceptions, try carefully to build an error page...
|
|
# log('exc A')
|
|
H = []
|
|
try:
|
|
H.append(self.standard_html_header(REQUEST))
|
|
except:
|
|
pass
|
|
H.append(
|
|
"""<table border="0" width="100%%"><tr valign="top">
|
|
<td width="10%%" align="center"></td>
|
|
<td width="90%%"><h2>Erreur !</h2>
|
|
<p>Une erreur est survenue</p>
|
|
<p>
|
|
<strong>Error Type: %(error_type)s</strong><br>
|
|
<strong>Error Value: %(error_value)s</strong><br>
|
|
</p>
|
|
<hr noshade>
|
|
<p>L'URL est peut-etre incorrecte ?</p>
|
|
|
|
<p>Si l'erreur persiste, contactez Emmanuel Viennet:
|
|
<a href="mailto:%(sco_dev_mail)s">%(sco_dev_mail)s</a>
|
|
en copiant ce message d'erreur et le contenu du cadre bleu ci-dessous si possible.
|
|
</p>
|
|
</td></tr>
|
|
</table> """
|
|
% params
|
|
)
|
|
# display error traceback (? may open a security risk via xss attack ?)
|
|
# log('exc B')
|
|
params["txt_html"] = self._report_request(REQUEST, fmt="html")
|
|
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;">
|
|
%(error_tb)s
|
|
<p><b>Informations:</b><br/>
|
|
%(txt_html)s
|
|
</p>
|
|
</div>
|
|
|
|
<p>Merci de votre patience !</p>
|
|
"""
|
|
% params
|
|
)
|
|
try:
|
|
H.append(self.standard_html_footer(REQUEST))
|
|
except:
|
|
log("no footer found for error page")
|
|
pass
|
|
|
|
# --- Mail:
|
|
params["error_traceback_txt"] = scu.scodoc_html2txt(error_tb)
|
|
txt = (
|
|
"""
|
|
ErrorType: %(error_type)s
|
|
|
|
%(error_traceback_txt)s
|
|
"""
|
|
% params
|
|
)
|
|
|
|
self.send_debug_alert(txt, REQUEST=REQUEST)
|
|
# ---
|
|
log("done processing exception")
|
|
# log( '\n page=\n' + '\n'.join(H) )
|
|
return "\n".join(H)
|
|
|
|
def _report_request(self, REQUEST, fmt="txt"):
|
|
"""string describing current request for bug reports"""
|
|
QUERY_STRING = REQUEST.get("QUERY_STRING", "")
|
|
if QUERY_STRING:
|
|
QUERY_STRING = "?" + QUERY_STRING
|
|
if fmt == "txt":
|
|
REFERER = REQUEST.get("HTTP_REFERER", "")
|
|
HTTP_USER_AGENT = REQUEST.get("HTTP_USER_AGENT", "")
|
|
else:
|
|
REFERER = "na"
|
|
HTTP_USER_AGENT = "na"
|
|
|
|
params = dict(
|
|
AUTHENTICATED_USER=REQUEST.get("AUTHENTICATED_USER", ""),
|
|
dt=time.asctime(),
|
|
URL=REQUEST.get("URL", ""),
|
|
QUERY_STRING=QUERY_STRING,
|
|
METHOD=REQUEST.get("REQUEST_METHOD", ""),
|
|
REFERER=REFERER,
|
|
HTTP_USER_AGENT=HTTP_USER_AGENT,
|
|
form=REQUEST.get("form", ""),
|
|
HTTP_X_FORWARDED_FOR=REQUEST.get("HTTP_X_FORWARDED_FOR", ""),
|
|
svn_version=scu.get_svn_version(self.file_path),
|
|
SCOVERSION=VERSION.SCOVERSION,
|
|
)
|
|
txt = (
|
|
"""
|
|
Version: %(SCOVERSION)s
|
|
User: %(AUTHENTICATED_USER)s
|
|
Date: %(dt)s
|
|
URL: %(URL)s%(QUERY_STRING)s
|
|
Method: %(METHOD)s
|
|
|
|
REFERER: %(REFERER)s
|
|
Form: %(form)s
|
|
Origin: %(HTTP_X_FORWARDED_FOR)s
|
|
Agent: %(HTTP_USER_AGENT)s
|
|
|
|
subversion: %(svn_version)s
|
|
"""
|
|
% params
|
|
)
|
|
if fmt == "html":
|
|
txt = txt.replace("\n", "<br/>")
|
|
return txt
|
|
|
|
security.declareProtected(
|
|
ScoSuperAdmin, "send_debug_alert"
|
|
) # not called through the web
|
|
|
|
def send_debug_alert(self, txt, REQUEST=None):
|
|
"""Send an alert email (bug report) to ScoDoc developpers"""
|
|
if not scu.SCO_EXC_MAIL:
|
|
log("send_debug_alert: email disabled")
|
|
return
|
|
if REQUEST:
|
|
txt = self._report_request(REQUEST) + txt
|
|
URL = REQUEST.get("URL", "")
|
|
else:
|
|
URL = "send_debug_alert"
|
|
msg = MIMEMultipart()
|
|
subj = Header("[scodoc] exc %s" % URL, scu.SCO_ENCODING)
|
|
msg["Subject"] = subj
|
|
recipients = [scu.SCO_EXC_MAIL]
|
|
msg["To"] = " ,".join(recipients)
|
|
msg["From"] = "scodoc-alert"
|
|
msg.epilogue = ""
|
|
msg.attach(MIMEText(txt, "plain", scu.SCO_ENCODING))
|
|
self.sendEmailFromException(msg)
|
|
log("Sent mail alert:\n" + txt)
|
|
|
|
security.declareProtected("View", "scodoc_admin")
|
|
|
|
def scodoc_admin(self, REQUEST=None):
|
|
"""Page Operations d'administration"""
|
|
e = self._check_admin_perm(REQUEST)
|
|
if e:
|
|
return e
|
|
|
|
H = [
|
|
self.scodoc_top_html_header(REQUEST, page_title="ScoDoc: bienvenue"),
|
|
"""
|
|
<h3>Administration ScoDoc</h3>
|
|
|
|
<p><a href="change_admin_user_form">changer le mot de passe super-administrateur</a></p>
|
|
<p><a href="%s">retour à la page d'accueil</a></p>
|
|
|
|
<h4 class="scodoc">Création d'un département</h4>
|
|
<p class="help_important">Le département doit avoir été créé au préalable sur le serveur en utilisant le script
|
|
<tt>create_dept.sh</tt> (à lancer comme <tt>root</tt> dans le répertoire <tt>config</tt> de ScoDoc).
|
|
</p>"""
|
|
% self.absolute_url(),
|
|
]
|
|
|
|
deptList = [x.id for x in self._list_depts()] # definis dans Zope
|
|
deptIds = set(self._list_depts_ids()) # definis sur le filesystem
|
|
existingDepts = set(deptList)
|
|
addableDepts = deptIds - existingDepts
|
|
|
|
if not addableDepts:
|
|
# aucun departement defini: aide utilisateur
|
|
H.append("<p>Aucun département à ajouter !</p>")
|
|
else:
|
|
H.append("""<form action="create_dept"><select name="DeptId"/>""")
|
|
for deptId in addableDepts:
|
|
H.append("""<option value="%s">%s</option>""" % (deptId, deptId))
|
|
H.append(
|
|
"""</select>
|
|
<input type="submit" value="Créer département">
|
|
</form>"""
|
|
)
|
|
|
|
if deptList:
|
|
H.append(
|
|
"""
|
|
<h4 class="scodoc">Suppression d'un département</h4>
|
|
<p>Ceci permet de supprimer le site web associé à un département, mais n'affecte pas la base de données
|
|
(le site peut donc être recréé sans perte de données).
|
|
</p>
|
|
<form action="delete_dept">
|
|
<select name="DeptId">
|
|
"""
|
|
)
|
|
for deptFolder in self._list_depts():
|
|
H.append(
|
|
'<option value="%s">%s</option>' % (deptFolder.id, deptFolder.id)
|
|
)
|
|
H.append(
|
|
"""</select>
|
|
<input type="submit" value="Supprimer département">
|
|
|
|
</form>"""
|
|
)
|
|
|
|
H.append("""</body></html>""")
|
|
return "\n".join(H)
|
|
|
|
def _list_depts_ids(self):
|
|
"""Liste de id de departements definis par create_dept.sh
|
|
(fichiers depts/*.cfg)
|
|
"""
|
|
filenames = glob.glob(scu.SCODOC_VAR_DIR + "/config/depts/*.cfg")
|
|
ids = [os.path.split(os.path.splitext(f)[0])[1] for f in filenames]
|
|
return ids
|
|
|
|
security.declareProtected("View", "http_expiration_date")
|
|
|
|
def http_expiration_date(self):
|
|
"http expiration date for cachable elements (css, ...)"
|
|
d = datetime.timedelta(minutes=10)
|
|
return (datetime.datetime.utcnow() + d).strftime("%a, %d %b %Y %H:%M:%S GMT")
|
|
|
|
security.declareProtected("View", "get_etud_dept")
|
|
|
|
def get_etud_dept(self, REQUEST=None):
|
|
"""Returns the dept id (eg "GEII") of an etud (identified by etudid, INE or NIP in REQUEST).
|
|
Warning: This function is inefficient and its result should be cached.
|
|
"""
|
|
depts = self._list_depts()
|
|
depts_etud = [] # liste des depts où l'etud est defini
|
|
for dept in depts:
|
|
etuds = dept.Scolarite.getEtudInfo(REQUEST=REQUEST)
|
|
if etuds:
|
|
depts_etud.append((dept, etuds))
|
|
if not depts_etud:
|
|
return "" # not found
|
|
elif len(depts_etud) == 1:
|
|
return depts_etud[0][0].id
|
|
# inscriptions dans plusieurs departements: cherche la plus recente
|
|
last_dept = None
|
|
last_date = None
|
|
for (dept, etuds) in depts_etud:
|
|
dept.Scolarite.fillEtudsInfo(etuds)
|
|
etud = etuds[0]
|
|
if etud["sems"]:
|
|
if (not last_date) or (etud["sems"][0]["date_fin_iso"] > last_date):
|
|
last_date = etud["sems"][0]["date_fin_iso"]
|
|
last_dept = dept
|
|
if not last_dept:
|
|
# est present dans plusieurs semestres mais inscrit dans aucun
|
|
return depts_etud[0][0]
|
|
return last_dept.id
|
|
|
|
security.declareProtected("View", "table_etud_in_accessible_depts")
|
|
table_etud_in_accessible_depts = sco_find_etud.table_etud_in_accessible_depts
|
|
|
|
security.declareProtected("View", "search_inscr_etud_by_nip")
|
|
search_inscr_etud_by_nip = sco_find_etud.search_inscr_etud_by_nip
|
|
|
|
|
|
def manage_addZScoDoc(self, id="ScoDoc", title="Site ScoDoc", REQUEST=None):
|
|
"Add a ZScoDoc instance to a folder."
|
|
log("============== creating a new ScoDoc instance =============")
|
|
zscodoc = ZScoDoc(
|
|
id, title
|
|
) # ne cree (presque rien), tout se passe lors du 1er accès
|
|
self._setObject(id, zscodoc)
|
|
if REQUEST is not None:
|
|
REQUEST.RESPONSE.redirect("/ScoDoc/manage_workspace")
|
|
return id
|