# -*- 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 # ############################################################################## """Gestions des "nouvelles" """ import PyRSS2Gen # pylint: disable=import-error from cStringIO import StringIO import datetime, re import time from stripogram import html2text, html2safehtml 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.Header import Header # pylint: disable=no-name-in-module,import-error from email import Encoders # pylint: disable=no-name-in-module,import-error import notesdb as ndb # pylint: disable=unused-wildcard-import from notes_log import log import scolars from sco_utils import SCO_ENCODING, SCO_ANNONCES_WEBSITE import sco_formsemestre import sco_moduleimpl _scolar_news_editor = ndb.EditableTable( "scolar_news", "news_id", ("date", "authenticated_user", "type", "object", "text", "url"), 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 = 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(context, REQUEST, 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. """ authuser_name = str(REQUEST.AUTHENTICATED_USER) cnx = context.GetDBConnexion() args = { "authenticated_user": authuser_name, "user_info": context.Users.user_info(user_name=authuser_name), "type": typ, "object": object, "text": text, "url": url, } log("news: %s" % args) 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 _LAST_NEWS[(authuser_name, typ, object)] = t _send_news_by_mail(context, args) return scolar_news_create(cnx, args, has_uniq_values=False) def scolar_news_summary(context, n=5): """Return last n news. News are "compressed", ie redondant events are joined. """ cnx = context.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor.execute("select * from scolar_news order by date desc limit 100") 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 = selected_news.values() # sort by date, descending news.sort(lambda x, y: cmp(y["date"], x["date"])) 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") n["rssdate"] = n["date"].strftime("%d/%m %Hh%M") # pour affichage for k in n.keys(): if n[k] is None: n[k] = "" if _scolar_news_editor.output_formators.has_key(k): n[k] = _scolar_news_editor.output_formators[k](n[k]) # date resumee j, m = n["date"].split("/")[:2] mois = scolars.abbrvmonthsnames[int(m) - 1] n["formatted_date"] = "%s %s %s" % (j, mois, n["hm"]) # indication semestre si ajout notes: infos = _get_formsemestre_infos_from_news(context, n) if infos: n["text"] += ( ' (%(descr_sem)s)' % infos ) n["text"] += ( " par " + context.Users.user_info(user_name=n["authenticated_user"])["nomcomplet"] ) return news def _get_formsemestre_infos_from_news(context, 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"] mods = sco_moduleimpl.do_moduleimpl_list(context, moduleimpl_id=moduleimpl_id) if not mods: return {} # module does not exists anymore mod = mods[0] formsemestre_id = mod["formsemestre_id"] if not formsemestre_id: return {} try: sem = sco_formsemestre.get_formsemestre(context, 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(context, n=5, rssicon=None): """News summary, formated in HTML""" news = scolar_news_summary(context, n=n) if not news: return "" H = ['
Dernières opérations'] if rssicon: # 2020-12-30 plus utilisé H.append('' + rssicon + "") H.append('") # Informations générales H.append( """
Pour être informé des évolutions de ScoDoc, vous pouvez vous abonner à la liste de diffusion.
""" % SCO_ANNONCES_WEBSITE ) H.append("
") return "\n".join(H) def scolar_news_summary_rss(context, title, sco_url, n=5): """rss feed for scolar news""" news = scolar_news_summary(context, n=n) items = [] for n in news: text = html2text(n["text"]) items.append( PyRSS2Gen.RSSItem( title=unicode("%s %s" % (n["rssdate"], text), SCO_ENCODING), link=sco_url + "/" + n["url"], pubDate=n["date822"], ) ) rss = PyRSS2Gen.RSS2( title=unicode(title, SCO_ENCODING), link=sco_url, description=unicode(title, SCO_ENCODING), lastBuildDate=datetime.datetime.now(), items=items, ) f = StringIO() rss.write_xml(f, encoding=SCO_ENCODING) f.seek(0) data = f.read() f.close() return data def _send_news_by_mail(context, n): """Notify by email""" infos = _get_formsemestre_infos_from_news(context, n) formsemestre_id = infos.get("formsemestre_id", None) prefs = context.get_preferences(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 += ( """%(descr_sem)s """ % 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 = context.ScoURL() txt = re.sub('href=.*?"', 'href="' + base + "/", txt) # Transforme les liens HTML en texte brut: 'texte' devient 'texte: url' # (si on veut des messages non html) txt = re.sub(r'(.*?)', r"\2: \1", txt) msg = MIMEMultipart() msg["Subject"] = Header("[ScoDoc] " + NEWS_MAP.get(n["type"], "?"), SCO_ENCODING) msg["From"] = prefs["email_from_addr"] txt = MIMEText(txt, "plain", SCO_ENCODING) msg.attach(txt) for email_addr in destinations: if email_addr: del msg["To"] msg["To"] = email_addr # log('xxx mail: %s' % msg) context.sendEmail(msg)