#! /usr/bin/env python # -*- coding: iso-8859-15 -*- # Copyright (C) Stéphane Glondu # Licence : GPLv2 u"""Ce script permet au trésorier de repérer plus facilement les papiers que les câbleurs n'ont pas encore rendus. Utilisations possibles : * %(prog)s {paiement|carte|list} [--debug ] * %(prog)s mail [--debug ] L'unique option est : --debug envoyer tous les mails à l' indiquée, plutôt qu'aux vrais destinataires Les commandes sont : * paiement contrôle interactif des nouvelles (ré-)adhésions * carte contrôle interactif des nouvelles cartes d'étudiant * list affiche le récapitulatif des papiers manquants * mail envoyer les mails de rappel aux câbleurs dont le login est dans la , 'bureau' désignant le mail récapitulatif envoyé à bureau ; si la liste est vide tous les mails nécessaires seront envoyés Le modèle du mail envoyé aux câbleurs est lu sur l'entrée standard (typiquement via une redirection). Les trois premières lignes doivent être : Encoding: Subject: Le reste est le corps du message. Il doit contenir une occurrence de '%%s' qui sera remplacée par la liste des papiers manquants. Le mail récapitulatif envoyé à bureau est immuable.""" import sys, os, re sys.path.append('/usr/scripts/gestion') # Fonctions d'affichage from affich_tools import coul, tableau, prompt # Importation de la base de données from ldap_crans import crans_ldap, ann_scol db = crans_ldap() # Détermination de l'uid du câbleur from user_tests import getuser uid = getuser() if not uid : print u"Impossible de déterminer l'utilisateur !" sys.exit(1) cableur = db.search('uid=%s' % uid)['adherent'][0] # Vérification des droits if u'Contrôleur' not in cableur.droits(): print u"Il faut être contrôleur pour exécuter ce script !" sys.exit(1) # Lors des tests, on m'envoie tous les mails ! from socket import gethostname debug = False if gethostname().split(".")[0] == 'egon': debug = 'glondu@crans.org' ann_scol = 2004 if __name__ == '__main__': if len(sys.argv) > 3 and sys.argv[-2] == '--debug': debug = sys.argv[-1] sys.argv.pop() sys.argv.pop() if debug: print u'Mode debug, tous les mails seront envoyés à %s.' % debug def _controle_interactif_adherents(liste, quoi): """ Contrôle interactif des adhérents de la liste (quoi = p ou c). Retourne (nb_OK, nb_pas_OK). """ if quoi not in 'pc': raise ValueError explicite = {'p': u'du paiement', 'c': u"de la carte d'étudiant"}[quoi] restant = len(liste) if restant == 0: return 0, 0 print coul(u'\nContrôle %s des adhérents' % explicite, 'cyan') print u"Pour chaque entrée, il faut taper 'o' ou 'n' (défaut=n)." print u"Une autre réponse entraîne l'interruption du processus." print u"Le format est [nb_restant] Nom, Prénom (aid)." print nb = 0 for a in liste: ok = prompt(u'[%3d] %s, %s (%s) ?' % (restant, a.nom(), a.prenom(), a.id()), 'n', '').lower() restant -= 1 if ok == 'o': modifiable = db.search('aid=%s' % a.id(), 'w')['adherent'][0] if modifiable._modifiable: modifiable.controle('+%s' % quoi) print modifiable.save() nb += 1 else: print coul(u'Adhérent %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge') elif ok != 'n': print coul(u'Arrêt du contrôle %s des adhérents' % explicite, 'rouge') break return nb, len(liste)-nb def _controle_interactif_clubs(liste): """ Contrôle interactif des clubs de la liste (uniquement la charte). Retourne (nb_OK, nb_pas_OK). """ restant = len(liste) if restant == 0: return 0, 0 print coul(u'\nContrôle de la charte des clubs', 'cyan') print u"Pour chaque entrée, il faut taper 'o' ou 'n'." print u"Une autre réponse entraîne l'interruption du processus." print u"Le format est [nb_restant] Nom (cid)." print nb = 0 for c in liste: ok = prompt(u'[%2d] %s (%s) ?' % (restant, c.Nom(), c.id()), 'n', '').lower() restant -= 1 if ok == 'o': modifiable = db.search('cid=%s' % c.id(), 'w')['club'][0] if modifiable._modifiable: modifiable.controle('+p') print modifiable.save() nb += 1 else: print coul(u'Club %s locké, réessayer plus tard' % modifiable.Nom(), 'rouge') elif ok != 'n': print coul(u'Arrêt du contrôle de la charte des clubs', 'rouge') break return nb, len(liste)-nb def controle_interactif(quoi): """ Procédure interactive de contrôle des paiements/chartes (quoi=p) et cartes (quoi=c). """ if quoi not in 'pc': raise ValueError todo_list = db.search('%s=%d&controle!=*%s*' % ({'p': 'paiement', 'c': 'carteEtudiant'}[quoi], ann_scol, quoi)) # Tri de la liste des adhérents selon nom, prénom # Ça peut se faire plus facilement en Python 2.4 avec l'argument key todo_list['adherent'].sort(lambda x, y: cmp((x.nom(), x.prenom()), (y.nom(), y.prenom()))) # Traitement des adhérents oka, noka = _controle_interactif_adherents(todo_list['adherent'], quoi) if quoi == 'p': # Tri de la liste des clubs todo_list['club'].sort(lambda x, y: cmp(x.nom(), y.nom())) # Traitement des clubs (uniquement la charte) okc, nokc =_controle_interactif_clubs(todo_list['club']) print coul(u'\nRécapitulatif des nouveaux contrôles +%s :' % quoi, 'violet') liste = [[u'adhérents', str(oka), str(noka)]] if quoi == 'p': liste.append([u'clubs', str(okc), str(nokc)]) print tableau(liste, titre = [u'Catégorie', u'OK', u'pas OK'], largeur = [15, 10, 10]) def formater_pour_cableur(liste): """ Formate la liste d'adhérents ou de clubs avec les dates correspondantes. liste est une liste de couples (date, objet). """ lignes = [] total = 0 liste.sort(lambda x, y: cmp(x[1].nom(), y[1].nom())) for date, a in liste: lignes.append([a.id(), a.Nom(), date]) total += 1 return tableau(lignes, titre = [u'id', u'Nom', u'Date Heure'], largeur = [6, 40, 18], alignement = ['d', 'c', 'c']) + u'\nTotal : %d' % total def formater_pour_bureau(dico): """ Formate la liste d'adhérents ou de clubs avec les câbleurs correspondantes pour le mail récapitulatif envoyé à bureau. """ lignes = [] total = 0 liste = dico.keys() liste.sort() for cableur in liste: for date, a in dico[cableur]: lignes.append([a.id(), a.Nom(), cableur, date]) total += 1 return tableau(lignes, titre = [u'id', u'Nom', u'Câbleur', u'Date'], largeur = [6, 40, 18, 18], alignement = ['d', 'c', 'c', 'c']) + u'\nTotal : %d' % total def qui(historique, quoi): """ Recherche le câbleur qui a effectué la dernière modification quoi dans l'historique, ou qui a inscrit l'adhérent. Retourne le couple (date, cableur) ou ('_inconnu_', '_inconnu_'). """ regexp = re.compile(r'^([^,]*), ([^ :]*)') cableur = ('_inconnu_', '_inconnu_') champ = re.compile(quoi) for ligne in historique: if champ.search(ligne) or 'inscription' in ligne: matched = regexp.search(ligne) if matched: cableur = matched.group(1), matched.group(2) return cableur def chercher_cableurs(liste, quoi): """ Renvoie un dictionnaire cableur->adherents à partir de liste, quoi désigne le champ qui sera recherché dans l'historique. """ resultat = {} for a in liste: date, cableur = qui(a.historique(), quoi) if not resultat.has_key(cableur): resultat[cableur] = [] resultat[cableur].append((date, a)) return resultat from email_tools import send_email, parse_mail_template # Mise en application du mémorable cours de Fred sur les objets :-) class ControleMailer: def __init__(self): # Recherche des câbleurs possédant des cotisations/chartes todo_list = db.search('paiement=%d&controle!=*p*' % ann_scol) self._paiements = chercher_cableurs(todo_list['adherent'], r'(paiement|controle.*\+k)') self._chartes = chercher_cableurs(todo_list['club'], 'paiement') # Recherche des câbleurs possédant des cartes d'étudiant todo_list = db.search('carteEtudiant=%d&controle!=*c*' % ann_scol) self._cartes = chercher_cableurs(todo_list['adherent'], 'carteEtudiant') # Récupère tous les câbleurs qui doivent quelque chose cableurs = {} for a in self._paiements.keys(): if a != '_inconnu_': cableurs[a] = None for a in self._chartes.keys(): if a != '_inconnu_': cableurs[a] = None for a in self._cartes.keys(): if a != '_inconnu_': cableurs[a] = None self._cableurs = cableurs.keys() # Vérification de l'alias pour l'expéditeur if u'tresorier' in cableur.alias(): self._sender = u'%s ' % cableur.Nom() else: self._sender = u'%s <%s@crans.org>' % (cableur.Nom(), cableur.canonical_alias() or cableur.mail()) # Se connecte au serveur SMTP par défaut from smtplib import SMTP self._server = SMTP() self._server.connect() def __del__(self): # Termine la connexion au serveur SMTP self._server.quit() def recapitulatif(self): """ Renvoie le récapitulatif pour le bureau. """ msg = u'' if self._paiements: msg += u"Fiches d'adhésion et cotisations et/ou caution des adhérents :\n" msg += formater_pour_bureau(self._paiements) + '\n\n' if self._chartes: msg += u"Chartes signées des clubs :\n" msg += formater_pour_bureau(self._chartes) + '\n\n' if self._cartes: msg += u"Carte d'étudiant des adhérents :\n" msg += formater_pour_bureau(self._cartes) + '\n\n' return msg.strip() def mail_bureau(self): """ Envoie le mail récapitulatif à bureau (s'il y a qqch à envoyer). """ msg = self.recapitulatif() if msg: msg += u"\n\n-- \nScript exécuté par %s\n" % cableur.Nom() send_email(self._sender, u"Bureau ", u"Récapitulatif des papiers manquants", msg, server = self._server, debug = debug) return coul(u'Mail envoyé à bureau avec succès !', 'vert') else: return coul(u'Tout est à jour, aucun mail envoyé.', 'vert') def mail_cableurs(self, subject, body, liste=None): """ Envoie un mail aux câbleurs concernés qui figurent dans la liste. Si liste vaut None, envoie un mail à tous les câbleurs concernés. Les arguments subject, body permettent de personnaliser (un peu) le mail qui sera envoyé. """ nb = 0 if liste == None: liste = self._cableurs for c in liste: msg = u'' if self._paiements.has_key(c): msg += u"Fiches d'adhésion, cotisations et/ou caution des adhérents :\n" msg += formater_pour_cableur(self._paiements[c]) + '\n\n' if self._chartes.has_key(c): msg += u"Chartes signées des clubs :\n" msg += formater_pour_cableur(self._chartes[c]) + '\n\n' if self._cartes.has_key(c): msg += u"Carte d'étudiant des adhérents :\n" msg += formater_pour_cableur(self._cartes[c]) + '\n\n' if msg: msg = msg.strip() send_email(self._sender, "%s@crans.org" % c, subject, body % msg, server = self._server, debug = debug) nb += 1 return coul(u'%d mail(s) envoyé(s) aux câbleurs avec succès !' % nb, 'vert') def __usage(message=None): """ Comment ça marche ? """ print __doc__ % { 'prog': sys.argv[0] } if message: print message sys.exit(1) if __name__ == '__main__' : if len(sys.argv) <= 1: __usage() elif sys.argv[1] == 'paiement': if len(sys.argv) != 2: __usage(u'Mauvaise utilisation de paiement') controle_interactif('p') elif sys.argv[1] == 'carte': if len(sys.argv) != 2: __usage(u'Mauvaise utilisation de carte') controle_interactif('c') elif sys.argv[1] == 'list': if len(sys.argv) != 2: __usage(u'Mauvaise utilisation de list') print ControleMailer().recapitulatif(), elif sys.argv[1] == 'mail': mailer = ControleMailer() cableurs = sys.argv[2:] if cableurs: bureau = False if 'bureau' in cableurs: cableurs.remove('bureau') bureau = True else: bureau = True cableurs = mailer._cableurs if cableurs: print u'Des mails vont être envoyés aux câbleurs, lecture du modèle...' subject, body = parse_mail_template(sys.stdin) try: body % u'' except TypeError: print u"Le format du modèle n'est pas correct, arrêt." sys.exit(1) print u'Modèle OK, on envoie les mails...' print mailer.mail_cableurs(subject, body, cableurs) if bureau: print mailer.mail_bureau() else: __usage(u'Commande inconnue : %s' % sys.argv[1]) sys.exit(0) # pydoc n'aime pas l'unicode :-(