From d586359e3d066577a6e436fbf6a95494fa62ece3 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sun, 13 Jun 2021 18:29:53 +0200 Subject: [PATCH] WIP: refactoring --- app/scodoc/ImportScolars.py | 4 +- app/scodoc/ZEntreprises.py | 4 +- app/scodoc/ZScoDoc.py | 208 +- app/scodoc/ZScolar.py | 2780 ------------------- app/scodoc/gen_tables.py | 4 +- app/scodoc/html_sco_header.py | 118 +- app/scodoc/html_sidebar.py | 2 +- app/scodoc/mails.py | 141 + app/scodoc/notes_log.py | 4 +- app/scodoc/notes_table.py | 2 +- app/scodoc/pe_view.py | 4 +- app/scodoc/sco_abs_notification.py | 4 +- app/scodoc/sco_abs_views.py | 44 +- app/scodoc/sco_apogee_compare.py | 8 +- app/scodoc/sco_archives.py | 12 +- app/scodoc/sco_archives_etud.py | 10 +- app/scodoc/sco_bulletins.py | 11 +- app/scodoc/sco_core.py | 4 +- app/scodoc/sco_debouche.py | 4 +- app/scodoc/sco_dept.py | 2 +- app/scodoc/sco_dump_db.py | 4 +- app/scodoc/sco_edit_formation.py | 12 +- app/scodoc/sco_edit_matiere.py | 16 +- app/scodoc/sco_edit_module.py | 43 +- app/scodoc/sco_edit_ue.py | 38 +- app/scodoc/sco_edt_cal.py | 4 +- app/scodoc/sco_etape_apogee_view.py | 14 +- app/scodoc/sco_evaluations.py | 12 +- app/scodoc/sco_export_results.py | 4 +- app/scodoc/sco_find_etud.py | 14 +- app/scodoc/sco_formations.py | 16 + app/scodoc/sco_formsemestre_custommenu.py | 4 +- app/scodoc/sco_formsemestre_edit.py | 25 +- app/scodoc/sco_formsemestre_exterieurs.py | 8 +- app/scodoc/sco_formsemestre_inscriptions.py | 20 +- app/scodoc/sco_formsemestre_status.py | 30 +- app/scodoc/sco_formsemestre_validation.py | 24 +- app/scodoc/sco_groups.py | 18 +- app/scodoc/sco_groups_edit.py | 4 +- app/scodoc/sco_groups_view.py | 4 +- app/scodoc/sco_import_users.py | 3 +- app/scodoc/sco_inscr_passage.py | 6 +- app/scodoc/sco_liste_notes.py | 8 +- app/scodoc/sco_lycee.py | 8 +- app/scodoc/sco_moduleimpl_inscriptions.py | 8 +- app/scodoc/sco_moduleimpl_status.py | 4 +- app/scodoc/sco_news.py | 3 +- app/scodoc/sco_page_etud.py | 9 +- app/scodoc/sco_placement.py | 4 +- app/scodoc/sco_preferences.py | 6 +- app/scodoc/sco_pvjury.py | 14 +- app/scodoc/sco_recapcomplet.py | 4 +- app/scodoc/sco_report.py | 16 +- app/scodoc/sco_saisie_notes.py | 26 +- app/scodoc/sco_semset.py | 4 +- app/scodoc/sco_synchro_etuds.py | 4 +- app/scodoc/sco_trombino.py | 12 +- app/scodoc/sco_ue_external.py | 4 +- app/scodoc/sco_utils.py | 2 +- app/scodoc/scolars.py | 3 +- app/views/absences.py | 34 +- app/views/notes.py | 274 +- app/views/scolar.py | 42 +- refactor.py | 72 +- 64 files changed, 780 insertions(+), 3474 deletions(-) delete mode 100644 app/scodoc/ZScolar.py create mode 100644 app/scodoc/mails.py mode change 100755 => 100644 app/scodoc/sco_core.py diff --git a/app/scodoc/ImportScolars.py b/app/scodoc/ImportScolars.py index d38218f52e..1935701f1b 100644 --- a/app/scodoc/ImportScolars.py +++ b/app/scodoc/ImportScolars.py @@ -240,14 +240,14 @@ def students_import_excel( dest = "formsemestre_status?formsemestre_id=%s" % formsemestre_id else: dest = context.NotesURL() - H = [context.sco_header(REQUEST, page_title="Import etudiants")] + H = [html_sco_header.sco_header(context, REQUEST, page_title="Import etudiants")] H.append("") H.append("

Import terminé !

") H.append('

Continuer

' % dest) - return "\n".join(H) + context.sco_footer(REQUEST) + return "\n".join(H) + html_sco_header.sco_footer(context, REQUEST) def scolars_import_excel_file( diff --git a/app/scodoc/ZEntreprises.py b/app/scodoc/ZEntreprises.py index 56be1de44d..9a70012f58 100644 --- a/app/scodoc/ZEntreprises.py +++ b/app/scodoc/ZEntreprises.py @@ -55,12 +55,12 @@ import sco_entreprises def entreprise_header(context, REQUEST=None, page_title=""): "common header for all Entreprises pages" - return context.sco_header(REQUEST, container=context, page_title=page_title) + return html_sco_header.sco_header(context, REQUEST, container=context, page_title=page_title) def entreprise_footer(context, REQUEST): "common entreprise footer" - return context.sco_footer(REQUEST) + return html_sco_header.sco_footer(context, REQUEST) class ZEntreprises( diff --git a/app/scodoc/ZScoDoc.py b/app/scodoc/ZScoDoc.py index 5aab6e8cd4..222e115717 100644 --- a/app/scodoc/ZScoDoc.py +++ b/app/scodoc/ZScoDoc.py @@ -74,6 +74,7 @@ except: import sco_utils as scu import VERSION +import mails from notes_log import log import sco_find_etud import sco_users @@ -94,6 +95,7 @@ from sco_permissions import ( ScoSuperAdmin, ) from sco_exceptions import ScoValueError, ScoLockedFormError, ScoGenError, AccessDenied +import html_sco_header class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit): @@ -264,8 +266,8 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp 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" + html_sco_header.scodoc_top_html_header( + self, REQUEST, page_title="ScoDoc: changement mot de passe" ) ] if message: @@ -367,54 +369,6 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp + """ supprimé du serveur web (la base de données n'est pas affectée)!

Continuer

""" ) - _top_level_css = """ - """ - - _html_begin = """ - - - -%(page_title)s - - - - - - - - - - - - - - - - - - - - - - - - - -""" - - 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, - """""", - scu.CUSTOM_HTML_HEADER_CNX, - ] - return "\n".join(H) - security.declareProtected("View", "index_html") def index_html(self, REQUEST=None, message=None): @@ -441,7 +395,9 @@ class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Imp pass H = [ - self.scodoc_top_html_header(REQUEST, page_title="ScoDoc: bienvenue"), + html_sco_header.scodoc_top_html_header( + self, REQUEST, page_title="ScoDoc: bienvenue" + ), self._check_users_folder(REQUEST=REQUEST), # ensure setup is done ] if message: @@ -573,7 +529,7 @@ E. Viennet (Université Paris 13).

) H = [ - self.standard_html_header(REQUEST), + html_sco_header.standard_html_header(), """

Scolarité du département %s

""" % deptfoldername, @@ -587,7 +543,7 @@ E. Viennet (Université Paris 13).

""" % self.ScoDocURL(), - self.standard_html_footer(REQUEST), + html_sco_header.standard_html_footer(), ] return "\n".join(H) @@ -626,69 +582,6 @@ E. Viennet (Université Paris 13).

