# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2022 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 "nouvelles" """ import re import time from operator import itemgetter from flask import g from flask_login import current_user import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app import log from app.scodoc import sco_formsemestre from app.scodoc import sco_moduleimpl from app.scodoc import sco_preferences from app import email _scolar_news_editor = ndb.EditableTable( "scolar_news", "news_id", ("date", "authenticated_user", "type", "object", "text", "url"), filter_dept=True, sortkey="date desc", output_formators={"date": ndb.DateISOtoDMY}, input_formators={"date": ndb.DateDMYtoISO}, html_quote=False, # no user supplied data, needed to store html links ) NEWS_INSCR = "INSCR" # inscription d'étudiants (object=None ou formsemestre_id) NEWS_NOTE = "NOTES" # saisie note (object=moduleimpl_id) NEWS_FORM = "FORM" # modification formation (object=formation_id) NEWS_SEM = "SEM" # creation semestre (object=None) NEWS_MISC = "MISC" # unused NEWS_MAP = { NEWS_INSCR: "inscription d'étudiants", NEWS_NOTE: "saisie note", NEWS_FORM: "modification formation", NEWS_SEM: "création semestre", NEWS_MISC: "opération", # unused } NEWS_TYPES = list(NEWS_MAP.keys()) scolar_news_create = _scolar_news_editor.create scolar_news_list = _scolar_news_editor.list _LAST_NEWS = {} # { (authuser_name, type, object) : time } def add(typ, object=None, text="", url=None, max_frequency=False): """Ajoute une nouvelle. Si max_frequency, ne genere pas 2 nouvelles identiques à moins de max_frequency secondes d'intervalle. """ from app.scodoc import sco_users authuser_name = current_user.user_name cnx = ndb.GetDBConnexion() args = { "authenticated_user": authuser_name, "user_info": sco_users.user_info(authuser_name), "type": typ, "object": object, "text": text, "url": url, } t = time.time() if max_frequency: last_news_time = _LAST_NEWS.get((authuser_name, typ, object), False) if last_news_time and (t - last_news_time < max_frequency): # log("not recording") return log("news: %s" % args) _LAST_NEWS[(authuser_name, typ, object)] = t _send_news_by_mail(args) return scolar_news_create(cnx, args) def scolar_news_summary(n=5): """Return last n news. News are "compressed", ie redondant events are joined. """ from app.scodoc import sco_etud from app.scodoc import sco_users cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor.execute( """SELECT id AS news_id, * FROM scolar_news WHERE dept_id=%(dept_id)s ORDER BY date DESC LIMIT 100 """, {"dept_id": g.scodoc_dept_id}, ) selected_news = {} # (type,object) : news dict news = cursor.dictfetchall() # la plus récente d'abord for r in reversed(news): # la plus ancienne d'abord # si on a deja une news avec meme (type,object) # et du meme jour, on la remplace dmy = ndb.DateISOtoDMY(r["date"]) # round key = (r["type"], r["object"], dmy) selected_news[key] = r news = list(selected_news.values()) # sort by date, descending news.sort(key=itemgetter("date"), reverse=True) news = news[:n] # mimic EditableTable.list output formatting: for n in news: n["date822"] = n["date"].strftime("%a, %d %b %Y %H:%M:%S %z") # heure n["hm"] = n["date"].strftime("%Hh%M") for k in n.keys(): if n[k] is None: n[k] = "" if k in _scolar_news_editor.output_formators: n[k] = _scolar_news_editor.output_formators[k](n[k]) # date resumee j, m = n["date"].split("/")[:2] mois = scu.MONTH_NAMES_ABBREV[int(m) - 1] n["formatted_date"] = "%s %s %s" % (j, mois, n["hm"]) # indication semestre si ajout notes: infos = _get_formsemestre_infos_from_news(n) if infos: n["text"] += ( ' (<a href="Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(descr_sem)s</a>)' % infos ) n["text"] += ( " par " + sco_users.user_info(n["authenticated_user"])["nomcomplet"] ) return news def _get_formsemestre_infos_from_news(n): """Informations sur le semestre concerné par la nouvelle n {} si inexistant """ formsemestre_id = None if n["type"] == NEWS_INSCR: formsemestre_id = n["object"] elif n["type"] == NEWS_NOTE: moduleimpl_id = n["object"] if n["object"]: mods = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id) if not mods: return {} # module does not exists anymore return {} # pas d'indication du module mod = mods[0] formsemestre_id = mod["formsemestre_id"] if not formsemestre_id: return {} try: sem = sco_formsemestre.get_formsemestre(formsemestre_id) except: # semestre n'existe plus return {} if sem["semestre_id"] > 0: descr_sem = "S%d" % sem["semestre_id"] else: descr_sem = "" if sem["modalite"]: descr_sem += " " + sem["modalite"] return {"formsemestre_id": formsemestre_id, "sem": sem, "descr_sem": descr_sem} def scolar_news_summary_html(n=5): """News summary, formated in HTML""" news = scolar_news_summary(n=n) if not news: return "" H = ['<div class="news"><span class="newstitle">Dernières opérations'] H.append('</span><ul class="newslist">') for n in news: H.append( '<li class="newslist"><span class="newsdate">%(formatted_date)s</span><span class="newstext">%(text)s</span></li>' % n ) H.append("</ul>") # Informations générales H.append( """<div> Pour être informé des évolutions de ScoDoc, vous pouvez vous <a class="stdlink" href="%s"> abonner à la liste de diffusion</a>. </div> """ % scu.SCO_ANNONCES_WEBSITE ) H.append("</div>") return "\n".join(H) def _send_news_by_mail(n): """Notify by email""" infos = _get_formsemestre_infos_from_news(n) formsemestre_id = infos.get("formsemestre_id", None) prefs = sco_preferences.SemPreferences(formsemestre_id=formsemestre_id) destinations = prefs["emails_notifications"] or "" destinations = [x.strip() for x in destinations.split(",")] destinations = [x for x in destinations if x] if not destinations: return # txt = n["text"] if infos: txt += "\n\nSemestre %(titremois)s\n\n" % infos["sem"] txt += ( """<a href="Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(descr_sem)s</a> """ % infos ) txt += "\n\nEffectué par: %(nomcomplet)s\n" % n["user_info"] txt = ( "\n" + txt + """\n --- Ceci est un message de notification automatique issu de ScoDoc --- vous recevez ce message car votre adresse est indiquée dans les paramètres de ScoDoc. """ ) # Transforme les URL en URL absolue base = scu.ScoURL() txt = re.sub('href=.*?"', 'href="' + base + "/", txt) # Transforme les liens HTML en texte brut: '<a href="url">texte</a>' devient 'texte: url' # (si on veut des messages non html) txt = re.sub(r'<a.*?href\s*=\s*"(.*?)".*?>(.*?)</a>', r"\2: \1", txt) subject = "[ScoDoc] " + NEWS_MAP.get(n["type"], "?") sender = prefs["email_from_addr"] email.send_email(subject, sender, destinations, txt)