From 67369a8b3c3516b9298697f0fe26a5f9bc8043cf Mon Sep 17 00:00:00 2001 From: Daniel STAN Date: Mon, 20 Oct 2014 00:09:34 +0200 Subject: [PATCH] fin_connexion: regarde les finAdhesion + calendar --- gestion/mail/fin_connexion.py | 140 +++++++++++++++++++++++++++------- 1 file changed, 114 insertions(+), 26 deletions(-) diff --git a/gestion/mail/fin_connexion.py b/gestion/mail/fin_connexion.py index 38e8fd78..90c54126 100755 --- a/gestion/mail/fin_connexion.py +++ b/gestion/mail/fin_connexion.py @@ -1,19 +1,29 @@ #!/bin/bash /usr/scripts/python.sh # -*- coding: utf-8 -*- -# Draf de code pour alerter les adhérents de leur fin de connexion imminente +# Draft de code pour alerter les adhérents de leur fin de connexion imminente # Ce script devra aussi permettre d'alerter les cableurs sur les prochaines # affluences en perm import sys import pytz import datetime +from dateutil.parser import parse as parse_gtf +import calendar from lc_ldap.shortcuts import lc_ldap_readonly from lc_ldap.variables import base_dn import ldap +from affich_tools import coul +#: Une journée (c'est plus pratique) +DAY = datetime.timedelta(days=1) + +#: Format des dates dans la base LDAP FORMAT_LDAP = '%Y%m%d%H%M%S%z' +#: Infos à oublier dans un datetime pour ne garder que le jour +ERASE_DAY = { 'second': 0, 'minute': 0, 'microsecond': 0, 'hour': 0, } + #: filtre ldap max(finConnexion) \in intervalle # NB: finConnexion est un attribut ldap multivalué, et on s'intéresse ici # à sa valeur max pour un adhérent. @@ -23,44 +33,122 @@ FORMAT_LDAP = '%Y%m%d%H%M%S%z' # L'autre inégalité ( < ) est plus délicate : # max(F) < v <=> \forall x\in F x < v <=> \not( \exists x\in F x >= v ) # <=> \not( finConnexion>= v ) -filtre_tpl = u'(&(finConnexion>=%s)(!(finConnexion>=%s)))' +FILTRE_TPL_SIMPLE = u'(&(finConnexion>=%(debut)s)(!(finConnexion>=%(fin)s)))' -try: - with open('/etc/timezone', 'r') as f: - tz = pytz.timezone(f.read().strip()) -except: - tz = pytz.UTC +# Le cas précédent était simplifié, en réalité, la connexion s'achève dès que +# l'adhésion se termine. On regarde donc min(max(finConnexion), max(finAdhesion)) +# min(a,b) >= v <=> a >= v /\ b >= v +# min(a,b) < v <=> a < v \/ b < v +FILTRE_TPL = u"""(& + (&(finConnexion>=%(debut)s)(finAdhesion>=%(debut)s)) + (|(!(finConnexion>=%(fin)s))(!(finAdhesion>=%(fin)s))) +)""" -#: Périodicité du script (pour n'envoyer le mail qu'une fois) -periodicite = datetime.timedelta(days=7) - -#: Dans combien de temps la connexion aura-t-elle expiré ? -expire_delay = datetime.timedelta(days=21) +# Calcul de la timezone locale +#try: +# with open('/etc/timezone', 'r') as f: +# tz = pytz.timezone(f.read().strip()) +#except: +# tz = pytz.UTC +# usage : +# datetime.datetime.now(tz) c = lc_ldap_readonly() # Instant courant: on ne garde que le jour -to_erase = { 'second': 0, 'minute': 0, 'microsecond': 0, 'hour': 0, } -now = datetime.datetime.now(tz).replace(**to_erase) +now = datetime.datetime.now() # Applique un delta, si spécifié for arg in sys.argv[1:]: if arg.startswith('+'): - now += datetime.timedelta(days=int(arg[1:])) -print "Nous serons le %s" % now + now += int(arg[1:])*DAY +today = now.replace(**ERASE_DAY) +print "Nous serons le %s" % today -fin = now + expire_delay -warn_debut = fin - periodicite +def compute_fin_connexion(adh): + return min( max(parse_gtf(v.value) for v in adh['fin' + l]) + for l in ['Adhesion', 'Connexion']) -filtre = filtre_tpl % tuple(d.strftime(FORMAT_LDAP) for d in [warn_debut, fin] ) +def select(conn, begin, to, mode='r'): + """Récupère les adhérents dont la connexion expire entre les datetimes + ``begin`` (inclu) et ``to`` (exclu)""" + # Nous avons besoin de dates avec timezone. + if not begin.tzinfo: + begin = begin.replace(tzinfo=pytz.UTC) + if not to.tzinfo: + to = to.replace(tzinfo=pytz.UTC) + data = { 'debut': begin.strftime(FORMAT_LDAP), + 'fin': to.strftime(FORMAT_LDAP), + } + filtre = FILTRE_TPL % data -# NB: on ne prend que les adhérents, d'où SCOPE_ONELEVEL -to_warn = c.search(filtre, scope=ldap.SCOPE_ONELEVEL, dn=base_dn) + # NB: on ne prend que les adhérents, d'où SCOPE_ONELEVEL + res = conn.search(filtre, scope=ldap.SCOPE_ONELEVEL, dn=base_dn, mode=mode) + + return res + +def brief(c, debut, fin): + if not debut.tzinfo: + debut = debut.replace(tzinfo=pytz.UTC) + if not fin.tzinfo: + fin = fin.replace(tzinfo=pytz.UTC) + to_warn = select(c, debut, fin) + print ("%d adhérents seront prévenus que leur connexion expire entre le %s " + \ + "et le %s") % (len(to_warn), debut, fin) + + if "--list" in sys.argv: + for adh in to_warn: + valeurs = [max(parse_gtf(v.value) for v in adh[l]) \ + for l in ['finConnexion', 'finAdhesion'] ] + [f_con, f_adh] = [ coul(str(v), 'rouge' if v >= debut and v < fin else 'vert') \ + for v in valeurs] + print "%r %s %s;%s" % (adh, adh.dn.split(',', 1)[0], f_con, f_adh) + return to_warn + +def prev(c, date): + """Prévisualise l'expiration des connexions sur le mois courant""" + month = date.month + year = date.year + + cal = calendar.Calendar() + first = datetime.datetime(day=1, month=month, year=year, tzinfo=pytz.UTC) + last = first + 31*DAY # un peu plus probablement + disconnect = brief(c, first, last) + by_day = {x: 0 for x in xrange(1,32)} + for adh in disconnect: + date = compute_fin_connexion(adh) + by_day[date.day] += 1 + + spaces = 3 + days = ['L', 'M', 'Me', 'J', 'V', 'S', 'D'] + print " ".join(d + ' '*(spaces-len(d)) for d in days) + l = [] + for d in cal.itermonthdays(year, month): + if not d: + item = "" + elif not by_day[d]: + item = "." + else: + item = str(by_day[d]) + l.append(item + ' '*(spaces-len(item))) + if len(l) == 7: + print " ".join(l) + l = [] + if l: + print " ".join(l) -print ("%d adhérents seront prévenu que leur connexion expire entre le %s " + \ - "et le %s") % (len(to_warn), warn_debut, fin) +# Plusieurs type d'execution: +# * Manuel (préventif): avertit d'une déco dans moins d'un mois +# select(c, today, today+30*DAY) +# * Quotidien (préventif) : avertit d'une déco dans moins d'un mois +# select(c, today+(30-1)*DAY, today+30*DAY) +# * Quotidien (last chance): avertit d'une déco dans moins de 4 jours +# (l'idée, c'est qu'il y a toujours une perm entre les deux) +# select(c, today+(4-1)*DAY, today+4*DAY) +# * Mensuel: avertit les cableurs des connexions à expiration dans le mois +# prochain +# select(c, first_day, last_day+DAY) +# prev(c, today+28*DAY) + -if "--list" in sys.argv: - for adh in to_warn: - print repr(adh) + ' ' + adh.dn.split(',', 1)[0]