return "\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 """ - -ScoDoc: accueil - - - - - - - -%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 """ -

Problèmes et suggestions sur le logiciel: %s

-

ScoDoc est un logiciel libre développé par Emmanuel Viennet.

-""" % ( - 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( @@ -736,18 +629,18 @@ Problème de connexion (identifiant, mot de passe): contacter votre responsa elif error_type in ("ScoValueError", "FormatError"): # Not a bug, presents a gentle message to the user: H = [ - self.standard_html_header(REQUEST), + html_sco_header.standard_html_header(), """

Erreur !

%s

""" % error_value, ] if error_value.dest_url: H.append('

Continuer

' % error_value.dest_url) - H.append(self.standard_html_footer(REQUEST)) + H.append(html_sco_header.standard_html_footer()) 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)) + H.append(html_sco_header.standard_html_header()) except: pass H.append( @@ -785,7 +678,7 @@ Problème de connexion (identifiant, mot de passe): contacter votre responsa % params ) try: - H.append(self.standard_html_footer(REQUEST)) + H.append(html_sco_header.standard_html_footer()) except: log("no footer found for error page") pass @@ -801,81 +694,12 @@ ErrorType: %(error_type)s % params ) - self.send_debug_alert(txt, REQUEST=REQUEST) + mails.send_debug_alert(context, 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 -""" - % params - ) - if fmt == "html": - txt = txt.replace("\n", "
") - 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): @@ -885,7 +709,9 @@ Agent: %(HTTP_USER_AGENT)s return e H = [ - self.scodoc_top_html_header(REQUEST, page_title="ScoDoc: bienvenue"), + html_sco_header.scodoc_top_html_header( + self, REQUEST, page_title="ScoDoc: bienvenue" + ), """

Administration ScoDoc

diff --git a/app/scodoc/ZScolar.py b/app/scodoc/ZScolar.py deleted file mode 100644 index 32d81f8a40..0000000000 --- a/app/scodoc/ZScolar.py +++ /dev/null @@ -1,2780 +0,0 @@ -# -*- 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 Scolarite pour département IUT -""" - -import sys -import traceback -import time -import string -import glob -import re -import urllib -import urllib2 -import cgi -import xml -import jaxml - -try: - from cStringIO import StringIO -except: - from StringIO import StringIO -from zipfile import ZipFile -import thread -import psycopg2 - -from sco_zope import * # pylint: disable=unused-wildcard-import - - -# --------------- -from notes_log import log - -log.set_log_directory(INSTANCE_HOME + "/log") -log("restarting...") - -log("ZScolar home=%s" % file_path) - - -import sco_utils as scu -import notesdb as ndb -from scolog import logdb -from sco_permissions import ( - ScoAbsChange, - ScoView, - ScoEnsView, - ScoImplement, - ScoChangeFormation, - ScoChangePreferences, - ScoObservateur, - ScoEtudAddAnnotations, - ScoEtudInscrit, - ScoEtudChangeGroups, - ScoEtudChangeAdr, - ScoEtudSupprAnnotations, - ScoEditAllEvals, - ScoEditAllNotes, - ScoEditFormationTags, - ScoEditApo, - ScoSuperAdmin, -) -import sco_permissions -from sco_exceptions import ( - AccessDenied, - ScoException, - ScoValueError, - ScoInvalidDateError, - ScoLockedFormError, - ScoGenError, -) -from TrivialFormulator import TrivialFormulator, tf_error_message -import scolars -import sco_codes_parcours -import sco_preferences -import sco_formations -from scolars import ( - format_nom, - format_prenom, - format_civilite, - format_lycee, - format_lycee_from_code, -) -from scolars import format_telephone, format_pays, make_etud_args -import sco_find_etud -import sco_photos -import sco_formsemestre -import sco_formsemestre_edit - -import sco_news -from sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC - -import html_sco_header, html_sidebar - -from gen_tables import GenTable -import sco_excel -import imageresize - -import ZNotes -import ZAbsences -import ZEntreprises -import ZScoUsers - -# import sco_modalites -import ImportScolars -import sco_abs -import sco_portal_apogee -import sco_synchro_etuds -import sco_page_etud -import sco_groups -import sco_trombino -import sco_groups_view -import sco_trombino_tours -import sco_parcours_dut -import sco_report -import sco_archives_etud -import sco_debouche -import sco_groups_edit -import sco_up_to_date -import sco_edt_cal -import sco_dept -import sco_dump_db - -from VERSION import SCOVERSION, SCONEWS - -if hasattr(scu.CONFIG, "ABSOLUTE_URL") and scu.CONFIG.ABSOLUTE_URL: - log("ScoDoc: ABSOLUTE_URL='%s'" % scu.CONFIG.ABSOLUTE_URL) -log("ScoDoc: using encoding %s" % scu.SCO_ENCODING) - -# import essai_cas - -# --------------- - - -class ZScolar(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit): - - "ZScolar object" - - meta_type = "ZScolar" - 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 - ) - - # no permissions, only called from python - def __init__(self, id, title, db_cnx_string=None): - "initialise a new instance of ZScolar" - log("*** creating ZScolar instance") - self.id = id - self.title = title - self._db_cnx_string = db_cnx_string - self._cnx = None - - # --- add Scousers instance - id = "Users" - obj = ZScoUsers.ZScoUsers(id, "Gestion utilisateurs zope") - self._setObject(id, obj) - # --- add Notes instance - id = "Notes" - obj = ZNotes.ZNotes(id, "Gestion Notes") - self._setObject(id, obj) - # --- add Absences instance - id = "Absences" - obj = ZAbsences.ZAbsences(id, "Gestion absences") - self._setObject(id, obj) - # --- add Entreprises instance - id = "Entreprises" - obj = ZEntreprises.ZEntreprises(id, "Suivi entreprises") - self._setObject(id, obj) - - # - self.manage_addProperty("roles_initialized", "0", "string") - - # The for used to edit this object - security.declareProtected(ScoView, "manage_editZScolar") - - def manage_editZScolar(self, title, REQUEST=None): - "Changes the instance values" - self.title = title - self._p_changed = 1 - return REQUEST.RESPONSE.redirect("manage_editForm") - - def _setup_initial_roles_and_permissions(self): - """Initialize roles and permissions - create 3 roles: EnsXXX, SecrXXX, AdminXXX - and set default permissions for each one. - """ - DeptId = self.DeptId() - log("initializing roles and permissions for %s" % DeptId) - H = [] - ok = True - DeptRoles = self.DeptUsersRoles() - - container = self.aq_parent # creates roles and permissions in parent folder - valid_roles = set(container.valid_roles()) - for role_name in DeptRoles: - if role_name not in valid_roles: - r = container._addRole(role_name) - if r: # error - H.append(r) - ok = False - - for permission in sco_permissions.Sco_Default_Permissions.keys(): - roles = [ - r + DeptId for r in sco_permissions.Sco_Default_Permissions[permission] - ] - roles.append("Manager") - log("granting '%s' to %s" % (permission, roles)) - try: - r = container.manage_permission(permission, roles=roles, acquire=0) - if r: - H.append(r) - ok = False - except ValueError: - log(traceback.format_exc()) - log("failed, ignoring.") - # set property indicating that we did the job: - self.manage_changeProperties(roles_initialized="1") - - return ok, "\n".join(H) - - security.declareProtected(ScoView, "DeptId") - - def DeptId(self): - """Returns Id for this department - (retreived as the name of the parent folder) - (c'est normalement l'id donne à create_dept.sh) - NB: la preference DeptName est au depart la même chose de cet id - mais elle peut être modifiée (préférences). - """ - return self.aq_parent.id - - def DeptUsersRoles(self): # not published - # Donne les rôles utilisés dans ce departement. - DeptId = self.DeptId() - DeptRoles = [] - for role_type in ("Ens", "Secr", "Admin", "RespPe"): - role_name = role_type + DeptId - DeptRoles.append(role_name) - return DeptRoles - - security.declareProtected(ScoView, "essai") - - def essai(self, x="", REQUEST=None): - """essai: header / body / footer""" - return """ - - - -Programme DUT TEST - - - - - - - - - - - - - -

- -REQUEST.URL=%s
-REQUEST.URL0=%s
-
-

- - """ % ( - REQUEST.URL, - REQUEST.URL0, - ) - # return ( - # self.sco_header(REQUEST) - # + """
%s
""" % x - # + self.sco_footer(REQUEST) - # ) - # b = "

Hello, World !


" - # raise ValueError("essai exception") - # raise ScoValueError('essai exception !', dest_url='totoro', REQUEST=REQUEST) - - # cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - # cursor.execute("select * from notes_formations") - # b += str(cursor.fetchall()) - # b = self.Notes.gloups() - # raise NoteProcessError('test exception !') - - # essai: liste des permissions - # from AccessControl import getSecurityManager # pylint: disable=import-error - # from AccessControl.Permission import Permission # pylint: disable=import-error - - # permissions = self.ac_inherited_permissions(1) - # scoperms = [p for p in permissions if p[0][:3] == "Sco"] - # # H.append( str(self.aq_parent.aq_parent.permission_settings()) ) - # # H.append('

perms: %s

'%str(scoperms)) - # # H.append('

valid_roles: %s

'%str(self.valid_roles())) - # # H.append('

ac_inherited_permissions=%s

'%str(self.ac_inherited_permissions(1))) - # def collect_roles(context, rd): - # for p in scoperms: - # name, value = p[:2] - # P = Permission(name, value, context) - # roles = list(P.getRoles()) - # if rd.has_key(name): - # rd[name] += roles - # else: - # rd[name] = roles - # if hasattr(context, "aq_parent"): - # collect_roles(context.aq_parent, rd) - - # b = "" - # rd = {} - # collect_roles(self, rd) - # b = "

" + str(rd) + "

" - - # authuser = REQUEST.AUTHENTICATED_USER - # for p in scoperms: - # permname, _ = p[:2] - # b += "

" + permname + " : " - # if authuser.has_permission(permname, self): - # b += "yes" - # else: - # b += "no" - # b += "

" - # b += "

xxx


" + str(self.aq_parent.aq_parent) - - # return self.sco_header(REQUEST) + str(b) + self.sco_footer(REQUEST) - - # essais calendriers: - security.declareProtected(ScoView, "experimental_calendar") - experimental_calendar = sco_edt_cal.experimental_calendar - security.declareProtected(ScoView, "group_edt_json") - group_edt_json = sco_edt_cal.group_edt_json - - security.declareProtected(ScoView, "ScoURL") - - def ScoURL(self): - """base URL for this sco instance. - e.g. https://scodoc.xxx.fr/ScoDoc/DEPT/Scolarite - = page accueil département - """ - # absolute_url is the classic Zope method - # The avoid the burden of configuring a proxy zope object, we offer - # a custom configuration via scodoc_config - # XXX ne devrait pas marcher car l'URL dépend du département !!! - return scu.CONFIG.ABSOLUTE_URL or self.absolute_url() - - def NotesURL(self): - """URL of Notes - e.g. https://scodoc.xxx.fr/ScoDoc/DEPT/Scolarite/Notes - = url de base des requêtes de ZNotes - et page accueil programmes. - """ - return self.ScoURL() + "/Notes" - - def EntreprisesURL(self): - """URL of Enterprises - e.g. https://scodoc.xxx.fr/ScoDoc/DEPT/Scolarite/Entreprises - = url de base des requêtes de ZEntreprises - et page accueil Entreprises - """ - return self.ScoURL() + "/Entreprises" - - def AbsencesURL(self): - """URL of Absences""" - return self.ScoURL() + "/Absences" - - def UsersURL(self): - """URL of Users - e.g. https://scodoc.xxx.fr/ScoDoc/DEPT/Scolarite/Users - = url de base des requêtes ZScoUsers - et page accueil users - """ - return self.ScoURL() + "/Users" - - security.declareProtected(ScoView, "sco_header") - sco_header = html_sco_header.sco_header - security.declareProtected(ScoView, "sco_footer") - sco_footer = html_sco_header.sco_footer - - # -------------------------------------------------------------------- - # - # GESTION DE LA BD - # - # -------------------------------------------------------------------- - - security.declareProtected(ScoSuperAdmin, "GetDBConnexion") - GetDBConnexion = ndb.GetDBConnexion - - # A enlever après re-ecriture de ZEntreprises.py - security.declareProtected(ScoView, "TrivialFormulator") - - def TrivialFormulator( - self, - form_url, - values, - formdescription=(), - initvalues={}, - method="POST", - submitlabel="OK", - formid="tf", - cancelbutton=None, - readonly=False, - ): - "generator/validator of simple forms" - # obsolete, still used by dtml/entreprises old code... - return TrivialFormulator( - form_url, - values, - formdescription=formdescription, - initvalues=initvalues, - method=method, - submitlabel=submitlabel, - formid=formid, - cancelbutton=cancelbutton, - readonly=readonly, - ) - - # -------------------------------------------------------------------- - # - # SCOLARITE (top level) - # - # -------------------------------------------------------------------- - - security.declareProtected(ScoView, "about") - - def about(self, REQUEST): - "version info" - H = [ - """

Système de gestion scolarité

-

© Emmanuel Viennet 1997-2021

-

Version %s

- """ - % (scu.get_scodoc_version()) - ] - H.append( - '

Logiciel libre écrit en Python.

Utilise ReportLab pour générer les documents PDF, et pyExcelerator pour le traitement des documents Excel.

' - ) - H.append("

Dernières évolutions

" + SCONEWS) - H.append( - '" - ) - d = "" - return self.sco_header(REQUEST) + "\n".join(H) + d + self.sco_footer(REQUEST) - - security.declareProtected(ScoView, "ScoErrorResponse") - - def ScoErrorResponse(self, msg, format="html", REQUEST=None): - """Send an error message to the client, in html or xml format.""" - REQUEST.RESPONSE.setStatus(404, reason=msg) - if format == "html" or format == "pdf": - raise ScoValueError(msg) - elif format == "xml": - REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE) - doc = jaxml.XML_document(encoding=scu.SCO_ENCODING) - doc.error(msg=msg) - return repr(doc) - elif format == "json": - REQUEST.RESPONSE.setHeader("content-type", scu.JSON_MIMETYPE) - return "undefined" # XXX voir quoi faire en cas d'erreur json - else: - raise ValueError("ScoErrorResponse: invalid format") - - # XXX essai XXX - # security.declareProtected(ScoView, 'essai_cas') - # essai_cas = essai_cas.essai_cas - - # -------------------------------------------------------------------- - # - # PREFERENCES - # - # -------------------------------------------------------------------- - security.declareProtected(ScoView, "get_preferences") - - def get_preferences(self, formsemestre_id=None): - "Get preferences for this instance (a dict-like instance)" - return sco_preferences.sem_preferences(self, formsemestre_id) - - security.declareProtected(ScoView, "get_preference") - - def get_preference(self, name, formsemestre_id=None): - """Returns value of named preference. - All preferences have a sensible default value (see sco_preferences.py), - this function always returns a usable value for all defined preferences names. - """ - return sco_preferences.get_base_preferences(self).get(formsemestre_id, name) - - security.declareProtected(ScoChangePreferences, "edit_preferences") - - def edit_preferences(self, REQUEST): - """Edit global preferences""" - return sco_preferences.get_base_preferences(self).edit(REQUEST=REQUEST) - - security.declareProtected(ScoView, "formsemestre_edit_preferences") - - def formsemestre_edit_preferences(self, formsemestre_id, REQUEST): - """Edit preferences for a semestre""" - authuser = REQUEST.AUTHENTICATED_USER - sem = sco_formsemestre.get_formsemestre(self.Notes, formsemestre_id) - ok = ( - authuser.has_permission(ScoImplement, self) - or ((str(authuser) in sem["responsables"]) and sem["resp_can_edit"]) - ) and (sem["etat"] == "1") - if ok: - return self.get_preferences(formsemestre_id=formsemestre_id).edit( - REQUEST=REQUEST - ) - else: - raise AccessDenied("Modification impossible pour %s" % authuser) - - security.declareProtected(ScoView, "doc_preferences") - - def doc_preferences(self, REQUEST): - """List preferences for wiki documentation""" - REQUEST.RESPONSE.setHeader("content-type", "text/plain") - return sco_preferences.doc_preferences(self) - - # -------------------------------------------------------------------- - # - # ETUDIANTS - # - # -------------------------------------------------------------------- - - # ----------------- BANDEAUX ------------------- - security.declareProtected(ScoView, "sidebar") - sidebar = html_sidebar.sidebar - security.declareProtected(ScoView, "sidebar_dept") - sidebar_dept = html_sidebar.sidebar_dept - - security.declareProtected(ScoView, "showEtudLog") - - def showEtudLog(self, etudid, format="html", REQUEST=None): - """Display log of operations on this student""" - etud = self.getEtudInfo(filled=1, REQUEST=REQUEST)[0] - - ops = self.listScoLog(etudid) - - tab = GenTable( - titles={ - "date": "Date", - "authenticated_user": "Utilisateur", - "remote_addr": "IP", - "method": "Opération", - "msg": "Message", - }, - columns_ids=("date", "authenticated_user", "remote_addr", "method", "msg"), - rows=ops, - html_sortable=True, - html_class="table_leftalign", - base_url="%s?etudid=%s" % (REQUEST.URL0, etudid), - page_title="Opérations sur %(nomprenom)s" % etud, - html_title="

Opérations effectuées sur l'étudiant %(nomprenom)s

" - % etud, - filename="log_" + scu.make_filename(etud["nomprenom"]), - html_next_section='' - % etud, - preferences=self.get_preferences(), - ) - - return tab.make_page(self, format=format, REQUEST=REQUEST) - - security.declareProtected(ScoView, "listScoLog") - - def listScoLog(self, etudid): - "liste des operations effectuees sur cet etudiant" - cnx = self.GetDBConnexion() - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - cursor.execute( - "select * from scolog where etudid=%(etudid)s ORDER BY DATE DESC", - {"etudid": etudid}, - ) - return cursor.dictfetchall() - - # ---------- PAGE ACCUEIL (listes) -------------- - security.declareProtected(ScoView, "index_html") - index_html = sco_dept.index_html - - security.declareProtected(ScoView, "rssnews") - - def rssnews(self, REQUEST=None): - "rss feed" - REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE) - return sco_news.scolar_news_summary_rss( - self, "Nouvelles de " + self.get_preference("DeptName"), self.ScoURL() - ) - - # genere liste html pour accès aux groupes de ce semestre - def make_listes_sem(self, sem, REQUEST=None, with_absences=True): - context = self - authuser = REQUEST.AUTHENTICATED_USER - r = self.ScoURL() # root url - # construit l'URL "destination" - # (a laquelle on revient apres saisie absences) - query_args = cgi.parse_qs(REQUEST.QUERY_STRING) - 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 ! - - # - H = [] - # pas de menu absences si pas autorise: - if with_absences and not authuser.has_permission(ScoAbsChange, self): - with_absences = False - - # - H.append( - '

Listes de %(titre)s (%(mois_debut)s - %(mois_fin)s)

' - % sem - ) - - formsemestre_id = sem["formsemestre_id"] - - # calcule dates 1er jour semaine pour absences - try: - if with_absences: - first_monday = sco_abs.ddmmyyyy(sem["date_debut"]).prev_monday() - FA = [] # formulaire avec menu saisi absences - FA.append( - '
' - ) - FA.append( - '' % sem - ) - FA.append( - '' - ) - - FA.append( - '' % destination - ) - FA.append('') - FA.append('") - FA.append( - 'état' - % sem - ) - FA.append("
") - FormAbs = "\n".join(FA) - else: - FormAbs = "" - except ScoInvalidDateError: # dates incorrectes dans semestres ? - FormAbs = "" - # - H.append('
') - # Genere liste pour chaque partition (categorie de groupes) - for partition in sco_groups.get_partitions_list( - context, sem["formsemestre_id"] - ): - if not partition["partition_name"]: - H.append("

Tous les étudiants

" % partition) - else: - H.append("

Groupes de %(partition_name)s

" % partition) - groups = sco_groups.get_partition_groups(context, partition) - if groups: - H.append("") - for group in groups: - n_members = len( - sco_groups.get_group_members(context, group["group_id"]) - ) - group["url"] = r - if group["group_name"]: - group["label"] = "groupe %(group_name)s" % group - else: - group["label"] = "liste" - H.append('') - H.append( - """""" - % group - ) - H.append("" % n_members) - - if with_absences: - H.append(FormAbs % group) - - H.append("") - H.append("
") - else: - H.append('

Aucun groupe dans cette partition') - if sco_groups.can_change_groups(self, REQUEST, formsemestre_id): - H.append( - ' (créer)' - % partition["partition_id"] - ) - H.append("

") - if sco_groups.can_change_groups(self, REQUEST, formsemestre_id): - H.append( - '

Ajouter une partition

' - % formsemestre_id - ) - - H.append("
") - return "\n".join(H) - - security.declareProtected(ScoView, "trombino") - trombino = sco_trombino.trombino - - security.declareProtected(ScoView, "pdf_trombino_tours") - pdf_trombino_tours = sco_trombino_tours.pdf_trombino_tours - - security.declareProtected(ScoView, "pdf_feuille_releve_absences") - pdf_feuille_releve_absences = sco_trombino_tours.pdf_feuille_releve_absences - - security.declareProtected(ScoView, "trombino_copy_photos") - trombino_copy_photos = sco_trombino.trombino_copy_photos - - security.declareProtected(ScoView, "groups_view") - groups_view = sco_groups_view.groups_view - - security.declareProtected(ScoView, "export_groups_as_moodle_csv") - export_groups_as_moodle_csv = sco_groups_view.export_groups_as_moodle_csv - - security.declareProtected(ScoView, "getEtudInfoGroupes") - - def getEtudInfoGroupes(self, group_ids, etat=None): - """liste triée d'infos (dict) sur les etudiants du groupe indiqué. - Attention: lent, car plusieurs requetes SQL par etudiant ! - """ - etuds = [] - for group_id in group_ids: - members = sco_groups.get_group_members(self, group_id, etat=etat) - for m in members: - etud = self.getEtudInfo(etudid=m["etudid"], filled=True)[0] - etuds.append(etud) - - return etuds - - # -------------------------- INFOS SUR ETUDIANTS -------------------------- - security.declareProtected(ScoView, "getEtudInfo") - - def getEtudInfo( - self, etudid=False, code_nip=False, filled=False, REQUEST=None, format=None - ): - """infos sur un etudiant pour utilisation en Zope DTML - On peut specifier etudid - ou bien cherche dans REQUEST.form: etudid, code_nip, code_ine - (dans cet ordre). - """ - if etudid is None: - return [] - cnx = self.GetDBConnexion() - args = make_etud_args(etudid=etudid, code_nip=code_nip, REQUEST=REQUEST) - etud = scolars.etudident_list(cnx, args=args) - - if filled: - self.fillEtudsInfo(etud) - if format is None: - return etud - else: - return scu.sendResult(REQUEST, etud, name="etud", format=format) - - security.declareProtected(ScoView, "search_etud_in_dept") - search_etud_in_dept = sco_find_etud.search_etud_in_dept - - security.declareProtected(ScoView, "search_etud_by_name") - search_etud_by_name = sco_find_etud.search_etud_by_name - - security.declareProtected(ScoView, "fillEtudsInfo") - - def fillEtudsInfo(self, etuds): - """etuds est une liste d'etudiants (mappings) - Pour chaque etudiant, ajoute ou formatte les champs - -> informations pour fiche etudiant ou listes diverses - """ - cnx = self.GetDBConnexion() - # open('/tmp/t','w').write( str(etuds) ) - for etud in etuds: - etudid = etud["etudid"] - etud["dept"] = self.DeptId() - adrs = scolars.adresse_list(cnx, {"etudid": etudid}) - if not adrs: - # certains "vieux" etudiants n'ont pas d'adresse - adr = {}.fromkeys(scolars._adresseEditor.dbfields, "") - adr["etudid"] = etudid - else: - adr = adrs[0] - if len(adrs) > 1: - log("fillEtudsInfo: etudid=%s a %d adresses" % (etudid, len(adrs))) - etud.update(adr) - scolars.format_etud_ident(etud) - - # Semestres dans lesquel il est inscrit - ins = self.Notes.do_formsemestre_inscription_list({"etudid": etudid}) - etud["ins"] = ins - sems = [] - cursem = None # semestre "courant" ou il est inscrit - for i in ins: - sem = sco_formsemestre.get_formsemestre(self, i["formsemestre_id"]) - if sco_formsemestre.sem_est_courant(self, sem): - cursem = sem - curi = i - sem["ins"] = i - sems.append(sem) - # trie les semestres par date de debut, le plus recent d'abord - # (important, ne pas changer (suivi cohortes)) - sems.sort(lambda x, y: cmp(y["dateord"], x["dateord"])) - etud["sems"] = sems - etud["cursem"] = cursem - if cursem: - etud["inscription"] = cursem["titremois"] - etud["inscriptionstr"] = "Inscrit en " + cursem["titremois"] - etud["inscription_formsemestre_id"] = cursem["formsemestre_id"] - etud["etatincursem"] = curi["etat"] - etud["situation"] = self._descr_situation_etud(etudid, etud["ne"]) - # XXX est-ce utile ? sco_groups.etud_add_group_infos(self, etud, cursem) - else: - if etud["sems"]: - if etud["sems"][0]["dateord"] > time.strftime( - "%Y-%m-%d", time.localtime() - ): - etud["inscription"] = "futur" - etud["situation"] = "futur élève" - else: - etud["inscription"] = "ancien" - etud["situation"] = "ancien élève" - else: - etud["inscription"] = "non inscrit" - etud["situation"] = etud["inscription"] - etud["inscriptionstr"] = etud["inscription"] - etud["inscription_formsemestre_id"] = None - # XXXetud['partitions'] = {} # ne va pas chercher les groupes des anciens semestres - etud["etatincursem"] = "?" - - # nettoyage champs souvents vides - if etud["nomlycee"]: - etud["ilycee"] = "Lycée " + format_lycee(etud["nomlycee"]) - if etud["villelycee"]: - etud["ilycee"] += " (%s)" % etud["villelycee"] - etud["ilycee"] += "
" - else: - if etud["codelycee"]: - etud["ilycee"] = format_lycee_from_code(etud["codelycee"]) - else: - etud["ilycee"] = "" - rap = "" - if etud["rapporteur"] or etud["commentaire"]: - rap = "Note du rapporteur" - if etud["rapporteur"]: - rap += " (%s)" % etud["rapporteur"] - rap += ": " - if etud["commentaire"]: - rap += "%s" % etud["commentaire"] - etud["rap"] = rap - - # if etud['boursier_prec']: - # pass - - if etud["telephone"]: - etud["telephonestr"] = "Tél.: " + format_telephone( - etud["telephone"] - ) - else: - etud["telephonestr"] = "" - if etud["telephonemobile"]: - etud["telephonemobilestr"] = "Mobile: " + format_telephone( - etud["telephonemobile"] - ) - else: - etud["telephonemobilestr"] = "" - etud["debouche"] = etud["debouche"] or "" - - security.declareProtected(ScoView, "etud_info") - - def etud_info(self, etudid=None, format="xml", REQUEST=None): - "Donne les informations sur un etudiant" - t0 = time.time() - args = make_etud_args(etudid=etudid, REQUEST=REQUEST) - cnx = self.GetDBConnexion() - etuds = scolars.etudident_list(cnx, args) - if not etuds: - # etudiant non trouvé: message d'erreur - d = { - "etudid": etudid, - "nom": "?", - "nom_usuel": "", - "prenom": "?", - "civilite": "?", - "sexe": "?", # for backward compat - "email": "?", - "emailperso": "", - "error": "code etudiant inconnu", - } - return scu.sendResult( - REQUEST, d, name="etudiant", format=format, force_outer_xml_tag=False - ) - d = {} - etud = etuds[0] - self.fillEtudsInfo([etud]) - etud["date_naissance_iso"] = ndb.DateDMYtoISO(etud["date_naissance"]) - for a in ( - "etudid", - "code_nip", - "code_ine", - "nom", - "nom_usuel", - "prenom", - "nomprenom", - "email", - "emailperso", - "domicile", - "codepostaldomicile", - "villedomicile", - "paysdomicile", - "telephone", - "telephonemobile", - "fax", - "bac", - "specialite", - "annee_bac", - "nomlycee", - "villelycee", - "codepostallycee", - "codelycee", - "date_naissance_iso", - ): - d[a] = scu.quote_xml_attr(etud[a]) - d["civilite"] = scu.quote_xml_attr( - etud["civilite_str"] - ) # exception: ne sort pas la civilite brute - d["sexe"] = d["civilite"] # backward compat pour anciens clients - d["photo_url"] = scu.quote_xml_attr(sco_photos.etud_photo_url(self, etud)) - - sem = etud["cursem"] - if sem: - sco_groups.etud_add_group_infos(self, etud, sem) - d["insemestre"] = [ - { - "current": "1", - "formsemestre_id": sem["formsemestre_id"], - "date_debut": ndb.DateDMYtoISO(sem["date_debut"]), - "date_fin": ndb.DateDMYtoISO(sem["date_fin"]), - "etat": scu.quote_xml_attr(sem["ins"]["etat"]), - "groupes": scu.quote_xml_attr( - etud["groupes"] - ), # slt pour semestre courant - } - ] - else: - d["insemestre"] = [] - for sem in etud["sems"]: - if sem != etud["cursem"]: - d["insemestre"].append( - { - "formsemestre_id": sem["formsemestre_id"], - "date_debut": ndb.DateDMYtoISO(sem["date_debut"]), - "date_fin": ndb.DateDMYtoISO(sem["date_fin"]), - "etat": scu.quote_xml_attr(sem["ins"]["etat"]), - } - ) - - log("etud_info (%gs)" % (time.time() - t0)) - return scu.sendResult( - REQUEST, d, name="etudiant", format=format, force_outer_xml_tag=False - ) - - security.declareProtected(ScoView, "XMLgetEtudInfos") - XMLgetEtudInfos = etud_info # old name, deprecated - - security.declareProtected(ScoView, "isPrimoEtud") - - def isPrimoEtud(self, etud, sem): - """Determine si un (filled) etud a ete inscrit avant ce semestre. - Regarde la liste des semestres dans lesquels l'étudiant est inscrit - """ - now = sem["dateord"] - for s in etud["sems"]: # le + recent d'abord - if s["dateord"] < now: - return False - return True - - # -------------------------- FICHE ETUDIANT -------------------------- - security.declareProtected(ScoView, "ficheEtud") - ficheEtud = sco_page_etud.ficheEtud - - security.declareProtected(ScoView, "etud_upload_file_form") - etud_upload_file_form = sco_archives_etud.etud_upload_file_form - - security.declareProtected(ScoView, "etud_delete_archive") - etud_delete_archive = sco_archives_etud.etud_delete_archive - - security.declareProtected(ScoView, "etud_get_archived_file") - etud_get_archived_file = sco_archives_etud.etud_get_archived_file - - security.declareProtected(ScoView, "etudarchive_import_files_form") - etudarchive_import_files_form = sco_archives_etud.etudarchive_import_files_form - - security.declareProtected(ScoView, "etudarchive_generate_excel_sample") - etudarchive_generate_excel_sample = ( - sco_archives_etud.etudarchive_generate_excel_sample - ) - - def _descr_situation_etud(self, etudid, ne=""): - """chaine decrivant la situation actuelle de l'etudiant""" - cnx = self.GetDBConnexion() - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - cursor.execute( - "select I.formsemestre_id, I.etat from notes_formsemestre_inscription I, notes_formsemestre S where etudid=%(etudid)s and S.formsemestre_id = I.formsemestre_id and date_debut < now() and date_fin > now() order by S.date_debut desc;", - {"etudid": etudid}, - ) - r = cursor.dictfetchone() - if not r: - situation = "non inscrit" - else: - sem = sco_formsemestre.get_formsemestre(self, r["formsemestre_id"]) - if r["etat"] == "I": - situation = "inscrit%s en %s" % (ne, sem["titremois"]) - # Cherche la date d'inscription dans scolar_events: - events = scolars.scolar_events_list( - cnx, - args={ - "etudid": etudid, - "formsemestre_id": sem["formsemestre_id"], - "event_type": "INSCRIPTION", - }, - ) - if not events: - log( - "*** situation inconsistante pour %s (inscrit mais pas d'event)" - % etudid - ) - date_ins = "???" # ??? - else: - date_ins = events[0]["event_date"] - situation += " le " + str(date_ins) - else: - situation = "démission de %s" % sem["titremois"] - # Cherche la date de demission dans scolar_events: - events = scolars.scolar_events_list( - cnx, - args={ - "etudid": etudid, - "formsemestre_id": sem["formsemestre_id"], - "event_type": "DEMISSION", - }, - ) - if not events: - log( - "*** situation inconsistante pour %s (demission mais pas d'event)" - % etudid - ) - date_dem = "???" # ??? - else: - date_dem = events[0]["event_date"] - situation += " le " + str(date_dem) - return situation - - # Debouche / devenir etudiant - - # vrai si l'utilisateur peut modifier les informations de suivi sur la page etud" - def can_edit_suivi(self, REQUEST=None): - authuser = REQUEST.AUTHENTICATED_USER - return authuser.has_permission(ScoEtudChangeAdr, self) - - security.declareProtected(ScoEtudChangeAdr, "itemsuivi_suppress") - itemsuivi_suppress = sco_debouche.itemsuivi_suppress - security.declareProtected(ScoEtudChangeAdr, "itemsuivi_create") - itemsuivi_create = sco_debouche.itemsuivi_create - security.declareProtected(ScoEtudChangeAdr, "itemsuivi_set_date") - itemsuivi_set_date = sco_debouche.itemsuivi_set_date - security.declareProtected(ScoEtudChangeAdr, "itemsuivi_set_situation") - itemsuivi_set_situation = sco_debouche.itemsuivi_set_situation - security.declareProtected(ScoView, "itemsuivi_list_etud") - itemsuivi_list_etud = sco_debouche.itemsuivi_list_etud - security.declareProtected(ScoView, "itemsuivi_tag_list") - itemsuivi_tag_list = sco_debouche.itemsuivi_tag_list - security.declareProtected(ScoView, "itemsuivi_tag_search") - itemsuivi_tag_search = sco_debouche.itemsuivi_tag_search - security.declareProtected(ScoEtudChangeAdr, "itemsuivi_tag_set") - itemsuivi_tag_set = sco_debouche.itemsuivi_tag_set - - security.declareProtected(ScoEtudAddAnnotations, "doAddAnnotation") - - def doAddAnnotation(self, etudid, comment, REQUEST): - "ajoute annotation sur etudiant" - authuser = REQUEST.AUTHENTICATED_USER - cnx = self.GetDBConnexion() - scolars.etud_annotations_create( - cnx, - args={ - "etudid": etudid, - "comment": comment, - "zope_authenticated_user": str(authuser), - "zope_remote_addr": REQUEST.REMOTE_ADDR, - }, - ) - logdb(REQUEST, cnx, method="addAnnotation", etudid=etudid) - return REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid) - - security.declareProtected(ScoView, "canSuppressAnnotation") - - def canSuppressAnnotation(self, annotation_id, REQUEST): - """True if current user can suppress this annotation - Seuls l'auteur de l'annotation et le chef de dept peuvent supprimer - une annotation. - """ - cnx = self.GetDBConnexion() - annos = scolars.etud_annotations_list(cnx, args={"id": annotation_id}) - if len(annos) != 1: - raise ScoValueError("annotation inexistante !") - anno = annos[0] - authuser = REQUEST.AUTHENTICATED_USER - # note: les anciennes installations n'ont pas le role ScoEtudSupprAnnotations - # c'est pourquoi on teste aussi ScoEtudInscrit (normalement détenue par le chef) - return ( - (str(authuser) == anno["zope_authenticated_user"]) - or authuser.has_permission(ScoEtudSupprAnnotations, self) - or authuser.has_permission(ScoEtudInscrit, self) - ) - - security.declareProtected(ScoView, "doSuppressAnnotation") - - def doSuppressAnnotation(self, etudid, annotation_id, REQUEST): - """Suppression annotation.""" - if not self.canSuppressAnnotation(annotation_id, REQUEST): - raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") - - cnx = self.GetDBConnexion() - annos = scolars.etud_annotations_list(cnx, args={"id": annotation_id}) - if len(annos) != 1: - raise ScoValueError("annotation inexistante !") - anno = annos[0] - log("suppress annotation: %s" % str(anno)) - logdb(REQUEST, cnx, method="SuppressAnnotation", etudid=etudid) - scolars.etud_annotations_delete(cnx, annotation_id) - - return REQUEST.RESPONSE.redirect( - "ficheEtud?etudid=%s&head_message=Annotation%%20supprimée" % (etudid) - ) - - security.declareProtected(ScoEtudChangeAdr, "formChangeCoordonnees") - - def formChangeCoordonnees(self, etudid, REQUEST): - "edit coordonnees etudiant" - cnx = self.GetDBConnexion() - etud = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] - adrs = scolars.adresse_list(cnx, {"etudid": etudid}) - if adrs: - adr = adrs[0] - else: - adr = {} # no data for this student - H = [ - '

Changement des coordonnées de %(nomprenom)s

' - % etud - ] - header = self.sco_header( - REQUEST, page_title="Changement adresse de %(nomprenom)s" % etud - ) - - tf = TrivialFormulator( - REQUEST.URL0, - REQUEST.form, - ( - ("adresse_id", {"input_type": "hidden"}), - ("etudid", {"input_type": "hidden"}), - ( - "email", - { - "size": 40, - "title": "e-mail", - "explanation": "adresse institutionnelle", - }, - ), - ( - "emailperso", - { - "size": 40, - "title": "e-mail", - "explanation": "adresse personnelle", - }, - ), - ( - "domicile", - {"size": 65, "explanation": "numéro, rue", "title": "Adresse"}, - ), - ("codepostaldomicile", {"size": 6, "title": "Code postal"}), - ("villedomicile", {"size": 20, "title": "Ville"}), - ("paysdomicile", {"size": 20, "title": "Pays"}), - ("", {"input_type": "separator", "default": " "}), - ("telephone", {"size": 13, "title": "Téléphone"}), - ("telephonemobile", {"size": 13, "title": "Mobile"}), - ), - initvalues=adr, - submitlabel="Valider le formulaire", - ) - dest_url = self.ScoURL() + "/ficheEtud?etudid=" + etudid - if tf[0] == 0: - return header + "\n".join(H) + tf[1] + self.sco_footer(REQUEST) - elif tf[0] == -1: - return REQUEST.RESPONSE.redirect(dest_url) - else: - if adrs: - scolars.adresse_edit(cnx, args=tf[2], context=self) - else: - scolars.adresse_create(cnx, args=tf[2]) - logdb(REQUEST, cnx, method="changeCoordonnees", etudid=etudid) - return REQUEST.RESPONSE.redirect(dest_url) - - # --- Gestion des groupes: - security.declareProtected(ScoView, "affectGroups") - affectGroups = sco_groups_edit.affectGroups - - security.declareProtected(ScoView, "XMLgetGroupsInPartition") - XMLgetGroupsInPartition = sco_groups.XMLgetGroupsInPartition - - security.declareProtected(ScoView, "formsemestre_partition_list") - formsemestre_partition_list = sco_groups.formsemestre_partition_list - - security.declareProtected(ScoView, "setGroups") - setGroups = sco_groups.setGroups - - security.declareProtected(ScoView, "createGroup") - createGroup = sco_groups.createGroup - - security.declareProtected(ScoView, "suppressGroup") - suppressGroup = sco_groups.suppressGroup - - security.declareProtected(ScoView, "group_set_name") - group_set_name = sco_groups.group_set_name - - security.declareProtected(ScoView, "group_rename") - group_rename = sco_groups.group_rename - - security.declareProtected(ScoView, "groups_auto_repartition") - groups_auto_repartition = sco_groups.groups_auto_repartition - - security.declareProtected(ScoView, "editPartitionForm") - editPartitionForm = sco_groups.editPartitionForm - - security.declareProtected(ScoView, "partition_delete") - partition_delete = sco_groups.partition_delete - - security.declareProtected(ScoView, "partition_set_attr") - partition_set_attr = sco_groups.partition_set_attr - - security.declareProtected(ScoView, "partition_move") - partition_move = sco_groups.partition_move - - security.declareProtected(ScoView, "partition_set_name") - partition_set_name = sco_groups.partition_set_name - - security.declareProtected(ScoView, "partition_rename") - partition_rename = sco_groups.partition_rename - - security.declareProtected(ScoView, "partition_create") - partition_create = sco_groups.partition_create - - security.declareProtected(ScoView, "etud_info_html") - etud_info_html = sco_page_etud.etud_info_html - - # --- Gestion des photos: - security.declareProtected(ScoView, "get_photo_image") - - get_photo_image = sco_photos.get_photo_image - - security.declareProtected(ScoView, "etud_photo_html") - - etud_photo_html = sco_photos.etud_photo_html - - security.declareProtected(ScoView, "etud_photo_orig_page") - - def etud_photo_orig_page(self, etudid=None, REQUEST=None): - "Page with photo in orig. size" - etud = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] - H = [ - self.sco_header(REQUEST, page_title=etud["nomprenom"]), - "

%s

" % etud["nomprenom"], - '
' % etudid, - sco_photos.etud_photo_orig_html(self, etud), - "
", - self.sco_footer(REQUEST), - ] - return "\n".join(H) - - security.declareProtected(ScoEtudChangeAdr, "formChangePhoto") - - def formChangePhoto(self, etudid=None, REQUEST=None): - """Formulaire changement photo étudiant""" - etud = self.getEtudInfo(filled=1, REQUEST=REQUEST)[0] - if sco_photos.etud_photo_is_local(self, etud): - etud["photoloc"] = "dans ScoDoc" - else: - etud["photoloc"] = "externe" - H = [ - self.sco_header(REQUEST, page_title="Changement de photo"), - """

Changement de la photo de %(nomprenom)s

-

Photo actuelle (%(photoloc)s): - """ - % etud, - sco_photos.etud_photo_html( - self, etud, title="photo actuelle", REQUEST=REQUEST - ), - """

Le fichier ne doit pas dépasser 500Ko (recadrer l'image, format "portrait" de préférence).

-

L'image sera automagiquement réduite pour obtenir une hauteur de 90 pixels.

- """, - ] - tf = TrivialFormulator( - REQUEST.URL0, - REQUEST.form, - ( - ("etudid", {"default": etudid, "input_type": "hidden"}), - ( - "photofile", - {"input_type": "file", "title": "Fichier image", "size": 20}, - ), - ), - submitlabel="Valider", - cancelbutton="Annuler", - ) - dest_url = self.ScoURL() + "/ficheEtud?etudid=" + etud["etudid"] - if tf[0] == 0: - return ( - "\n".join(H) - + tf[1] - + '

Supprimer cette photo

' - % etudid - + self.sco_footer(REQUEST) - ) - elif tf[0] == -1: - return REQUEST.RESPONSE.redirect(dest_url) - else: - data = tf[2]["photofile"].read() - status, diag = sco_photos.store_photo(self, etud, data, REQUEST=REQUEST) - if status != 0: - return REQUEST.RESPONSE.redirect(dest_url) - else: - H.append('

Erreur:' + diag + "

") - return "\n".join(H) + self.sco_footer(REQUEST) - - security.declareProtected(ScoEtudChangeAdr, "formSuppressPhoto") - - def formSuppressPhoto(self, etudid=None, REQUEST=None, dialog_confirmed=False): - """Formulaire suppression photo étudiant""" - etud = self.getEtudInfo(filled=1, REQUEST=REQUEST)[0] - if not dialog_confirmed: - return self.confirmDialog( - "

Confirmer la suppression de la photo de %(nomprenom)s ?

" % etud, - dest_url="", - REQUEST=REQUEST, - cancel_url="ficheEtud?etudid=%s" % etudid, - parameters={"etudid": etudid}, - ) - - sco_photos.suppress_photo(self, etud, REQUEST=REQUEST) - - return REQUEST.RESPONSE.redirect( - self.ScoURL() + "/ficheEtud?etudid=" + etud["etudid"] - ) - - # - security.declareProtected(ScoEtudInscrit, "formDem") - - def formDem(self, etudid, formsemestre_id, REQUEST): - "Formulaire Démission Etudiant" - return self._formDem_of_Def( - etudid, - formsemestre_id, - REQUEST=REQUEST, - operation_name="Démission", - operation_method="doDemEtudiant", - ) - - security.declareProtected(ScoEtudInscrit, "formDef") - - def formDef(self, etudid, formsemestre_id, REQUEST): - "Formulaire Défaillance Etudiant" - return self._formDem_of_Def( - etudid, - formsemestre_id, - REQUEST=REQUEST, - operation_name="Défaillance", - operation_method="doDefEtudiant", - ) - - def _formDem_of_Def( - self, - etudid, - formsemestre_id, - REQUEST=None, - operation_name="", - operation_method="", - ): - "Formulaire démission ou défaillance Etudiant" - etud = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] - sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) - if sem["etat"] != "1": - raise ScoValueError("Modification impossible: semestre verrouille") - - etud["formsemestre_id"] = formsemestre_id - etud["semtitre"] = sem["titremois"] - etud["nowdmy"] = time.strftime("%d/%m/%Y") - etud["operation_name"] = operation_name - # - header = self.sco_header( - REQUEST, - page_title="%(operation_name)s de %(nomprenom)s (du semestre %(semtitre)s)" - % etud, - ) - H = [ - '

%(operation_name)s de %(nomprenom)s (semestre %(semtitre)s)

' - % etud - ] - H.append( - """

- Date de la %s (J/M/AAAA):  - """ - % (operation_method, scu.strlower(operation_name)) - ) - H.append( - """ - - - -

- -

""" - % etud - ) - return header + "\n".join(H) + self.sco_footer(REQUEST) - - security.declareProtected(ScoEtudInscrit, "doDemEtudiant") - - def doDemEtudiant(self, etudid, formsemestre_id, event_date=None, REQUEST=None): - "Déclare la démission d'un etudiant dans le semestre" - return self._doDem_or_Def_Etudiant( - etudid, - formsemestre_id, - event_date=event_date, - etat_new="D", - operation_method="demEtudiant", - event_type="DEMISSION", - REQUEST=REQUEST, - ) - - security.declareProtected(ScoEtudInscrit, "doDefEtudiant") - - def doDefEtudiant(self, etudid, formsemestre_id, event_date=None, REQUEST=None): - "Déclare la défaillance d'un etudiant dans le semestre" - return self._doDem_or_Def_Etudiant( - etudid, - formsemestre_id, - event_date=event_date, - etat_new=sco_codes_parcours.DEF, - operation_method="defailleEtudiant", - event_type="DEFAILLANCE", - REQUEST=REQUEST, - ) - - def _doDem_or_Def_Etudiant( - self, - etudid, - formsemestre_id, - event_date=None, - etat_new="D", # 'D' or DEF - operation_method="demEtudiant", - event_type="DEMISSION", - REQUEST=None, - ): - "Démission ou défaillance d'un étudiant" - # marque 'D' ou DEF dans l'inscription au semestre et ajoute - # un "evenement" scolarite - cnx = self.GetDBConnexion() - # check lock - sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) - if sem["etat"] != "1": - raise ScoValueError("Modification impossible: semestre verrouille") - # - ins = self.Notes.do_formsemestre_inscription_list( - {"etudid": etudid, "formsemestre_id": formsemestre_id} - )[0] - if not ins: - raise ScoException("etudiant non inscrit ?!") - ins["etat"] = etat_new - self.Notes.do_formsemestre_inscription_edit( - args=ins, formsemestre_id=formsemestre_id - ) - logdb(REQUEST, cnx, method=operation_method, etudid=etudid) - scolars.scolar_events_create( - cnx, - args={ - "etudid": etudid, - "event_date": event_date, - "formsemestre_id": formsemestre_id, - "event_type": event_type, - }, - ) - if REQUEST: - return REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid) - - security.declareProtected(ScoEtudInscrit, "doCancelDem") - - def doCancelDem( - self, etudid, formsemestre_id, dialog_confirmed=False, args=None, REQUEST=None - ): - "Annule une démission" - return self._doCancelDem_or_Def( - etudid, - formsemestre_id, - dialog_confirmed=dialog_confirmed, - args=args, - operation_name="démission", - etat_current="D", - etat_new="I", - operation_method="cancelDem", - event_type="DEMISSION", - REQUEST=REQUEST, - ) - - security.declareProtected(ScoEtudInscrit, "doCancelDef") - - def doCancelDef( - self, etudid, formsemestre_id, dialog_confirmed=False, args=None, REQUEST=None - ): - "Annule la défaillance de l'étudiant" - return self._doCancelDem_or_Def( - etudid, - formsemestre_id, - dialog_confirmed=dialog_confirmed, - args=args, - operation_name="défaillance", - etat_current=sco_codes_parcours.DEF, - etat_new="I", - operation_method="cancelDef", - event_type="DEFAILLANCE", - REQUEST=REQUEST, - ) - - def _doCancelDem_or_Def( - self, - etudid, - formsemestre_id, - dialog_confirmed=False, - args=None, - operation_name="", # "démission" ou "défaillance" - etat_current="D", - etat_new="I", - operation_method="cancelDem", - event_type="DEMISSION", - REQUEST=None, - ): - "Annule une demission ou une défaillance" - # check lock - sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) - if sem["etat"] != "1": - raise ScoValueError("Modification impossible: semestre verrouille") - # verif - info = self.getEtudInfo(etudid, filled=True)[0] - ok = False - for i in info["ins"]: - if i["formsemestre_id"] == formsemestre_id: - if i["etat"] != etat_current: - raise ScoValueError("etudiant non %s !" % operation_name) - ok = True - break - if not ok: - raise ScoValueError("etudiant non inscrit ???") - if not dialog_confirmed: - return self.confirmDialog( - "

Confirmer l'annulation de la %s ?

" % operation_name, - dest_url="", - REQUEST=REQUEST, - cancel_url="ficheEtud?etudid=%s" % etudid, - parameters={"etudid": etudid, "formsemestre_id": formsemestre_id}, - ) - # - ins = self.Notes.do_formsemestre_inscription_list( - {"etudid": etudid, "formsemestre_id": formsemestre_id} - )[0] - if ins["etat"] != etat_current: - raise ScoException("etudiant non %s !!!" % etat_current) # obviously a bug - ins["etat"] = etat_new - cnx = self.GetDBConnexion() - self.Notes.do_formsemestre_inscription_edit( - args=ins, formsemestre_id=formsemestre_id - ) - logdb(REQUEST, cnx, method=operation_method, etudid=etudid) - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - cursor.execute( - "delete from scolar_events where etudid=%(etudid)s and formsemestre_id=%(formsemestre_id)s and event_type='" - + event_type - + "'", - {"etudid": etudid, "formsemestre_id": formsemestre_id}, - ) - cnx.commit() - return REQUEST.RESPONSE.redirect("ficheEtud?etudid=%s" % etudid) - - security.declareProtected(ScoEtudInscrit, "etudident_create_form") - - def etudident_create_form(self, REQUEST=None): - "formulaire creation individuelle etudiant" - return self.etudident_create_or_edit_form(REQUEST, edit=False) - - security.declareProtected(ScoEtudInscrit, "etudident_edit_form") - - def etudident_edit_form(self, REQUEST=None): - "formulaire edition individuelle etudiant" - return self.etudident_create_or_edit_form(REQUEST, edit=True) - - security.declareProtected(ScoEtudInscrit, "etudident_create_or_edit_form") - - def etudident_create_or_edit_form(self, REQUEST, edit): - "Le formulaire HTML" - H = [self.sco_header(REQUEST, init_jquery_ui=True)] - F = self.sco_footer(REQUEST) - etudid = REQUEST.form.get("etudid", None) - cnx = self.GetDBConnexion() - descr = [] - if not edit: - # creation nouvel etudiant - initvalues = {} - submitlabel = "Ajouter cet étudiant" - H.append( - """

Création d'un étudiant

-

En général, il est recommandé d'importer les étudiants depuis Apogée. - N'utilisez ce formulaire que pour les cas particuliers ou si votre établissement - n'utilise pas d'autre logiciel de gestion des inscriptions.

-

L'étudiant créé ne sera pas inscrit. - Pensez à l'inscrire dans un semestre !

- """ - ) - else: - # edition donnees d'un etudiant existant - # setup form init values - if not etudid: - raise ValueError("missing etudid parameter") - descr.append(("etudid", {"default": etudid, "input_type": "hidden"})) - H.append( - '

Modification d\'un étudiant (fiche)

' - % etudid - ) - initvalues = scolars.etudident_list(cnx, {"etudid": etudid}) - assert len(initvalues) == 1 - initvalues = initvalues[0] - submitlabel = "Modifier les données" - - # recuperation infos Apogee - # Si on a le code NIP, fait juste une requete, sinon tente de rechercher par nom - # (la recherche par nom ne fonctionne plus à Paris 13) - # XXX A terminer - # code_nip = initvalues.get("code_nip", "") - # if code_nip: - # try: - # infos = sco_portal_apogee.get_etud_apogee(self, code_nip) - # except ValueError: - # infos = None - # pass # XXX a terminer - nom = REQUEST.form.get("nom", None) - if nom is None: - nom = initvalues.get("nom", None) - if nom is None: - infos = [] - else: - prenom = REQUEST.form.get("prenom", "") - if REQUEST.form.get("tf-submitted", False) and not prenom: - prenom = initvalues.get("prenom", "") - infos = sco_portal_apogee.get_infos_apogee(self, nom, prenom) - - if infos: - formatted_infos = [ - """ - -
    """ - ] - nanswers = len(infos) - nmax = 10 # nb max de reponse montrees - infos = infos[:nmax] - for i in infos: - formatted_infos.append("
    • ") - for k in i.keys(): - if k != "nip": - item = "
    • %s : %s
    • " % (k, i[k]) - else: - item = ( - '
    • %s : %s
    • ' - % (k, i[k], i[k]) - ) - formatted_infos.append(item) - - formatted_infos.append("
  1. ") - formatted_infos.append("
") - m = "%d étudiants trouvés" % nanswers - if len(infos) != nanswers: - m += " (%d montrés)" % len(infos) - A = """
-
Informations Apogée
-

%s

- %s -
""" % ( - m, - "\n".join(formatted_infos), - ) - else: - A = """

Pas d'informations d'Apogée

""" - - require_ine = self.get_preference("always_require_ine") - - descr += [ - ("adm_id", {"input_type": "hidden"}), - ("nom", {"size": 25, "title": "Nom", "allow_null": False}), - ("nom_usuel", {"size": 25, "title": "Nom usuel", "allow_null": True}), - ( - "prenom", - { - "size": 25, - "title": "Prénom", - "allow_null": scu.CONFIG.ALLOW_NULL_PRENOM, - }, - ), - ( - "civilite", - { - "input_type": "menu", - "labels": ["Homme", "Femme", "Autre/neutre"], - "allowed_values": ["M", "F", "X"], - "title": "Civilité", - }, - ), - ( - "date_naissance", - { - "title": "Date de naissance", - "input_type": "date", - "explanation": "j/m/a", - }, - ), - ("lieu_naissance", {"title": "Lieu de naissance", "size": 32}), - ("dept_naissance", {"title": "Département de naissance", "size": 5}), - ("nationalite", {"size": 25, "title": "Nationalité"}), - ( - "statut", - { - "size": 25, - "title": "Statut", - "explanation": '("salarie", ...) inutilisé par ScoDoc', - }, - ), - ( - "annee", - { - "size": 5, - "title": "Année admission IUT", - "type": "int", - "allow_null": False, - "explanation": "année 1ere inscription (obligatoire)", - }, - ), - # - ("sep", {"input_type": "separator", "title": "Scolarité antérieure:"}), - ("bac", {"size": 5, "explanation": "série du bac (S, STI, STT, ...)"}), - ( - "specialite", - { - "size": 25, - "title": "Spécialité", - "explanation": "spécialité bac: SVT M, GENIE ELECTRONIQUE, ...", - }, - ), - ( - "annee_bac", - { - "size": 5, - "title": "Année bac", - "type": "int", - "explanation": "année obtention du bac", - }, - ), - ( - "math", - { - "size": 3, - "type": "float", - "title": "Note de mathématiques", - "explanation": "note sur 20 en terminale", - }, - ), - ( - "physique", - { - "size": 3, - "type": "float", - "title": "Note de physique", - "explanation": "note sur 20 en terminale", - }, - ), - ( - "anglais", - { - "size": 3, - "type": "float", - "title": "Note d'anglais", - "explanation": "note sur 20 en terminale", - }, - ), - ( - "francais", - { - "size": 3, - "type": "float", - "title": "Note de français", - "explanation": "note sur 20 obtenue au bac", - }, - ), - ( - "type_admission", - { - "input_type": "menu", - "title": "Voie d'admission", - "allowed_values": scu.TYPES_ADMISSION, - }, - ), - ( - "boursier_prec", - { - "input_type": "boolcheckbox", - "labels": ["non", "oui"], - "title": "Boursier ?", - "explanation": "dans le cycle précédent (lycée)", - }, - ), - ( - "rang", - { - "size": 1, - "type": "int", - "title": "Position établissement", - "explanation": "rang de notre établissement dans les voeux du candidat (si connu)", - }, - ), - ( - "qualite", - { - "size": 3, - "type": "float", - "title": "Qualité", - "explanation": "Note de qualité attribuée au dossier (par le jury d'adm.)", - }, - ), - ( - "decision", - { - "input_type": "menu", - "title": "Décision", - "allowed_values": [ - "ADMIS", - "ATTENTE 1", - "ATTENTE 2", - "ATTENTE 3", - "REFUS", - "?", - ], - }, - ), - ( - "score", - { - "size": 3, - "type": "float", - "title": "Score", - "explanation": "score calculé lors de l'admission", - }, - ), - ( - "classement", - { - "size": 3, - "type": "int", - "title": "Classement", - "explanation": "Classement par le jury d'admission (de 1 à N)", - }, - ), - ("apb_groupe", {"size": 15, "title": "Groupe APB ou PS"}), - ( - "apb_classement_gr", - { - "size": 3, - "type": "int", - "title": "Classement", - "explanation": "Classement par le jury dans le groupe ABP ou PS (de 1 à Ng)", - }, - ), - ("rapporteur", {"size": 50, "title": "Enseignant rapporteur"}), - ( - "commentaire", - { - "input_type": "textarea", - "rows": 4, - "cols": 50, - "title": "Note du rapporteur", - }, - ), - ("nomlycee", {"size": 20, "title": "Lycée d'origine"}), - ("villelycee", {"size": 15, "title": "Commune du lycée"}), - ("codepostallycee", {"size": 15, "title": "Code Postal lycée"}), - ( - "codelycee", - { - "size": 15, - "title": "Code Lycée", - "explanation": "Code national établissement du lycée ou établissement d'origine", - }, - ), - ("sep", {"input_type": "separator", "title": "Codes Apogée: (optionnels)"}), - ( - "code_nip", - { - "size": 25, - "title": "Numéro NIP", - "allow_null": True, - "explanation": "numéro identité étudiant (Apogée)", - }, - ), - ( - "code_ine", - { - "size": 25, - "title": "Numéro INE", - "allow_null": not require_ine, - "explanation": "numéro INE", - }, - ), - ( - "dont_check_homonyms", - { - "title": "Autoriser les homonymes", - "input_type": "boolcheckbox", - "explanation": "ne vérifie pas les noms et prénoms proches", - }, - ), - ] - initvalues["dont_check_homonyms"] = False - tf = TrivialFormulator( - REQUEST.URL0, - REQUEST.form, - descr, - submitlabel=submitlabel, - cancelbutton="Re-interroger Apogee", - initvalues=initvalues, - ) - if tf[0] == 0: - return "\n".join(H) + tf[1] + "

" + A + F - elif tf[0] == -1: - return "\n".join(H) + tf[1] + "

" + A + F - # return '\n'.join(H) + '

annulation

' + F - else: - # form submission - if edit: - etudid = tf[2]["etudid"] - else: - etudid = None - ok, NbHomonyms = scolars.check_nom_prenom( - cnx, nom=tf[2]["nom"], prenom=tf[2]["prenom"], etudid=etudid - ) - if not ok: - return ( - "\n".join(H) - + tf_error_message("Nom ou prénom invalide") - + tf[1] - + "

" - + A - + F - ) - # log('NbHomonyms=%s' % NbHomonyms) - if not tf[2]["dont_check_homonyms"] and NbHomonyms > 0: - return ( - "\n".join(H) - + tf_error_message( - """Attention: il y a déjà un étudiant portant des noms et prénoms proches. Vous pouvez forcer la présence d'un homonyme en cochant "autoriser les homonymes" en bas du formulaire.""" - ) - + tf[1] - + "

" - + A - + F - ) - - if not edit: - etud = scolars.create_etud(self, cnx, args=tf[2], REQUEST=REQUEST) - etudid = etud["etudid"] - else: - # modif d'un etudiant - scolars.etudident_edit(cnx, tf[2], context=self, REQUEST=REQUEST) - etud = scolars.etudident_list(cnx, {"etudid": etudid})[0] - self.fillEtudsInfo([etud]) - # Inval semesters with this student: - to_inval = [s["formsemestre_id"] for s in etud["sems"]] - if to_inval: - self.Notes._inval_cache( - formsemestre_id_list=to_inval - ) # > etudident_create_or_edit - # - return REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid) - - security.declareProtected(ScoEtudInscrit, "etudident_delete") - - def etudident_delete(self, etudid, dialog_confirmed=False, REQUEST=None): - "Delete a student" - cnx = self.GetDBConnexion() - etuds = scolars.etudident_list(cnx, {"etudid": etudid}) - if not etuds: - raise ScoValueError("Etudiant inexistant !") - else: - etud = etuds[0] - self.fillEtudsInfo([etud]) - if not dialog_confirmed: - return self.confirmDialog( - """

Confirmer la suppression de l'étudiant %(nomprenom)s ?

-

-

Prenez le temps de vérifier que vous devez vraiment supprimer cet étudiant !

-

Cette opération irréversible efface toute trace de l'étudiant: inscriptions, notes, absences... dans tous les semestres qu'il a fréquenté.

-

Dans la plupart des cas, vous avez seulement besoin de le

    désinscrire
d'un semestre ? (dans ce cas passez par sa fiche, menu associé au semestre)

- -

Vérifier la fiche de %(nomprenom)s -

""" - % etud, - dest_url="", - REQUEST=REQUEST, - cancel_url="ficheEtud?etudid=%s" % etudid, - OK="Supprimer définitivement cet étudiant", - parameters={"etudid": etudid}, - ) - log("etudident_delete: etudid=%(etudid)s nomprenom=%(nomprenom)s" % etud) - # delete in all tables ! - tables = [ - "notes_appreciations", - "scolar_autorisation_inscription", - "scolar_formsemestre_validation", - "scolar_events", - "notes_notes_log", - "notes_notes", - "notes_moduleimpl_inscription", - "notes_formsemestre_inscription", - "group_membership", - "entreprise_contact", - "etud_annotations", - "scolog", - "admissions", - "adresse", - "absences", - "billet_absence", - "identite", - ] - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - for table in tables: - cursor.execute("delete from %s where etudid=%%(etudid)s" % table, etud) - cnx.commit() - # Inval semestres où il était inscrit: - to_inval = [s["formsemestre_id"] for s in etud["sems"]] - if to_inval: - self.Notes._inval_cache(formsemestre_id_list=to_inval) # > - return REQUEST.RESPONSE.redirect( - self.ScoURL() + r"?head_message=Etudiant%20supprimé" - ) - - security.declareProtected(ScoEtudInscrit, "check_group_apogee") - - def check_group_apogee( - self, group_id, REQUEST=None, etat=None, fix=False, fixmail=False - ): - """Verification des codes Apogee et mail de tout un groupe. - Si fix == True, change les codes avec Apogée. - - XXX A re-écrire pour API 2: prendre liste dans l'étape et vérifier à partir de cela. - """ - etat = etat or None - members, group, _, sem, _ = sco_groups.get_group_infos( - self, group_id, etat=etat - ) - formsemestre_id = group["formsemestre_id"] - - cnx = self.GetDBConnexion() - H = [ - self.Notes.html_sem_header( - REQUEST, "Etudiants du %s" % (group["group_name"] or "semestre"), sem - ), - '', - "", - ] - nerrs = 0 # nombre d'anomalies détectées - nfix = 0 # nb codes changes - nmailmissing = 0 # nb etuds sans mail - for t in members: - nom, nom_usuel, prenom, etudid, email, code_nip = ( - t["nom"], - t["nom_usuel"], - t["prenom"], - t["etudid"], - t["email"], - t["code_nip"], - ) - infos = sco_portal_apogee.get_infos_apogee(self, nom, prenom) - if not infos: - info_apogee = ( - 'Pas d\'information (Modifier identité)' - % etudid - ) - nerrs += 1 - else: - if len(infos) == 1: - nip_apogee = infos[0]["nip"] - if code_nip != nip_apogee: - if fix: - # Update database - scolars.identite_edit( - cnx, - args={"etudid": etudid, "code_nip": nip_apogee}, - context=self, - ) - info_apogee = ( - 'copié %s' % nip_apogee - ) - nfix += 1 - else: - info_apogee = ( - '%s' % nip_apogee - ) - nerrs += 1 - else: - info_apogee = "ok" - else: - info_apogee = ( - '%d correspondances (Choisir)' - % (len(infos), etudid) - ) - nerrs += 1 - # check mail - if email: - mailstat = "ok" - else: - if fixmail and len(infos) == 1 and "mail" in infos[0]: - mail_apogee = infos[0]["mail"] - adrs = scolars.adresse_list(cnx, {"etudid": etudid}) - if adrs: - adr = adrs[0] # modif adr existante - args = {"adresse_id": adr["adresse_id"], "email": mail_apogee} - scolars.adresse_edit(cnx, args=args) - else: - # creation adresse - args = {"etudid": etudid, "email": mail_apogee} - scolars.adresse_create(cnx, args=args) - mailstat = 'copié' - else: - mailstat = "inconnu" - nmailmissing += 1 - H.append( - '' - % (etudid, nom, nom_usuel, prenom, mailstat, code_nip, info_apogee) - ) - H.append("
NomNom usuelPrénomMailNIP (ScoDoc)Apogée
%s%s%s%s%s%s
") - H.append("
    ") - if nfix: - H.append("
  • %d codes modifiés
  • " % nfix) - H.append("
  • Codes NIP: %d anomalies détectées
  • " % nerrs) - H.append( - "
  • Adresse mail: %d étudiants sans adresse
  • " % nmailmissing - ) - H.append("
") - H.append( - """ -
- - - - - -
-

Retour au semestre - """ - % ( - REQUEST.URL0, - formsemestre_id, - scu.strnone(group_id), - scu.strnone(etat), - formsemestre_id, - ) - ) - H.append( - """ -

- - - - - -
-

Retour au semestre - """ - % ( - REQUEST.URL0, - formsemestre_id, - scu.strnone(group_id), - scu.strnone(etat), - formsemestre_id, - ) - ) - - return "\n".join(H) + self.sco_footer(REQUEST) - - security.declareProtected(ScoEtudInscrit, "form_students_import_excel") - - def form_students_import_excel(self, REQUEST, formsemestre_id=None): - "formulaire import xls" - if formsemestre_id: - sem = sco_formsemestre.get_formsemestre(self.Notes, formsemestre_id) - dest_url = ( - self.ScoURL() - + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id - ) - else: - sem = None - dest_url = self.ScoURL() - if sem and sem["etat"] != "1": - raise ScoValueError("Modification impossible: semestre verrouille") - H = [ - self.sco_header(REQUEST, page_title="Import etudiants"), - """

Téléchargement d\'une nouvelle liste d\'etudiants

-
-

A utiliser pour importer de nouveaux étudiants (typiquement au - premier semestre).

-

Si les étudiants à inscrire sont déjà dans un autre - semestre, utiliser le menu "Inscriptions (passage des étudiants) - depuis d'autres semestres à partir du semestre destination. -

-

Si vous avez un portail Apogée, il est en général préférable d'importer les - étudiants depuis Apogée, via le menu "Synchroniser avec étape Apogée". -

-
-

- L'opération se déroule en deux étapes. Dans un premier temps, - vous téléchargez une feuille Excel type. Vous devez remplir - cette feuille, une ligne décrivant chaque étudiant. Ensuite, - vous indiquez le nom de votre fichier dans la case "Fichier Excel" - ci-dessous, et cliquez sur "Télécharger" pour envoyer au serveur - votre liste. -

- """, - ] # ' - if sem: - H.append( - """

Les étudiants importés seront inscrits dans - le semestre %s

""" - % sem["titremois"] - ) - else: - H.append( - """ -

Pour inscrire directement les étudiants dans un semestre de - formation, il suffit d'indiquer le code de ce semestre - (qui doit avoir été créé au préalable). Cliquez ici pour afficher les codes -

- """ - % (self.ScoURL()) - ) - - H.append("""
  1. """) - if formsemestre_id: - H.append( - """ - - """ - ) - else: - H.append("""""") - H.append( - """Obtenir la feuille excel à remplir
  2. -
  3. """ - ) - - F = self.sco_footer(REQUEST) - tf = TrivialFormulator( - REQUEST.URL0, - REQUEST.form, - ( - ( - "csvfile", - {"title": "Fichier Excel:", "input_type": "file", "size": 40}, - ), - ( - "check_homonyms", - { - "title": "Vérifier les homonymes", - "input_type": "boolcheckbox", - "explanation": "arrète l'importation si plus de 10% d'homonymes", - }, - ), - ( - "require_ine", - { - "title": "Importer INE", - "input_type": "boolcheckbox", - "explanation": "n'importe QUE les étudiants avec nouveau code INE", - }, - ), - ("formsemestre_id", {"input_type": "hidden"}), - ), - initvalues={"check_homonyms": True, "require_ine": False}, - submitlabel="Télécharger", - ) - S = [ - """

    Le fichier Excel décrivant les étudiants doit comporter les colonnes suivantes. -

    Les colonnes peuvent être placées dans n'importe quel ordre, mais -le titre exact (tel que ci-dessous) doit être sur la première ligne. -

    -

    -Les champs avec un astérisque (*) doivent être présents (nulls non autorisés). -

    - - -

    - -""" - ] - for t in ImportScolars.sco_import_format( - with_codesemestre=(formsemestre_id == None) - ): - if int(t[3]): - ast = "" - else: - ast = "*" - S.append( - "" - % (t[0], t[1], t[4], ast) - ) - if tf[0] == 0: - return "\n".join(H) + tf[1] + "" + "\n".join(S) + F - elif tf[0] == -1: - return REQUEST.RESPONSE.redirect(dest_url) - else: - return ImportScolars.students_import_excel( - self, - tf[2]["csvfile"], - REQUEST=REQUEST, - formsemestre_id=formsemestre_id, - check_homonyms=tf[2]["check_homonyms"], - require_ine=tf[2]["require_ine"], - ) - - security.declareProtected(ScoEtudInscrit, "import_generate_excel_sample") - - def import_generate_excel_sample(self, REQUEST, with_codesemestre="1"): - "une feuille excel pour importation etudiants" - if with_codesemestre: - with_codesemestre = int(with_codesemestre) - else: - with_codesemestre = 0 - format = ImportScolars.sco_import_format() - data = ImportScolars.sco_import_generate_excel_sample( - format, with_codesemestre, exclude_cols=["photo_filename"], REQUEST=REQUEST - ) - return sco_excel.sendExcelFile(REQUEST, data, "ImportEtudiants.xls") - - # --- Données admission - security.declareProtected(ScoView, "import_generate_admission_sample") - - def import_generate_admission_sample(self, REQUEST, formsemestre_id): - "une feuille excel pour importation données admissions" - group = sco_groups.get_group( - self, sco_groups.get_default_group(self, formsemestre_id) - ) - fmt = ImportScolars.sco_import_format() - data = ImportScolars.sco_import_generate_excel_sample( - fmt, - only_tables=["identite", "admissions", "adresse"], - exclude_cols=["nationalite", "foto", "photo_filename"], - group_ids=[group["group_id"]], - context=self.Notes, - REQUEST=REQUEST, - ) - return sco_excel.sendExcelFile(REQUEST, data, "AdmissionEtudiants.xls") - - # --- Données admission depuis fichier excel (version nov 2016) - security.declareProtected(ScoView, "form_students_import_infos_admissions") - - def form_students_import_infos_admissions(self, REQUEST, formsemestre_id=None): - "formulaire import xls" - authuser = REQUEST.AUTHENTICATED_USER - F = self.sco_footer(REQUEST) - if not authuser.has_permission(ScoEtudInscrit, self): - # autorise juste l'export - H = [ - self.sco_header( - REQUEST, - page_title="Export données admissions (Parcoursup ou autre)", - ), - """

    Téléchargement des informations sur l'admission des étudiants

    -

    - Exporter les informations de ScoDoc (classeur Excel) (ce fichier peut être ré-importé après d'éventuelles modifications) -

    -

    Vous n'avez pas le droit d'importer les données

    - """ - % {"formsemestre_id": formsemestre_id}, - ] - return "\n".join(H) + F - - # On a le droit d'importer: - H = [ - self.sco_header(REQUEST, page_title="Import données admissions Parcoursup"), - """

    Téléchargement des informations sur l'admission des étudiants depuis feuilles import Parcoursup

    -
    -

    A utiliser pour renseigner les informations sur l'origine des étudiants (lycées, bac, etc). Ces informations sont facultatives mais souvent utiles pour mieux connaitre les étudiants et aussi pour effectuer des statistiques (résultats suivant le type de bac...). Les données sont affichées sur les fiches individuelles des étudiants.

    -
    -

    - Importer ici la feuille excel utilisée pour envoyer le classement Parcoursup. - Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés, - les autres lignes de la feuille seront ignorées. Et seules les colonnes intéressant ScoDoc - seront importées: il est inutile d'éliminer les autres. -
    - Seules les données "admission" seront modifiées (et pas l'identité de l'étudiant). -
    - Les colonnes "nom" et "prenom" sont requises, ou bien une colonne "etudid". -

    -

    - Avant d'importer vos données, il est recommandé d'enregistrer les informations actuelles: - exporter les données actuelles de ScoDoc (ce fichier peut être ré-importé après d'éventuelles modifications) -

    - """ - % {"formsemestre_id": formsemestre_id}, - ] # ' - - type_admission_list = ( - "Autre", - "Parcoursup", - "Parcoursup PC", - "APB", - "APB PC", - "CEF", - "Direct", - ) - - tf = TrivialFormulator( - REQUEST.URL0, - REQUEST.form, - ( - ( - "csvfile", - {"title": "Fichier Excel:", "input_type": "file", "size": 40}, - ), - ( - "type_admission", - { - "title": "Type d'admission", - "explanation": "sera attribué aux étudiants modifiés par cet import n'ayant pas déjà un type", - "input_type": "menu", - "allowed_values": type_admission_list, - }, - ), - ("formsemestre_id", {"input_type": "hidden"}), - ), - submitlabel="Télécharger", - ) - - help_text = ( - """

    Les colonnes importables par cette fonction sont indiquées dans la table ci-dessous. - Seule la première feuille du classeur sera utilisée. -

    - """ - + ImportScolars.adm_table_description_format(self).html() - + """
    """ - ) - - if tf[0] == 0: - return "\n".join(H) + tf[1] + help_text + F - elif tf[0] == -1: - return REQUEST.RESPONSE.redirect( - self.ScoURL() - + "/formsemestre_status?formsemestre_id=" - + formsemestre_id - ) - else: - return self._students_import_admission( - tf[2]["csvfile"], - type_admission=tf[2]["type_admission"], - REQUEST=REQUEST, - formsemestre_id=formsemestre_id, - ) - - # unpublished - def _students_import_admission( - self, csvfile, type_admission="", REQUEST=None, formsemestre_id=None - ): - "import donnees admission from Excel file (v2016)" - diag = ImportScolars.scolars_import_admission( - csvfile, - self.Notes, - REQUEST, - formsemestre_id=formsemestre_id, - type_admission=type_admission, - ) - if REQUEST: - H = [self.sco_header(REQUEST, page_title="Import données admissions")] - H.append("

    Import terminé !

    ") - H.append( - '

    Continuer

    ' - % "formsemestre_status?formsemestre_id=%s" - % formsemestre_id - ) - if diag: - H.append( - "

    Diagnostic:

    • %s

    " % "
  4. ".join(diag) - ) - - return "\n".join(H) + self.sco_footer(REQUEST) - - security.declareProtected(ScoEtudChangeAdr, "formsemestre_import_etud_admission") - - def formsemestre_import_etud_admission( - self, formsemestre_id, import_email=True, REQUEST=None - ): - """Reimporte donnees admissions par synchro Portail Apogée""" - ( - no_nip, - unknowns, - changed_mails, - ) = sco_synchro_etuds.formsemestre_import_etud_admission( - self.Notes, formsemestre_id, import_identite=True, import_email=import_email - ) - H = [ - self.Notes.html_sem_header(REQUEST, "Reimport données admission"), - "

    Opération effectuée

    ", - ] - if no_nip: - H.append("

    Attention: étudiants sans NIP: " + str(no_nip) + "

    ") - if unknowns: - H.append( - "

    Attention: étudiants inconnus du portail: codes NIP=" - + str(unknowns) - + "

    " - ) - if changed_mails: - H.append("

    Adresses mails modifiées:

    ") - for (info, new_mail) in changed_mails: - H.append( - "%s: %s devient %s
    " - % (info["nom"], info["email"], new_mail) - ) - return "\n".join(H) + self.sco_footer(REQUEST) - - security.declareProtected(ScoEtudChangeAdr, "photos_import_files_form") - photos_import_files_form = sco_trombino.photos_import_files_form - security.declareProtected(ScoEtudChangeAdr, "photos_generate_excel_sample") - photos_generate_excel_sample = sco_trombino.photos_generate_excel_sample - - # --- Statistiques - security.declareProtected(ScoView, "stat_bac") - - def stat_bac(self, formsemestre_id): - "Renvoie statistisques sur nb d'etudiants par bac" - cnx = self.GetDBConnexion() - ins = self.Notes.do_formsemestre_inscription_list( - args={"formsemestre_id": formsemestre_id} - ) - Bacs = {} # type bac : nb etud - for i in ins: - etud = scolars.etudident_list(cnx, {"etudid": i["etudid"]})[0] - typebac = "%(bac)s %(specialite)s" % etud - Bacs[typebac] = Bacs.get(typebac, 0) + 1 - return Bacs - - # --- Dump - security.declareProtected(ScoView, "sco_dump_and_send_db") - sco_dump_and_send_db = sco_dump_db.sco_dump_and_send_db - - # - def confirmDialog( - self, - message="

    Confirmer ?

    ", - OK="OK", - Cancel="Annuler", - dest_url="", - cancel_url="", - target_variable="dialog_confirmed", - parameters={}, - add_headers=True, # complete page - REQUEST=None, # required - helpmsg=None, - ): - # dialog de confirmation simple - parameters[target_variable] = 1 - # Attention: la page a pu etre servie en GET avec des parametres - # si on laisse l'url "action" vide, les parametres restent alors que l'on passe en POST... - if not dest_url: - dest_url = REQUEST.URL - # strip remaining parameters from destination url: - dest_url = urllib.splitquery(dest_url)[0] - H = [ - """
    """ % dest_url, - message, - """""" % OK, - ] - if cancel_url: - H.append( - """""" - % (Cancel, cancel_url) - ) - for param in parameters.keys(): - if parameters[param] is None: - parameters[param] = "" - if type(parameters[param]) == type([]): - for e in parameters[param]: - H.append('' % (param, e)) - else: - H.append( - '' - % (param, parameters[param]) - ) - H.append("") - if helpmsg: - H.append('

    ' + helpmsg + "

    ") - if add_headers and REQUEST: - return self.sco_header(REQUEST) + "\n".join(H) + self.sco_footer(REQUEST) - else: - return "\n".join(H) - - # -------------------------------------------------------------------- - # Certaines methodes devant etre appeles en dehors de ZNotes: - security.declareProtected(ScoView, "formsemestre_edit_uecoefs") - formsemestre_edit_uecoefs = sco_formsemestre_edit.formsemestre_edit_uecoefs - - -# -# Product Administration -# - - -def manage_addZScolar( - self, - id="id_ZScolar", - title="The Title for ZScolar Object", - db_cnx_string="the db connexion string", - REQUEST=None, -): - "Add a ZScolar instance to a folder." - zscolar = ZScolar(id, title, db_cnx_string=db_cnx_string) - self._setObject(id, zscolar) - - -# The form used to get the instance id from the user. -def manage_addZScolarForm(context, DeptId, REQUEST=None): - """Form used to create a new ZScolar instance""" - - if not re.match("^[a-zA-Z0-9_]+$", DeptId): - raise ScoValueError("Invalid department id: %s" % DeptId) - - H = [ - context.standard_html_header(context), - "

    Ajout d'un département ScoDoc

    ", - """

    Cette page doit être utilisée pour ajouter un nouveau - département au site.

    - -

    Avant d'ajouter le département, il faut impérativement - avoir préparé la base de données en lançant le script - create_dept.sh nom_du_site en tant que - root sur le serveur. -

    """, - ] - - descr = [ - ( - "db_cnx_string", - { - "title": "DB connexion string", - "size": 32, - "explanation": "laisser vide si BD locale standard", - }, - ), - ("pass2", {"input_type": "hidden", "default": "1"}), - ("DeptId", {"input_type": "hidden", "default": DeptId}), - ] - - tf = TrivialFormulator( - REQUEST.URL0, REQUEST.form, descr, submitlabel="Créer le site ScoDoc" - ) - if tf[0] == 0: - return "\n".join(H) + tf[1] + context.standard_html_footer(context) - elif tf[0] == -1: - return REQUEST.RESPONSE.redirect("/ScoDoc") - else: - DeptId = tf[2]["DeptId"].strip() - db_cnx_string = tf[2]["db_cnx_string"].strip() - # default connexion string - if not db_cnx_string: - db_name = "SCO" + DeptId.upper() - db_user = scu.SCO_DEFAULT_SQL_USER - db_cnx_string = "user=%s dbname=%s port=%s" % ( - db_user, - db_name, - scu.SCO_DEFAULT_SQL_PORT, - ) - # vérifie que la bd existe et possede le meme nom de dept. - try: - cnx = psycopg2.connect(db_cnx_string) - cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) - cursor.execute("select * from sco_prefs where name='DeptName'") - except: - return _simple_error_page( - context, "Echec de la connexion à la BD (%s)" % db_cnx_string, DeptId - ) - r = cursor.dictfetchall() - if not r: - return _simple_error_page( - context, "Pas de departement défini dans la BD", DeptId - ) - if r[0]["value"] != DeptId: - return _simple_error_page( - context, - "La BD ne correspond pas: nom departement='%s'" % r[0]["value"], - DeptId, - ) - # ok, crée instance ScoDoc: - manage_addZScolar( - context, - id="Scolarite", - title="ScoDoc for %s" % DeptId, - db_cnx_string=db_cnx_string, - ) - - return REQUEST.RESPONSE.redirect("index_html") - - -def _simple_error_page(context, msg, DeptId=None): - """Minimal error page (used by installer only).""" - H = [context.standard_html_header(context), "

    Erreur !

    ", "

    ", msg, "

    "] - if DeptId: - H.append( - '

    Supprimer le dossier %s(très recommandé !)

    ' - % (DeptId, DeptId) - ) - H.append(context.standard_html_footer(context)) - return "\n".join(H) diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py index 51cc641f09..3ec9d5652f 100644 --- a/app/scodoc/gen_tables.py +++ b/app/scodoc/gen_tables.py @@ -626,7 +626,7 @@ class GenTable: if with_html_headers: H.append( self.html_header - or context.sco_header( + or html_sco_header.sco_header(context, REQUEST, page_title=page_title, javascripts=javascripts, @@ -637,7 +637,7 @@ class GenTable: H.append(html_title) H.append(self.html()) if with_html_headers: - H.append(context.sco_footer(REQUEST)) + H.append(html_sco_header.sco_footer(context, REQUEST)) return "\n".join(H) elif format == "pdf": objects = self.pdf() diff --git a/app/scodoc/html_sco_header.py b/app/scodoc/html_sco_header.py index 3eb22aab4f..be8cc3342c 100644 --- a/app/scodoc/html_sco_header.py +++ b/app/scodoc/html_sco_header.py @@ -28,6 +28,7 @@ import cgi import sco_utils as scu +import VERSION """ HTML Header/Footer for ScoDoc pages @@ -49,6 +50,87 @@ BOOTSTRAP_MULTISELECT_CSS = [ "libjs/bootstrap-multiselect/bootstrap-multiselect.css", ] + +def standard_html_header(): + """Standard HTML header for pages outside depts""" + # not used in ZScolar, see sco_header + return """ + +ScoDoc: accueil + + + + + + + +%s""" % ( + scu.SCO_ENCODING, + scu.CUSTOM_HTML_HEADER_CNX, + ) + + +def standard_html_footer(): + """Le pied de page HTML de la page d'accueil.""" + return """ +

    Problèmes et suggestions sur le logiciel: %s

    +

    ScoDoc est un logiciel libre développé par Emmanuel Viennet.

    +""" % ( + scu.SCO_USERS_LIST, + scu.SCO_USERS_LIST, + ) + + +_TOP_LEVEL_CSS = """ + """ + +_HTML_BEGIN = """ + + + +%(page_title)s + + + + + + + + + + + + + + + + + + + + + + + + + +""" + + +def scodoc_top_html_header(context, REQUEST, page_title="ScoDoc"): + H = [ + HTML_BEGIN % {"page_title": "ScoDoc: bienvenue", "encoding": scu.SCO_ENCODING}, + TOP_LEVEL_CSS, + """""", + scu.CUSTOM_HTML_HEADER_CNX, + ] + return "\n".join(H) + + # Header: def sco_header( context, @@ -72,14 +154,6 @@ def sco_header( ): "Main HTML page header for ScoDoc" - # If running for first time, initialize roles and permissions - try: - ri = context.roles_initialized - except: - ri = None # old instances does not have this attribute - if ri == "0": - context._setup_initial_roles_and_permissions() - # context est une instance de ZScolar. container est une instance qui "acquiert" ZScolar if container: context = container # je pense que cela suffit pour ce qu'on veut. @@ -93,7 +167,7 @@ def sco_header( head_message = REQUEST.form["head_message"] params = { - "page_title": page_title or context.title_or_id(), + "page_title": page_title or VERSION.SCONAME, "no_side_bar": no_side_bar, "ScoURL": context.ScoURL(), "encoding": scu.SCO_ENCODING, @@ -228,7 +302,7 @@ def sco_header( H.append(scu.CUSTOM_HTML_HEADER) # if not no_side_bar: - H.append(context.sidebar(REQUEST)) + H.append(html_sidebar.sidebar(context, REQUEST)) H.append("""
    """) # # Barre menu semestre: @@ -264,4 +338,28 @@ def sco_footer(context, REQUEST=None): ) +def html_sem_header( + context, + REQUEST, + title, + sem=None, + with_page_header=True, + with_h2=True, + page_title=None, + **args +): + "Titre d'une page semestre avec lien vers tableau de bord" + # sem now unused and thus optional... + if with_page_header: + h = sco_header( + context, REQUEST, page_title="%s" % (page_title or title), **args + ) + else: + h = "" + if with_h2: + return h + """

    %s

    """ % (title) + else: + return h + + from sco_formsemestre_status import formsemestre_page_title diff --git a/app/scodoc/html_sidebar.py b/app/scodoc/html_sidebar.py index 76b8253baf..7afdcf5cfc 100644 --- a/app/scodoc/html_sidebar.py +++ b/app/scodoc/html_sidebar.py @@ -53,7 +53,7 @@ def sidebar_common(context, REQUEST=None): 'ScoDoc', '' % params, - context.sidebar_dept(REQUEST), + sidebar_dept(context, REQUEST), """

    Scolarité

    Semestres
    Programmes
    diff --git a/app/scodoc/mails.py b/app/scodoc/mails.py new file mode 100644 index 0000000000..6a1944b7cc --- /dev/null +++ b/app/scodoc/mails.py @@ -0,0 +1,141 @@ +# -*- 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 +# +############################################################################## + +"""Gestion des emails +""" + +# XXX WIP: à ré-écrire pour ScoDoc 8 (étaient des méthodes de ZScoDoc) +import os +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 + +import sco_utils as scu +from notes_log import log + + +def sendEmail(context, msg): # TODO A REECRIRE ScoDoc8 + """Send an email to the address using the mailhost, if there is one.""" + raise NotImplementedError() + try: + mail_host = context.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(context, 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") + + +def send_debug_alert(context, 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 = _report_request(context, 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)) + sendEmailFromException(context, msg) + log("Sent mail alert:\n" + txt) + + +def _report_request(context, 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(context.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 +""" + % params + ) + if fmt == "html": + txt = txt.replace("\n", "
    ") + return txt \ No newline at end of file diff --git a/app/scodoc/notes_log.py b/app/scodoc/notes_log.py index f0b386a74d..9d1ba7c2b4 100644 --- a/app/scodoc/notes_log.py +++ b/app/scodoc/notes_log.py @@ -21,6 +21,8 @@ from email.MIMEBase import MIMEBase # pylint: disable=no-name-in-module,import- 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 +import mails + # Simple & stupid file logguer, used only to debug # (logging to SQL is done in scolog) @@ -120,7 +122,7 @@ def sendAlarm(context, subj, txt): msg.epilogue = "" txt = MIMEText(txt, "plain", sco_utils.SCO_ENCODING) msg.attach(txt) - context.sendEmail(msg) + mails.sendEmail(context, msg) # Debug: log call stack diff --git a/app/scodoc/notes_table.py b/app/scodoc/notes_table.py index 04be302cd6..e72765583c 100644 --- a/app/scodoc/notes_table.py +++ b/app/scodoc/notes_table.py @@ -309,7 +309,7 @@ class NotesTable: # fallback *** should not occur *** # txt = '\nkey missing in cmprows !!!\nx=%s\ny=%s\n' % (str(x),str(y)) # txt += '\nrangalpha=%s' % str(rangalpha) + '\n\nT=%s' % str(T) - # context.send_debug_alert(txt, REQUEST=None) + # send_debug_alert(txt, REQUEST=None) # return cmp(x,y) T.sort(cmprows) diff --git a/app/scodoc/pe_view.py b/app/scodoc/pe_view.py index 39ee975875..ad59460be9 100644 --- a/app/scodoc/pe_view.py +++ b/app/scodoc/pe_view.py @@ -54,7 +54,7 @@ import pe_avislatex def _pe_view_sem_recap_form(context, formsemestre_id, REQUEST=None): H = [ - context.sco_header(REQUEST, page_title="Avis de poursuite d'études"), + html_sco_header.sco_header(context, REQUEST, page_title="Avis de poursuite d'études"), """

    Génération des avis de poursuites d'études

    Cette fonction génère un ensemble de fichiers permettant d'éditer des avis de poursuites d'études. @@ -77,7 +77,7 @@ def _pe_view_sem_recap_form(context, formsemestre_id, REQUEST=None): formsemestre_id=formsemestre_id ), ] - return "\n".join(H) + context.sco_footer(REQUEST) + return "\n".join(H) + html_sco_header.sco_footer(context, REQUEST) def pe_view_sem_recap( diff --git a/app/scodoc/sco_abs_notification.py b/app/scodoc/sco_abs_notification.py index b02f46b1f8..a163dc974f 100644 --- a/app/scodoc/sco_abs_notification.py +++ b/app/scodoc/sco_abs_notification.py @@ -37,7 +37,7 @@ from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.header import Header - +import mails import notesdb as ndb import sco_utils as scu from notes_log import log @@ -114,7 +114,7 @@ def abs_notify_send( for email in destinations: del msg["To"] msg["To"] = email - context.sendEmail(msg) + mails.sendEmail(context, msg) ndb.SimpleQuery( context, """insert into absences_notifications (etudid, email, nbabs, nbabsjust, formsemestre_id) values (%(etudid)s, %(email)s, %(nbabs)s, %(nbabsjust)s, %(formsemestre_id)s)""", diff --git a/app/scodoc/sco_abs_views.py b/app/scodoc/sco_abs_views.py index 24916c4598..95b1677d91 100644 --- a/app/scodoc/sco_abs_views.py +++ b/app/scodoc/sco_abs_views.py @@ -116,7 +116,7 @@ def doSignaleAbsence( if modimpl["moduleimpl_id"] == moduleimpl_id: M = "dans le module %s" % modimpl["module"]["code"] H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Signalement d'une absence pour %(nomprenom)s" % etud ), """

    Signalement d'absences

    """, @@ -140,7 +140,7 @@ def doSignaleAbsence( % etud ) H.append(sco_find_etud.form_search_etud(context, REQUEST)) - H.append(context.sco_footer(REQUEST)) + H.append(html_sco_header.sco_footer(context, REQUEST)) return "\n".join(H) @@ -203,7 +203,7 @@ def SignaleAbsenceEtud(context, REQUEST=None): # etudid implied menu_module += """

    """ H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Signalement d'une absence pour %(nomprenom)s" % etud ), """
  5. AttributTypeDescription
    %s%s%s%s
    @@ -258,7 +258,7 @@ Raison: (optionnel) "menu_module": menu_module, "disabled": "disabled" if disabled else "", }, - context.sco_footer(REQUEST), + html_sco_header.sco_footer(context, REQUEST), ] return "\n".join(H) @@ -316,7 +316,7 @@ def doJustifAbsence( nbadded += 1 # H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Justification d'une absence pour %(nomprenom)s" % etud ), """

    Justification d'absences

    """, @@ -342,7 +342,7 @@ def doJustifAbsence( % etud ) H.append(sco_find_etud.form_search_etud(context, REQUEST)) - H.append(context.sco_footer(REQUEST)) + H.append(html_sco_header.sco_footer(context, REQUEST)) return "\n".join(H) @@ -352,7 +352,7 @@ def JustifAbsenceEtud(context, REQUEST=None): # etudid implied etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] etudid = etud["etudid"] H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Justification d'une absence pour %(nomprenom)s" % etud ), """
    @@ -396,7 +396,7 @@ Raison: (optionnel) """ % etud, - context.sco_footer(REQUEST), + html_sco_header.sco_footer(context, REQUEST), ] return "\n".join(H) @@ -421,7 +421,7 @@ def doAnnuleAbsence( nbadded += 1 # H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Annulation d'une absence pour %(nomprenom)s" % etud ), """

    Annulation d'absences pour %(nomprenom)s

    """ % etud, @@ -447,7 +447,7 @@ autre absence pour %(nomprenom)s % etud ) H.append(sco_find_etud.form_search_etud(context, REQUEST)) - H.append(context.sco_footer(REQUEST)) + H.append(html_sco_header.sco_footer(context, REQUEST)) return "\n".join(H) @@ -458,7 +458,7 @@ def AnnuleAbsenceEtud(context, REQUEST=None): # etudid implied etudid = etud["etudid"] H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Annulation d'une absence pour %(nomprenom)s" % etud ), """
    @@ -532,7 +532,7 @@ def AnnuleAbsenceEtud(context, REQUEST=None): # etudid implied
    """ % etud, - context.sco_footer(REQUEST), + html_sco_header.sco_footer(context, REQUEST), ] return "\n".join(H) @@ -557,7 +557,7 @@ def doAnnuleJustif( nbadded += 1 # H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Annulation d'une justification pour %(nomprenom)s" % etud, ), @@ -584,7 +584,7 @@ autre absence pour %(nomprenom)s % etud ) H.append(sco_find_etud.form_search_etud(context, REQUEST)) - H.append(context.sco_footer(REQUEST)) + H.append(html_sco_header.sco_footer(context, REQUEST)) return "\n".join(H) @@ -592,7 +592,7 @@ def EtatAbsences(context, REQUEST=None): """Etat des absences: choix du groupe""" # crude portage from 1999 DTML H = [ - context.sco_header(REQUEST, page_title="Etat des absences"), + html_sco_header.sco_header(context, REQUEST, page_title="Etat des absences"), """

    Etat des absences pour un groupe

    """, formChoixSemestreGroupe(context), @@ -609,7 +609,7 @@ def EtatAbsences(context, REQUEST=None):
    """ % (scu.AnneeScolaire(REQUEST), datetime.datetime.now().strftime("%d/%m/%Y")), - context.sco_footer(REQUEST), + html_sco_header.sco_footer(context, REQUEST), ] return "\n".join(H) @@ -672,7 +672,7 @@ def CalAbs(context, REQUEST=None): # etud implied # H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Calendrier des absences de %(nomprenom)s" % etud, cssstyles=["css/calabs.css"], @@ -712,7 +712,7 @@ def CalAbs(context, REQUEST=None): # etud implied H.append("selected") H.append(""">%s""" % y) H.append("""""") - H.append(context.sco_footer(REQUEST)) + H.append(html_sco_header.sco_footer(context, REQUEST)) return "\n".join(H) @@ -777,7 +777,7 @@ def ListeAbsEtud( # Mise en forme HTML: H = [] H.append( - context.sco_header(REQUEST, page_title="Absences de %s" % etud["nomprenom"]) + html_sco_header.sco_header(context, REQUEST, page_title="Absences de %s" % etud["nomprenom"]) ) H.append( """

    Absences de %s (à partir du %s)

    """ @@ -795,7 +795,7 @@ def ListeAbsEtud( H.append(tab_absjust.html()) else: H.append("""

    Pas d'absences justifiées

    """) - return "\n".join(H) + context.sco_footer(REQUEST) + return "\n".join(H) + html_sco_header.sco_footer(context, REQUEST) elif format == "text": T = [] @@ -828,7 +828,7 @@ def absences_index_html(context, REQUEST=None): authuser = REQUEST.AUTHENTICATED_USER H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Gestion des absences", cssstyles=["css/calabs.css"], @@ -877,7 +877,7 @@ saisir les absences de toute cette semaine.

    """

    Vous n'avez pas l'autorisation d'ajouter, justifier ou supprimer des absences.

    """ ) - H.append(context.sco_footer(REQUEST)) + H.append(html_sco_header.sco_footer(context, REQUEST)) return "\n".join(H) diff --git a/app/scodoc/sco_apogee_compare.py b/app/scodoc/sco_apogee_compare.py index 259254201e..f2f76c5406 100644 --- a/app/scodoc/sco_apogee_compare.py +++ b/app/scodoc/sco_apogee_compare.py @@ -65,7 +65,7 @@ _help_txt = """ def apo_compare_csv_form(context, REQUEST=None): """Form: submit 2 CSV files to compare them.""" H = [ - context.sco_header(REQUEST, page_title="Comparaison de fichiers Apogée"), + html_sco_header.sco_header(context, REQUEST, page_title="Comparaison de fichiers Apogée"), """

    Comparaison de fichiers Apogée

    """, @@ -84,7 +84,7 @@ def apo_compare_csv_form(context, REQUEST=None):
    """, - context.sco_footer(REQUEST), + html_sco_header.sco_footer(context, REQUEST), ] return "\n".join(H) @@ -95,14 +95,14 @@ def apo_compare_csv(context, A_file, B_file, autodetect=True, REQUEST=None): B = _load_apo_data(B_file, autodetect=autodetect) H = [ - context.sco_header(REQUEST, page_title="Comparaison de fichiers Apogée"), + html_sco_header.sco_header(context, REQUEST, page_title="Comparaison de fichiers Apogée"), "

    Comparaison de fichiers Apogée

    ", _help_txt, '
    ', _apo_compare_csv(context, A, B, REQUEST=None), "
    ", """

    Autre comparaison

    """, - context.sco_footer(REQUEST), + html_sco_header.sco_footer(context, REQUEST), ] return "\n".join(H) diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py index 28fdae366c..c9f1953b8a 100644 --- a/app/scodoc/sco_archives.py +++ b/app/scodoc/sco_archives.py @@ -299,7 +299,7 @@ def do_formsemestre_archive( if data: data = "\n".join( [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Moyennes archivées le %s" % date, head_message="Moyennes archivées le %s" % date, @@ -308,7 +308,7 @@ def do_formsemestre_archive( '

    Valeurs archivées le %s

    ' % date, '', data, - context.sco_footer(REQUEST), + html_sco_header.sco_footer(context, REQUEST), ] ) PVArchive.store(archive_id, "Tableau_moyennes.html", data) @@ -381,7 +381,7 @@ def formsemestre_archive(context, REQUEST, formsemestre_id, group_ids=[]): ) H = [ - context.html_sem_header( + html_sco_header.html_sem_header(context, REQUEST, "Archiver les PV et résultats du semestre", sem=sem, @@ -400,7 +400,7 @@ enregistrés et non modifiables, on peut les retrouver ultérieurement. F = [ """

    Note: les documents sont aussi affectés par les réglages sur la page "Paramétrage" (accessible à l'administrateur du département).

    """, - context.sco_footer(REQUEST), + html_sco_header.sco_footer(context, REQUEST), ] descr = [ @@ -503,7 +503,7 @@ def formsemestre_list_archives(context, REQUEST, formsemestre_id): L.append(a) sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) - H = [context.html_sem_header(REQUEST, "Archive des PV et résultats ", sem)] + H = [html_sco_header.html_sem_header(context, REQUEST, "Archive des PV et résultats ", sem)] if not L: H.append("

    aucune archive enregistrée

    ") else: @@ -529,7 +529,7 @@ def formsemestre_list_archives(context, REQUEST, formsemestre_id): H.append("") H.append("") - return "\n".join(H) + context.sco_footer(REQUEST) + return "\n".join(H) + html_sco_header.sco_footer(context, REQUEST) def formsemestre_get_archived_file( diff --git a/app/scodoc/sco_archives_etud.py b/app/scodoc/sco_archives_etud.py index 4e135437fc..6171534896 100644 --- a/app/scodoc/sco_archives_etud.py +++ b/app/scodoc/sco_archives_etud.py @@ -131,7 +131,7 @@ def etud_upload_file_form(context, REQUEST, etudid): ) etud = context.getEtudInfo(filled=1, REQUEST=REQUEST)[0] H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Chargement d'un document associé à %(nomprenom)s" % etud, ), @@ -162,7 +162,7 @@ def etud_upload_file_form(context, REQUEST, etudid): cancelbutton="Annuler", ) if tf[0] == 0: - return "\n".join(H) + tf[1] + context.sco_footer(REQUEST) + return "\n".join(H) + tf[1] + html_sco_header.sco_footer(context, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect( context.NotesURL() + "/ficheEtud?etudid=" + etudid @@ -252,7 +252,7 @@ def etudarchive_generate_excel_sample(context, group_id=None, REQUEST=None): def etudarchive_import_files_form(context, group_id, REQUEST=None): """Formualaire pour importation fichiers d'un groupe""" H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, page_title="Import de fichiers associés aux étudiants" ), """

    Téléchargement de fichier associés aux étudiants

    @@ -276,7 +276,7 @@ def etudarchive_import_files_form(context, group_id, REQUEST=None): """ % group_id, ] - F = context.sco_footer(REQUEST) + F = html_sco_header.sco_footer(context, REQUEST) tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, @@ -329,4 +329,4 @@ def etudarchive_import_files( r = sco_trombino.zip_excel_import_files( context, xlsfile, zipfile, REQUEST, callback, filename_title, page_title ) - return r + context.sco_footer(REQUEST) + return r + html_sco_header.sco_footer(context, REQUEST) diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index 90c451cfac..e41d0cbc00 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -41,6 +41,7 @@ from email.header import Header from reportlab.lib.colors import Color +import mails import sco_utils as scu import notesdb as ndb from notes_log import log @@ -831,7 +832,7 @@ def formsemestre_bulletinetud( R.append('
    ') # --- Pied de page - R.append(context.sco_footer(REQUEST)) + R.append(html_sco_header.sco_footer(context, REQUEST)) return "".join(R) @@ -1010,7 +1011,7 @@ def mail_bulletin(context, formsemestre_id, I, pdfdata, filename, recipient_addr email.encoders.encode_base64(att) msg.attach(att) log("mail bulletin a %s" % msg["To"]) - context.sendEmail(msg) + mails.sendEmail(context, msg) def _formsemestre_bulletinetud_header_html( @@ -1026,7 +1027,7 @@ def _formsemestre_bulletinetud_header_html( authuser = REQUEST.AUTHENTICATED_USER uid = str(authuser) H = [ - context.sco_header( + html_sco_header.sco_header(context, page_title="Bulletin de %(nomprenom)s" % etud, REQUEST=REQUEST, javascripts=[ @@ -1196,7 +1197,7 @@ def formsemestre_bulletins_choice( """Choix d'une version de bulletin""" sem = sco_formsemestre.get_formsemestre(context, formsemestre_id) H = [ - context.html_sem_header(REQUEST, title, sem), + html_sco_header.html_sem_header(context, REQUEST, title, sem), """
    @@ -1219,7 +1220,7 @@ def formsemestre_bulletins_choice( H.append("""

    """ + explanation + """

    """) - return "\n".join(H) + context.sco_footer(REQUEST) + return "\n".join(H) + html_sco_header.sco_footer(context, REQUEST) expl_bull = """Versions des bulletins:
    • courte: moyennes des modules
    • intermédiaire: moyennes des modules et notes des évaluations sélectionnées
    • complète: toutes les notes
      • """ diff --git a/app/scodoc/sco_core.py b/app/scodoc/sco_core.py old mode 100755 new mode 100644 index 2437cc0a4c..963a3c1e46 --- a/app/scodoc/sco_core.py +++ b/app/scodoc/sco_core.py @@ -16,8 +16,8 @@ def sco_get_version(context, REQUEST=None): def test_refactor(context, x=1): x = context.toto() - y = ("context=" + context.module_is_locked("alpha")) + "23" - z = context.sco_header( + y = ("context=" + sco_edit_module.module_is_locked(context, "alpha")) + "23" + z = html_sco_header.sco_header(context, a_long_argument_hahahahaha=1, another_very_long_arggggggggggggg=2, z=6, diff --git a/app/scodoc/sco_debouche.py b/app/scodoc/sco_debouche.py index 8641d68dc0..a02e208d99 100644 --- a/app/scodoc/sco_debouche.py +++ b/app/scodoc/sco_debouche.py @@ -191,11 +191,11 @@ def table_debouche_etudids(context, etudids, keep_numeric=True): def report_debouche_ask_date(context, REQUEST=None): """Formulaire demande date départ""" return ( - context.sco_header(REQUEST) + html_sco_header.sco_header(context, REQUEST) + """ Date de départ de la recherche: """ - + context.sco_footer(REQUEST) + + html_sco_header.sco_footer(context, REQUEST) ) diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py index 66a6fa79ff..1b9341a2dc 100644 --- a/app/scodoc/sco_dept.py +++ b/app/scodoc/sco_dept.py @@ -171,7 +171,7 @@ Chercher étape courante: Suppression de la formation %(titre)s (%(acronyme)s)""" % F, ] @@ -86,7 +86,7 @@ def formation_delete(context, formation_id=None, dialog_confirmed=False, REQUEST % context.NotesURL() ) - H.append(context.sco_footer(REQUEST)) + H.append(html_sco_header.sco_footer(context, REQUEST)) return "\n".join(H) @@ -99,7 +99,7 @@ def formation_edit(context, formation_id=None, create=False, REQUEST=None): """Edit or create a formation""" if create: H = [ - context.sco_header(REQUEST, page_title="Création d'une formation"), + html_sco_header.sco_header(context, REQUEST, page_title="Création d'une formation"), """

        Création d'une formation

        Une "formation" décrit une filière, comme un DUT ou une Licence. La formation se subdivise en unités pédagogiques (UE, matières, modules). Elle peut se diviser en plusieurs semestres (ou sessions), qui seront mis en place séparément. @@ -121,7 +121,7 @@ def formation_edit(context, formation_id=None, create=False, REQUEST=None): is_locked = context.formation_has_locked_sems(formation_id) submitlabel = "Modifier les valeurs" H = [ - context.sco_header(REQUEST, page_title="Modification d'une formation"), + html_sco_header.sco_header(context, REQUEST, page_title="Modification d'une formation"), """

        Modification de la formation %(acronyme)s

        """ % initvalues, ] if is_locked: @@ -191,7 +191,7 @@ def formation_edit(context, formation_id=None, create=False, REQUEST=None): submitlabel=submitlabel, ) if tf[0] == 0: - return "\n".join(H) + tf[1] + context.sco_footer(REQUEST) + return "\n".join(H) + tf[1] + html_sco_header.sco_footer(context, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect(context.NotesURL()) else: @@ -214,7 +214,7 @@ def formation_edit(context, formation_id=None, create=False, REQUEST=None): "Valeurs incorrectes: il existe déjà une formation avec même titre, acronyme et version." ) + tf[1] - + context.sco_footer(REQUEST) + + html_sco_header.sco_footer(context, REQUEST) ) # if create: diff --git a/app/scodoc/sco_edit_matiere.py b/app/scodoc/sco_edit_matiere.py index 98a86dfee5..1c2f1ae4ab 100644 --- a/app/scodoc/sco_edit_matiere.py +++ b/app/scodoc/sco_edit_matiere.py @@ -40,7 +40,7 @@ def matiere_create(context, ue_id=None, REQUEST=None): """Creation d'une matiere""" UE = context.do_ue_list(args={"ue_id": ue_id})[0] H = [ - context.sco_header(REQUEST, page_title="Création d'une matière"), + html_sco_header.sco_header(context, REQUEST, page_title="Création d'une matière"), """

        Création d'une matière dans l'UE %(titre)s (%(acronyme)s)

        """ % UE, """

        Les matières sont des groupes de modules dans une UE d'une formation donnée. Les matières servent surtout pour la @@ -78,7 +78,7 @@ associé. dest_url = context.NotesURL() + "/ue_list?formation_id=" + UE["formation_id"] if tf[0] == 0: - return "\n".join(H) + tf[1] + context.sco_footer(REQUEST) + return "\n".join(H) + tf[1] + html_sco_header.sco_footer(context, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect(dest_url) else: @@ -89,7 +89,7 @@ associé. "\n".join(H) + tf_error_message("Titre de matière déjà existant dans cette UE") + tf[1] - + context.sco_footer(REQUEST) + + html_sco_header.sco_footer(context, REQUEST) ) _ = context.do_matiere_create(tf[2], REQUEST) return REQUEST.RESPONSE.redirect(dest_url) @@ -100,7 +100,7 @@ def matiere_delete(context, matiere_id=None, REQUEST=None): M = context.do_matiere_list(args={"matiere_id": matiere_id})[0] UE = context.do_ue_list(args={"ue_id": M["ue_id"]})[0] H = [ - context.sco_header(REQUEST, page_title="Suppression d'une matière"), + html_sco_header.sco_header(context, REQUEST, page_title="Suppression d'une matière"), "

        Suppression de la matière %(titre)s" % M, " dans l'UE (%(acronyme)s))

        " % UE, ] @@ -114,7 +114,7 @@ def matiere_delete(context, matiere_id=None, REQUEST=None): cancelbutton="Annuler", ) if tf[0] == 0: - return "\n".join(H) + tf[1] + context.sco_footer(REQUEST) + return "\n".join(H) + tf[1] + html_sco_header.sco_footer(context, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect(dest_url) else: @@ -138,7 +138,7 @@ def matiere_edit(context, matiere_id=None, REQUEST=None): ue_names = ["%(acronyme)s (%(titre)s)" % u for u in ues] ue_ids = [u["ue_id"] for u in ues] H = [ - context.sco_header(REQUEST, page_title="Modification d'une matière"), + html_sco_header.sco_header(context, REQUEST, page_title="Modification d'une matière"), """

        Modification de la matière %(titre)s""" % F, """(formation %(acronyme)s, version %(version)s)

        """ % Fo, ] @@ -182,7 +182,7 @@ associé. dest_url = context.NotesURL() + "/ue_list?formation_id=" + U["formation_id"] if tf[0] == 0: - return "\n".join(H) + tf[1] + help + context.sco_footer(REQUEST) + return "\n".join(H) + tf[1] + help + html_sco_header.sco_footer(context, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect(dest_url) else: @@ -195,7 +195,7 @@ associé. "\n".join(H) + tf_error_message("Titre de matière déjà existant dans cette UE") + tf[1] - + context.sco_footer(REQUEST) + + html_sco_header.sco_footer(context, REQUEST) ) # changement d'UE ? diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index 4186721170..1d513977b9 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -66,7 +66,7 @@ def module_create(context, matiere_id=None, REQUEST=None): parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"]) semestres_indices = range(1, parcours.NB_SEM + 1) H = [ - context.sco_header(REQUEST, page_title="Création d'un module"), + html_sco_header.sco_header(context, REQUEST, page_title="Création d'un module"), """

        Création d'un module dans la matière %(titre)s""" % M, """ (UE %(acronyme)s)

        """ % UE, _MODULE_HELP, @@ -170,7 +170,7 @@ def module_create(context, matiere_id=None, REQUEST=None): submitlabel="Créer ce module", ) if tf[0] == 0: - return "\n".join(H) + tf[1] + context.sco_footer(REQUEST) + return "\n".join(H) + tf[1] + html_sco_header.sco_footer(context, REQUEST) else: context.do_module_create(tf[2], REQUEST) return REQUEST.RESPONSE.redirect( @@ -187,7 +187,9 @@ def module_delete(context, module_id=None, REQUEST=None): raise ScoValueError("Module inexistant !") Mod = Mods[0] H = [ - context.sco_header(REQUEST, page_title="Suppression d'un module"), + html_sco_header.sco_header( + context, REQUEST, page_title="Suppression d'un module" + ), """

        Suppression du module %(titre)s (%(code)s)

        """ % Mod, ] @@ -201,7 +203,7 @@ def module_delete(context, module_id=None, REQUEST=None): cancelbutton="Annuler", ) if tf[0] == 0: - return "\n".join(H) + tf[1] + context.sco_footer(REQUEST) + return "\n".join(H) + tf[1] + html_sco_header.sco_footer(context, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect(dest_url) else: @@ -226,7 +228,7 @@ def module_edit(context, module_id=None, REQUEST=None): if not Mod: raise ScoValueError("invalid module !") Mod = Mod[0] - unlocked = not context.module_is_locked(module_id) + unlocked = not module_is_locked(context, module_id) Fo = context.formation_list(args={"formation_id": Mod["formation_id"]})[0] parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"]) M = ndb.SimpleDictFetch( @@ -243,7 +245,8 @@ def module_edit(context, module_id=None, REQUEST=None): dest_url = context.NotesURL() + "/ue_list?formation_id=" + Mod["formation_id"] H = [ - context.sco_header( + html_sco_header.sco_header( + context, REQUEST, page_title="Modification du module %(titre)s" % Mod, cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css"], @@ -375,7 +378,7 @@ def module_edit(context, module_id=None, REQUEST=None): ) if tf[0] == 0: - return "\n".join(H) + tf[1] + context.sco_footer(REQUEST) + return "\n".join(H) + tf[1] + html_sco_header.sco_footer(context, REQUEST) elif tf[0] == -1: return REQUEST.RESPONSE.redirect(dest_url) else: @@ -412,7 +415,9 @@ def module_list(context, formation_id, REQUEST=None): raise ScoValueError("invalid formation !") F = context.formation_list(args={"formation_id": formation_id})[0] H = [ - context.sco_header(REQUEST, page_title="Liste des modules de %(titre)s" % F), + html_sco_header.sco_header( + context, REQUEST, page_title="Liste des modules de %(titre)s" % F + ), """

        Listes des modules dans la formation %(titre)s (%(acronyme)s)

        """ % F, '
          ', @@ -430,11 +435,29 @@ def module_list(context, formation_id, REQUEST=None): ) H.append("") H.append("
        ") - H.append(context.sco_footer(REQUEST)) + H.append(html_sco_header.sco_footer(context, REQUEST)) return "\n".join(H) -# +def module_is_locked(context, module_id): + """True if module should not be modified + (used in a locked formsemestre) + """ + r = ndb.SimpleDictFetch( + context, + """SELECT mi.* from notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi + WHERE mi.module_id = mod.module_id AND mi.formsemestre_id = sem.formsemestre_id + AND mi.module_id = %(module_id)s AND sem.etat = 0 + """, + {"module_id": module_id}, + ) + return len(r) > 0 + + +def module_count_moduleimpls(context, module_id): + "Number of moduleimpls using this module" + mods = sco_moduleimpl.do_moduleimpl_list(context, module_id=module_id) + return len(mods) def formation_add_malus_modules(context, formation_id, titre=None, REQUEST=None): diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 4762116aee..18cf73498a 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -73,7 +73,7 @@ def ue_edit(context, ue_id=None, create=False, formation_id=None, REQUEST=None): parcours = sco_codes_parcours.get_parcours_from_code(Fo["type_parcours"]) H = [ - context.sco_header(REQUEST, page_title=title, javascripts=["js/edit_ue.js"]), + html_sco_header.sco_header(context, REQUEST, page_title=title, javascripts=["js/edit_ue.js"]), "

        " + title, " (formation %(acronyme)s, version %(version)s)

        " % Fo, """ @@ -186,7 +186,7 @@ def ue_edit(context, ue_id=None, create=False, formation_id=None, REQUEST=None): if tf[0] == 0: X = """
        """ - return "\n".join(H) + tf[1] + X + context.sco_footer(REQUEST) + return "\n".join(H) + tf[1] + X + html_sco_header.sco_footer(context, REQUEST) else: if create: if not tf[2]["ue_code"]: @@ -323,7 +323,7 @@ def ue_list(context, formation_id=None, msg="", REQUEST=None): "delete_small_dis_img", title="Suppression impossible (module utilisé)" ) H = [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css"], javascripts=[ @@ -466,7 +466,7 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module '%s' % sco_codes_parcours.UE_TYPE_NAME[UE["type"]] ) - ue_editable = editable and not context.ue_is_locked(UE["ue_id"]) + ue_editable = editable and not ue_is_locked(context, UE["ue_id"]) if ue_editable: H.append( 'modifier' % UE @@ -479,20 +479,20 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module for Mat in Matlist: if not parcours.UE_IS_MODULE: H.append('
      • ') - if editable and not context.matiere_is_locked(Mat["matiere_id"]): + if editable and not sco_edit_matiere.matiere_is_locked(context, Mat["matiere_id"]): H.append( '' % Mat ) H.append("%(titre)s" % Mat) - if editable and not context.matiere_is_locked(Mat["matiere_id"]): + if editable and not sco_edit_matiere.matiere_is_locked(context, Mat["matiere_id"]): H.append("") H.append('
          ') Modlist = context.do_module_list(args={"matiere_id": Mat["matiere_id"]}) im = 0 for Mod in Modlist: - Mod["nb_moduleimpls"] = context.module_count_moduleimpls( + Mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls(context, Mod["module_id"] ) klass = "notes_module_list" @@ -526,7 +526,7 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module H.append("") mod_editable = ( - editable # and not context.module_is_locked(Mod['module_id']) + editable # and not sco_edit_module.module_is_locked(context, Mod['module_id']) ) if mod_editable: H.append( @@ -669,7 +669,7 @@ Si vous souhaitez modifier cette formation (par exemple pour y ajouter un module warn, _ = sco_formsemestre_validation.check_formation_ues(context, formation_id) H.append(warn) - H.append(context.sco_footer(REQUEST)) + H.append(html_sco_header.sco_footer(context, REQUEST)) return "".join(H) @@ -732,7 +732,7 @@ def do_ue_edit(context, args, bypass_lock=False, dont_invalidate_cache=False): # check ue_id = args["ue_id"] ue = context.do_ue_list({"ue_id": ue_id})[0] - if (not bypass_lock) and context.ue_is_locked(ue["ue_id"]): + if (not bypass_lock) and ue_is_locked(context, ue["ue_id"]): raise ScoLockedFormError() # check: acronyme unique dans cette formation if args.has_key("acronyme"): @@ -782,6 +782,22 @@ def edit_ue_set_code_apogee(context, id=None, value=None, REQUEST=None): return value +def ue_is_locked(context, ue_id): + """True if UE should not be modified + (contains modules used in a locked formsemestre) + """ + r = ndb.SimpleDictFetch( + context, + """SELECT ue.* FROM notes_ue ue, notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi + WHERE ue.ue_id = mod.ue_id + AND mi.module_id = mod.module_id AND mi.formsemestre_id = sem.formsemestre_id + AND ue.ue_id = %(ue_id)s AND sem.etat = 0 + """, + {"ue_id": ue_id}, + ) + return len(r) > 0 + + # ---- Table recap formation def formation_table_recap(context, formation_id, format="html", REQUEST=None): """Table recapitulant formation.""" @@ -796,7 +812,7 @@ def formation_table_recap(context, formation_id, format="html", REQUEST=None): for Mat in Matlist: Modlist = context.do_module_list(args={"matiere_id": Mat["matiere_id"]}) for Mod in Modlist: - Mod["nb_moduleimpls"] = context.module_count_moduleimpls( + Mod["nb_moduleimpls"] = sco_edit_module.module_count_moduleimpls(context, Mod["module_id"] ) # diff --git a/app/scodoc/sco_edt_cal.py b/app/scodoc/sco_edt_cal.py index bb01b88ecb..6f560ab205 100644 --- a/app/scodoc/sco_edt_cal.py +++ b/app/scodoc/sco_edt_cal.py @@ -163,7 +163,7 @@ def experimental_calendar(context, group_id=None, formsemestre_id=None, REQUEST= """experimental page""" return "\n".join( [ - context.sco_header( + html_sco_header.sco_header(context, REQUEST, javascripts=[ "libjs/purl.js", @@ -195,7 +195,7 @@ def experimental_calendar(context, group_id=None, formsemestre_id=None, REQUEST= """
          loading...
          """, - context.sco_footer(REQUEST), + html_sco_header.sco_footer(context, REQUEST), """