From e867dfe2214b6121bba7de68e3a9ed8c5c6b0e3f Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Thu, 13 Mar 2014 09:54:03 +0100 Subject: [PATCH] =?UTF-8?q?[gest=5Fcrans=5Flc]=20Machines=20=C3=A9ditables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestion/gest_crans_lc.py | 991 +++++++++++++++++++++++++++++++++++---- 1 file changed, 889 insertions(+), 102 deletions(-) diff --git a/gestion/gest_crans_lc.py b/gestion/gest_crans_lc.py index d1bdc06a..038d3c4f 100755 --- a/gestion/gest_crans_lc.py +++ b/gestion/gest_crans_lc.py @@ -9,37 +9,119 @@ Copyright (C) Valentin Samir Licence : GPLv3 """ +### sur le dialog (1.1) de zamok, il manque les fonctionnalité suivante présente dans la 1.2 +### --default-button pour choisir le bouton sélectionner par defaut +### --not-tags pour masquer les tags quand il ne servent à rien pour l'utilisateur (mais sont utilisé comme index par le programme) + import os import sys +import time +import ldap import signal +import inspect +import tempfile import collections +sys.path.append('/usr/scripts/') from pythondialog import Dialog from gestion.affich_tools import get_screen_size, coul +import gestion.config as config import lc_ldap.shortcuts +import lc_ldap.objets +import lc_ldap.attributs as attributs import lc_ldap.printing as printing +debugf=None +debug_enable = False + +def mydebug(txt): + global debugf, debug_enable + if debug_enable: + if debugf is None: + debugf = open('/tmp/gest_crans_lc.log', 'w') + if isinstance(txt, list): + for v in txt: + mydebug(' ' + str(v)) + else: + debugf.write(str(txt)+'\n') + debugf.flush() + os.fsync(debugf) + # Implémentation "à la main" de la tail récursion en python # voir http://kylem.net/programming/tailcall.html # je trouve ça assez sioux +# En plus, ça nous permet de gérer plus facilement le +# code en CPS class TailCaller(object) : + """ + Classe permetant, en décorant des fonctions avec, d'avoir de la tail récursion + faite "à la main" en python (voir http://kylem.net/programming/tailcall.html) + """ + other_callers = {} def __init__(self, f) : self.f = f + self.func_name = f.func_name + TailCaller.other_callers[id(self)]=f.func_name + def __del__(self): + del(TailCaller.other_callers[id(self)]) + def __call__(self, *args, **kwargs) : - ret = self.f(*args, **kwargs) - while isinstance(ret, TailCall) : - ret = ret.handle() + mydebug("***%s calling" % self.func_name) + stacklvl = len(inspect.stack()) + try: + ret = self.f(*args, **kwargs) + except Continue as c: + ret = c.tailCall + while isinstance(ret, TailCall): + # Si la continuation a été créer par un TailCaller précédent, on propage + # Cela permet d'assurer qu'un TailCaller ne s'occupe que des TailCall + # Crée après lui. + if stacklvl>=ret.stacklvl: + mydebug("***%s terminate" % self.func_name) + raise Continue(ret) + mydebug("***%s doing %s" % (self.func_name, ret)) + try: + ret = ret.handle() + except Continue as c: + ret = c.tailCall + mydebug("***%s returning %s" % (self.func_name, ret)) return ret -def tailCaller(f): +def tailcaller(f): + """ + Décorateur retardant la décoration par TailCaller d'une fonction + À utiliser sur les fonctions faisant de la tail récursion + et devant retourner une valeur. On l'utilise de toute + façon sur la fonction d'entrée dans l'application + Une fonction de devrait pas instancier de TailCall sans + être décoré par ce décorteur de façon générale + """ + f.tailCaller = True return f - + +class Continue(Exception): + """Exception pour envoyer des TailCall en les raisant""" + def __init__(self, tailCall): + self.tailCall = tailCall + class TailCall(object) : + """ + Appel tail récursif à une fonction et ses argument + On peut voir un TailCall comme le fait de retarder + l'appel à une fonction (avec ses listes d'arguements) + à un moment futur. La fonction sera appelée dans les + cas suivant : + * Le TailCall est retourné par une fonction qui est un TailCaller + * L'exception Continue(TailCall(...)) est levée et traverse + une fonction qui est un TailCaller + """ + def __init__(self, call, *args, **kwargs) : + self.stacklvl = len(inspect.stack()) if isinstance(call, TailCall): - call.update(**kwargs) + call.kwargs.update(**kwargs) kwargs = call.kwargs args = call.args + args call = call.call @@ -48,22 +130,30 @@ class TailCall(object) : self.args = args self.kwargs = kwargs + def __str__(self): + return "TailCall<%s(%s%s%s)>" % (self.call.func_name, ', '.join(repr(a) for a in self.args), ', ' if self.args and self.kwargs else '', ', '.join("%s=%s" % (repr(k),repr(v)) for (k,v) in self.kwargs.items())) + def __call__(self, *args, **kwargs): self.kwargs.update(kwargs) self.args = self.args + args return self - def update(self, **d): - self.kwargs.update(d) - return self - def handle(self) : - if isinstance(self.call, TailCaller) : - return self.call.f(*self.args, **self.kwargs) - else : - return self.call(*self.args, **self.kwargs) + caller = None + call = self.call + while isinstance(call, TailCaller) : + caller = call + call = self.call.f + return call(*self.args, **self.kwargs) + +def unicode_of_Error(x): + """Formatte l'exception de type ValueError""" + return u"\n".join(unicode(i, 'utf-8') if type(i) == str + else repr(i) for i in x.args) + def handle_exit_code(d, code): + """Gère les codes de retour dialog du menu principal""" if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): if code == d.DIALOG_CANCEL: msg = "Vous avez choisi Annuler dans la dernière fenêtre de dialogue.\n\n" \ @@ -82,16 +172,19 @@ def handle_exit_code(d, code): class GestCrans(object): - + """Interface de gestion des machines et des adhérents du crans, version lc_ldap""" def __getattribute__(self, attr): ret = super(GestCrans, self).__getattribute__(attr) + # Petit hack pour que ça soit les methodes de l'objet instancié qui soient + # décorée par TailCaller et pas les méthodes statiques de la classe GestCrans if getattr(ret, 'tailCaller', False) and not isinstance(ret, TailCaller): ret = TailCaller(ret) setattr(self, attr, ret) return ret - + def __init__(self): - signal.signal(signal.SIGINT, signal.SIG_IGN) + if not debug_enable: + signal.signal(signal.SIGINT, signal.SIG_IGN) # On initialise le moteur de rendu en spécifiant qu'on va faire du dialog printing.template(dialog=True) @@ -106,14 +199,323 @@ class GestCrans(object): self.conn.dn = luser[0].dn self.dialog = Dialog() - self.menu_principal() - - @tailCaller + + @tailcaller + def edit_blacklist_select(self, obj, title, cont): + """ + Permet de choisir une blackliste parmis celle de obj oubien une nouvelle blacklist. + Retourne (index, bl) où bl est un dictionnaire représentant la blackliste et index + l'index de la blackliste dans obj['blacklist'] ou new pour une nouvelle blacklist + """ + def box(): + choices = [('new', 'Ajouter une nouvelle blackliste')] + index = 0 + for bl in obj['blacklist']: + choices.append((str(index), coul("%s [%s]" % (bl['type'], bl['comm']), 'rouge' if bl['actif'] else None, dialog=True))) + index+=1 + return self.dialog.menu( + "Éditer une blacklist ou en ajouter une nouvelle ?\n(les blacklistes actives apparaissent en rouge)", + width=0, + height=0, + menu_height=0, + item_help=0, + title=title, + scrollbar=True, + colors=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=choices) + + (code, tag) = box() + retry_cont = TailCall(self.edit_blacklist_select, obj=obj, title=title, cont=cont) + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + elif code in [self.dialog.DIALOG_OK]: + if tag == 'new': + return tag, {'debut':0, 'fin':0, 'type':'', 'comm':''} + else: + try: + bl = {} + bl.update(obj['blacklist'][int(tag)].value) + return tag, bl + except Exception as e: + self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) + raise Continue(retry_cont(values=values)) + else: + self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) + raise Continue(retry_cont) + + @tailcaller + def edit_blacklist_type(self, title, cont): + """Permet de choisir un type de blackliste pour les nouvelles blacklistes""" + def box(): + return self.dialog.menu( + "Choisissez un type de blacklist", + width=0, + height=0, + menu_height=0, + item_help=0, + title=title, + scrollbar=True, + colors=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=[(k,v) for (k,v) in config.blacklist_items.items()]) + (code, tag) = box() + retry_cont = TailCall(self.edit_blacklist_type, title=title, cont=cont) + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + elif code in [self.dialog.DIALOG_OK]: + if tag in config.blacklist_items: + return tag + else: + self.dialog.msgbox("%s n'est pas une blacklist" % tag, title="Erreur rencontrée", width=73, height=10) + raise Continue(retry_cont(values=values)) + else: + self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) + raise Continue(retry_cont) + + @tailcaller + def get_timestamp(self, title, text, cont, hour=-1, minute=-1, second=-1, day=0, month=0, year=0): + """Fait choisir une date et une heure et retourne le tuple (year, month, day, hour, minute, second)""" + retry_cont = TailCall(self.get_timestamp, title=title, text=text, cont=cont, hour=hour, minute=minute, second=second, day=day, month=month, year=year) + def get_date(day, month, year): + (code, output) = self.dialog.calendar(text, day=day, month=month, year=year, title=title) + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + elif output: + (day, month, year) = output + return (year, month, day) + else: + raise EnvironmentError("Pourquoi je n'ai pas de date ?") + def get_time(hour, minute, second, day, month, year): + (code, output) = self.dialog.timebox(text, hour=hour, minute=minute, second=second) + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(retry_cont(day=day, month=month, year=year)) + elif output: + (hour, minute, second) = output + return (hour, minute, second) + else: + raise EnvironmentError("Pourquoi je n'ai pas d'horaire ?") + (year, month, day) = get_date(day, month, year) + (hour, minute, second) = get_time(hour, minute, second, day, month, year) + return (year, month, day) + (hour, minute, second) + + def edit_blacklist(self, obj, title, update_obj, cont, bl=None, tag=None, bl_type=None, debut=None, fin=None, comm=None): + """Pour éditer les blacklistes d'un objet lc_ldap""" + self_cont = TailCall(self.edit_blacklist, obj=obj, title=title, update_obj=update_obj, cont=cont, bl=bl, tag=tag, bl_type=bl_type, debut=debut, fin=fin, comm=comm) + # Si bl ou tag ne sont pas définit on les demande + if bl is None or tag is None: + bl_type = None + debut = None + fin = None + comm = None + tag, bl = self.edit_blacklist_select(obj, title, cont) + if bl_type is None and tag == 'new': + bl['type'] = self.edit_blacklist_type(title, self_cont) + elif tag == 'new': + bl['type'] = bl_type + + # Cas de l'ajout d'un blacklist + if tag == 'new': + # Si debut n'est pas encore spécifié, on le demande + if debut is None: + debut_tuple = self.get_timestamp(title=title, text="Choisir le début de la blacklist", cont=self_cont(bl=bl, tag=tag, bl_type=None, debut=None, fin=None, comm=None)) + debut = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) + # Idem pour fin + if fin is None: + if self.dialog.yesno("Mettre une date de fin ?", title=title) == self.dialog.DIALOG_OK: + fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :", cont=self_cont(bl=bl, tag=tag, bl_type=bl_type, debut=None, fin=None, comm=None)) + fin = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) + else: + fin = '-' + bl['debut']=debut + bl['fin']=fin + bl['comm']=self.get_comment(title=title, text="Commentaire ?", cont=self_cont(bl=bl, tag=tag, bl_type=bl['type'], debut=debut, fin=None, comm=None)) + if self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Ajouter la blacklist ?"): + try: + with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: + obj['blacklist'].append(bl) + obj.save() + # On s'en va en mettant à jour dans la continuation la valeur de obj + raise Continue(cont(**{update_obj:obj})) + # On propage les Continue + except Continue: + raise + # En cas d'une autre erreur, on l'affiche et on retourne + except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: + self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73) + raise Continue(self_cont) + else: + raise Continue(self_cont(bl=None)) + + # Cas de l'édition d'une blacklist + else: + if debut is None: + # Mettre un warning pour éditer (seulement quand debut vaut None pour ne pas le répéter à chaque fois que l'on revient en arrière par la suite + if not self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Éditer la blackliste ?"): + raise Continue(self_cont(bl=None)) + debut = time.localtime(bl['debut']) + debut_tuple = self.get_timestamp(title=title, text="Choisir le début de la blacklist", cont=self_cont(bl=bl, tag=tag, debut=None, fin=None, comm=None), + day=debut.tm_mday, + month=debut.tm_mon, + year=debut.tm_year, + hour=debut.tm_hour, + minute=debut.tm_min, + second=debut.tm_sec + ) + debut = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) + bl['debut'] = debut + if fin is None: + if self.dialog.yesno("Mettre une date de fin ?", title=title) == self.dialog.DIALOG_OK: + if bl['fin'] == '-': + fin = time.localtime() + else: + fin = time.localtime(bl['fin']) + fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :", cont=self_cont(bl=bl, tag=tag, debut=debut, fin=None, comm=None), + day=fin.tm_mday, + month=fin.tm_mon, + year=fin.tm_year, + hour=fin.tm_hour, + minute=fin.tm_min, + second=fin.tm_sec + ) + fin = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1)))) + else: + fin = '-' + bl['fin'] = fin + bl['comm']=self.get_comment(title=title, text="Commentaire ?", init=bl['comm'], cont=self_cont(bl=bl, tag=tag, bl_type=bl['type'], debut=debut, fin=None, comm=None)) + if self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Modifier la blacklist ?"): + try: + with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: + obj['blacklist'][int(tag)]=bl + obj.save() + # On s'en va en mettant à jour dans la continuation la valeur de obj + raise Continue(cont(**{update_obj:obj})) + # On propage les Continue + except Continue: + raise + # En cas d'une autre erreur, on l'affiche et on retourne au menu d'édition + except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: + self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73) + raise Continue(self_cont) + else: + raise Continue(self_cont(bl=None)) + + + + def edit_boolean_attributs(self, obj, attribs, title, update_obj, cont, values={}): + """ + Permet d'éditer des attributs booléen de l'objet obj listé dans attribs. + update_obj est le nom du paramètre à mettre à jour dans cont pour passer l'objet modifié + """ + # Dictionnaire décrivant quelle est la valeur booléenne à donner à l'absence de l'attribut + missing = { + 'default' : False, # par défaut, on dit que c'est False + attributs.dnsIpv6 : True # pour dnsIpv6, c'est True + } + choices = [(a.ldap_name, a.legend, 1 if values.get(a.ldap_name, obj[a.ldap_name][0] if obj[a.ldap_name] else missing.get(a, missing['default'])) else 0) for a in attribs] + (code, output) = self.dialog.checklist("Activier ou désactiver les propriétés suivantes", + height=0, + width=0, + list_height=7, + choices=choices, + title=title) + + # Une continuation que l'on suivra si quelque chose se passe mal + retry_cont = TailCall(self.edit_boolean_attributs, obj=obj, update_obj=update_obj, attribs=attribs, title=title, cont=cont, values=values) + # Si on a appuyé sur annulé ou ESC, on s'en va via la continuation donnée en argument + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + # Sinon, c'est OK + elif code in [self.dialog.DIALOG_OK]: + # On transforme la liste des cases dialog cochée en dictionnnaire + values = dict((a.ldap_name, a.ldap_name in output) for a in attribs) + try: + # On met à jour chaque attribut si sa valeur à changé + with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: + for a in attribs: + if obj[a.ldap_name] and obj[a.ldap_name][0] != values[a.ldap_name]: + obj[a.ldap_name]=values[a.ldap_name] + elif not obj[a.ldap_name] and missing.get(a, missing['default']) != values[a.ldap_name]: + obj[a.ldap_name]=values[a.ldap_name] + obj.save() + # On s'en va en mettant à jour dans la continuation la valeur de obj + raise Continue(cont(**{update_obj:obj})) + # On propage les Continue + except Continue: + raise + # En cas d'une autre erreur, on l'affiche et on retourne au menu d'édition + except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: + self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) + raise Continue(retry_cont(values=values)) + else: + self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) + raise Continue(retry_cont) + + + def edit_attributs(self, obj, attr, title, update_obj, cont, tag=None, values=None): + """ + Permet d'éditer la liste d'attribut attr de l'objet obj. + update_obj est le nom du paramètre à mettre à jour dans cont pour passer l'objet modifié + """ + + # Il n'y a pas inputmenu dans la lib dialog, du coup, on traite les arguments à la main. + # Ça reste relativement acceptable comme on utilise la fonction de la lib pour appeler dialog + def box(values, default_tag): + cmd = ['--inputmenu', "Édition de l'attribut %s :" % attr, "0", "0", "20"] + index=0 + for value in values: + cmd.extend([str(index), str(value)]) + index+=1 + cmd.extend(['new', '']) + (code, output) = self.dialog._perform(*(cmd,), title=title, default_item=str(default_tag)) + if code == self.dialog.DIALOG_EXTRA: + output = output.split(' ', 2)[1:] + else: + output = '' + return (code, output) + + if values is None: + values = [str(a) for a in obj[attr]] + retry_cont = TailCall(self.edit_attributs, obj=obj, attr=attr, title=title, update_obj=update_obj, cont=cont, values=values) + (code, output) = box(values, tag) + # Si on a fait annulé ou appuyé sur ESC + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + # Si on a éditer ou ajouté une valeur de/dans la liste de l'attribut + elif code in [self.dialog.DIALOG_EXTRA]: + tag, value = output + if tag == 'new': + if value: + values.append(value) + elif value == '': + values.pop(int(tag)) + else: + values[int(tag)] = value + raise Continue(retry_cont(values=values, tag=tag)) + # Si on valide les changement affectué, on sauvegarde les nouvelles valeurs dans l'objet + elif code in [self.dialog.DIALOG_OK]: + try: + with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj: + obj[attr] = [unicode(value, 'utf-8') for value in values] + obj.save() + raise Continue(cont(**{update_obj:obj})) + except Continue: + raise + except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: + self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) + raise Continue(retry_cont) + # Si dialog retourne un code inconnu, on le signale + else: + self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) + raise Continue(retry_cont) + def search(self, objectClassS, title, values={}, cont=None): """ Rechercher des adhérents ou des machines dans la base ldap retourne le tuple (code de retour dialog, valeurs entrée par l'utilisateur, liste d'objets trouvés) - La fonction est découpé en trois partie : + La fonction est découpé en trois partie : * affichage dialog et récupération du résultat * construction de filtres de recherche ldap et recherches ldap * filtre sur les résultats des recherches ldap @@ -155,7 +557,7 @@ class GestCrans(object): (code, dialog_values) = box() # Si il a appuyé sur annuler ou sur escape, on saute sur la continuation if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - return cont + raise Continue(cont) else: # Transformation de la liste des valeures entrée en dictionnnaire dialog_values = dict(zip(select_adherent + select_machine, dialog_values)) @@ -208,7 +610,6 @@ class GestCrans(object): # On filtre sur les objectClassS return ldap_values, [ o for objectClass in objectClassS for o in machines_f+adherents_f if objectClass in o['objectClass'] ] - # select_one n'est pas un tailcaller, les continuations sont traitée par les fonctions appelantes def select_one(self, items, title, default_item=None, cont=None): """Fait selectionner un item parmis une liste d'items à l'utisateur""" choices=[] @@ -253,29 +654,58 @@ class GestCrans(object): # Si l'utilisateur à annulé, on coninue avec la continuation if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - return cont + raise Continue(cont) # Si l'utilisateur n'a pas choisis une ligne correspondant à quelque chose elif not tag: self.dialog.msgbox("Merci de choisir l'un des item de la liste ou d'annuler", title="Sélection", width=0, height=0) - return TailCall(self.select_one, items, title, default_item, cont) + raise Continue(TailCall(self.select_one, items=items, title=title, default_item=default_item, cont=cont)) # Sinon on retourne l'item choisis - elif self.select_confirm(items[int(tag)], title): + elif self.confirm_item(items[int(tag)], title): return items[int(tag)] else: - return cont + raise Continue(cont) - def select_confirm(self, item, title): + @tailcaller + def get_comment(self, title, text, cont, init='', force=False): + """ + Fait entrer à l'utilisateur un commentaire et le retourne. + Si force est à True, on oblige le commentaire à être non vide + """ + (code, output) = self.dialog.inputbox(text=text, title=title, init=init) + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + elif force and not output: + self.dialog.msgbox("Entrée vide, merci d'indiquer quelque chose") + raise Continue(TailCall(self.get_comment, title=title, text=text, cont=cont, force=force)) + else: + return unicode(output, 'utf-8') + + def confirm_item(self, item, title, defaultno=False, **params): """Affiche un item et demande si c'est bien celui là que l'on veux (supprimer, éditer, créer,...)""" return self.dialog.yesno( - printing.sprint(item), + printing.sprint(item, **params), + no_collapse=True, + colors=True, + title=title, + defaultno=defaultno, + width=0, height=0, + backtitle="Appuyez sur MAJ pour selectionner du texte" + ) == self.dialog.DIALOG_OK + + def display_item(self, item, title, **params): + """Affiche un item""" + return self.dialog.msgbox( + printing.sprint(item, **params), no_collapse=True, colors=True, title=title, width=0, height=0, backtitle="Appuyez sur MAJ pour selectionner du texte" - ) == self.dialog.DIALOG_OK + ) - @tailCaller + # On a besoin du décorateur ici car select va retourner un item après avoir + # possblement traiter plusieurs tailcall + @tailcaller def select(self, objectClassS, title, values={}, cont=None): """Permet de choisir un objet adhérent ou machine dans la base ldap""" try: @@ -284,83 +714,380 @@ class GestCrans(object): # S'il n'y a pas de résultas, on recommence if not items: self.dialog.msgbox("Aucun Résultat", title="Recherche", width=0, height=0) - return TailCall(self.select, objectClassS, title, values, cont=cont) + raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, cont=cont)) # S'il y a plusieurs résultats elif len(items)>1: # On en fait choisir un, si c'est une continuation qui est renvoyé, elle est gérée par select - return self.select_one(items, title, cont=TailCall(self.select, objectClassS, title, values, cont)) + return self.select_one(items, title, cont=TailCall(self.select, objectClassS=objectClassS, title=title, values=values, cont=cont)) # S'il y a exactement 1 résultat à la recherche, on fait confirmer son choix à l'utilisateur elif len(items) == 1: item=items[0] # On fait confirmer son choix à l'utilisateur - if self.select_confirm(item, title): + if self.confirm_item(item, title): return item else: - return TailCall(self.select, objectClassS, title, values, cont=cont) + raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, cont=cont)) + except Continue: + raise except Exception as e: self.dialog.msgbox("%r" % e, title="Erreur rencontrée", width=0, height=0) - return TailCall(self.select, objectClassS, title, values, cont=cont) + raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, cont=cont)) - @tailCaller - def modif_adherent(self, cont, adherent=None): - if adherent is None: - adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont) - self.dialog.msgbox("todo", width=0, height=0) - return cont(proprio=adherent) + def machine_information(self, cont, machine=None, objectClass=None, parent=None, realm=None, fields_values=None): + """ + Permet de modifier une machine si elle est fournit par le paramètre machine + sinon, crée une machine à partir de parent, objectClass et realm. + Si on ne fait qu'éditer une machine, parent, objectClass et realm sont ignoré + D'une machinère générale, il faudrait mettre ici tous les attributs single value + et les multivalué que l'on peut simplement représenter de façon textuelle avec + un séparateur. + Pour le moment il y a : + * host + * macAddress + * ipHostNumber + * port(TCP|UDP)(in|out) + """ + a = attributs + # Quel sont les attributs ldap dont on veut afficher et la taille du champs d'édition correspondant + to_display = [(a.host, 30), (a.macAddress, 17), (a.ipHostNumber, 15), + (a.portTCPout, 50), (a.portTCPin, 50), (a.portUDPout, 50), + (a.portUDPin, 50) + ] - @tailCaller - def create_adherent(self, cont): - self.dialog.msgbox("todo", width=0, height=0) - return cont + # Quel séparateur on utilise pour les champs multivalué + separateur = ' ' - @tailCaller - def delete_adherent(self, cont): - adherent = self.select(["adherent"], "Recherche d'un adhérent pour supression", cont=cont) - self.dialog.msgbox("todo", width=0, height=0) - return cont + if machine: + objectClass = machine["objectClass"][0] + attrs = dict((k,[str(a) for a in at]) for k,at in machine.items()) + else: + attrs = {} - def modif_machine_information(self, machine, cont): - self.dialog.msgbox("todo", width=0, height=0) - (code, tag) = self.dialog.form( + fields = [("%s :" % a.legend, separateur.join(attrs.get(a.ldap_name, [a.default] if a.default else [])), l+1, l) for a,l in to_display] + + (code, tags) = self.dialog.form( text="", height=0, width=0, form_height=0, - fields=[("Nom de machine :", "", 10, 30), - ("Rugby Team", "", 20), - ("Car", "", 20), - ("Celebrity", "", 20)], - title="A demo of the form dialog.", - backtitle="And now, for something " - "completely different...") - return cont + fields=fields_values if fields_values else fields, + title="Paramètres machine", + backtitle="Gestion des machines du Crans") + + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + + # On prépare les fiels à afficher à l'utilisateur si une erreure à lieu + # pendant le traitement des donnée (on n'éfface pas ce qui a déjà été entré + # c'est au cableur de corriger ou d'annuler + fields_values = [] + for ((a,l),values) in zip(to_display, tags): + fields_values.append(("%s :" % a.legend, values, l)) + try: + attrs = {} + # On traite les valeurs reçues + for ((a,l),values) in zip(to_display, tags): + values = unicode(values, 'utf-8') + # Si le champs n'est pas single value, on utilise separateur pour découper + # et on ne garde que les valeurs non vides + if not a.singlevalue: + values = [v for v in values.split(separateur) if v] + # Pour host, on fait quelqes vérification de syntaxe + if a.ldap_name == 'host': + # Si c'est une machine wifi, host doit finir par wifi.crans.org + if "machineWifi" == objectClass: + hostend = ".wifi.crans.org" + # Si c'est une machine wifi, host doit finir par crans.org + elif "machineFixe" == objectClass: + hostend = ".crans.org" + # Sinon, libre à chachun d'ajouter d'autres objectClass ou de filtrer + # plus finement fonction des droits de self.conn.droits + else: + raise ValueError("La machine n'est ni une machine fixe, ni une machine wifi mais %s ?!?" % objectClass) + + if not values.endswith(hostend) and not '.' in values: + values = "%s.wifi.crans.org" % values + elif values.endswith(hostend) and '.' in values[:-len(hostend)]: + raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend) + elif not values.endswith(hostend) and '.' in values: + raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend) + + attrs[a.ldap_name]=values + if machine: + with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine: + for (key, values) in attrs.items(): + machine[key]=values + machine.validate_changes() + machine.save() + else: + if parent is None or realm is None: + raise EnvironmentError("On essaye de créer une machine mais parent ou realm vaut None") + ldif = { + 'macAddress': ['%s' % attrs['macAddress'].encode('utf-8')], + 'host': ['%s' % attrs['host'].encode('utf-8')] + } + with self.conn.newMachine(parent.dn, realm, ldif) as machine: + for (key, values) in attrs.items(): + machine[key]=values + if attributs.ipsec in machine.attribs: + machine[attributs.ipsec.ldap_name]=attributs.ipsec.default + machine.validate_changes() + if self.confirm_item(machine, "Voulez vous vraiement créer cette machine ?"): + machine.create() + self.display_item(machine, "La machine à bien été créée", ipsec=True) + else: + raise Continue(cont) + return cont(machine=machine) + except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e: + self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) + raise Continue(TailCall(self.machine_information, machine=machine, cont=cont, objectClass=objectClass, parent=parent, realm=realm, fields_values=fields_values)) def modif_machine_blacklist(self, machine, cont): - self.dialog.msgbox("todo", width=0, height=0) + """Raccourci vers edit_blacklist spécifique aux machines""" + return self.edit_blacklist(obj=machine, title="Éditions des blacklist de la machine %s" % machine['host'][0], update_obj='machine', cont=cont) + + def certificat_tlsa(self, certificat, cont, values=None): + """Menu d'éditions des paramètres TLSA d'un certificat""" + separateur = ' ' + form = { + 'Type de certificat' : {'ldap_name' : 'certificatUsage', 'text':"".join(str(s) for s in certificat.get('certificatUsage', [])), 'len':1}, + 'Type de correspondance' : {'ldap_name' : 'matchingType', 'text':"".join(str(s) for s in certificat.get('matchingType', [])), 'len':1}, + 'Ports TCP' : {'ldap_name' : 'portTCPin', 'text':separateur.join(str(p) for p in certificat.get('portTCPin', [])), 'len':30}, + 'Ports UDP' : {'ldap_name' : 'portUDPin', 'text':separateur.join(str(p) for p in certificat.get('portUDPin', [])), 'len':30}, + } + form_order = ['Type de certificat', 'Type de correspondance', 'Ports TCP', 'Ports UDP'] + def box(fields_values=None): + fields = [("%s : " % k, form[k]['text'], form[k]['len'] + 1, form[k]['len']) for k in form_order] + return self.dialog.form( + text="", + height=0, width=0, form_height=0, + fields=fields_values if fields_values else fields, + title="Paramètres TLS d'un certificat de la machine %s" % certificat.machine()['host'][0], + backtitle="Gestion des certificats des machines du Crans") + + (code, output) = box(values) + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + else: + values = dict(zip([form[k]['ldap_name'] for k in form_order], output)) + fields_values = [("%s : " % k, values[form[k]['ldap_name']], form[k]['len'] + 1, form[k]['len']) for k in form_order] + self_cont=TailCall(self.certificat_tlsa, certificat=certificat, cont=cont, values=fields_values) + try: + if not values['certificatUsage'] in ['0', '1', '2', '3']: + raise ValueError("""Type de certificat invalide : +les valeurs valident sont : + * 0 pour du CA pinning + (le certificat doit être une autorité de certification valide) + * 1 pour du certificat pinning + (le certificat doit déjà être validé par les navigateur) + * 2 pour ajouter un CA + (pour les autorité de certification autosigné) + * 3 pour les certificats autosigné""" +) + if not values['matchingType'] in ['0', '1', '2']: + raise ValueError("""Type de correspondance invalide : +les valeurs valident sont : + * 0 le certificat sera mis entièrement dans le dns + * 1 le sha256 du certificat sera mis dans le dns + * 2 le sha512 du certificat sera mis dans le dns""" +) + with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: + if "TLSACert" in certificat['objectClass']: + certificat['certificatUsage'] = unicode(values['certificatUsage']) + certificat['matchingType'] = unicode(values['matchingType']) + else: + certificat.tlsa(values['certificatUsage'], values['matchingType']) + certificat['portTCPin'] = [unicode(s, 'utf-8') for s in values['portTCPin'].split(separateur) if s] + certificat['portUDPin'] = [unicode(s, 'utf-8') for s in values['portUDPin'].split(separateur) if s] + certificat.save() + raise Continue(cont(certificat=certificat)) + except Continue: + raise + except Exception as e: + self.dialog.msgbox("%s" % (unicode_of_Error(e)), title="Erreur rencontrée", width=73, height=10) + raise Continue(self_cont) + + def create_certificat(self, machine, cont): + """Permet d'ajouter un certificat à une machine à partir du PEM du certificat""" + # input multiline en utilisant un editbox + def box(): + fp, path = tempfile.mkstemp() + os.close(fp) + cmd = ['--editbox', path, "0", "0"] + (code, output) = self.dialog._perform(*(cmd,), + no_mouse=True, + backtitle="Appuyez sur CTRL+MAJ+V pour coller", + title="Création d'un certificat, entrez le PEM du certificat") + os.remove(path) + if code == self.dialog.DIALOG_OK: + return code, output + else: + return code, None + + (code, pem) = box() + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + else: + try: + with self.conn.newCertificat(machine.dn, {}) as certificat: + certificat['certificat'] = unicode(pem, 'utf-8') + certificat.create() + raise Continue(cont(certificat=certificat, machine=certificat.machine())) + except Continue: + raise + except Exception as e: + self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) + self_cont = TailCall(self.create_certificat, machine=machine, cont=cont) + raise Continue(self_cont) + + def delete_certificat(self, certificat, cont): + """Supprime un certificat""" + if self.confirm_item(item=certificat, title="Voulez vous vraiement supprimer le certificat ?"): + try: + with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat: + certificat.delete() + self.dialog.msgbox("Le certificat a bien été supprimé", title="Suppression d'un certificat") + raise Continue(cont(certificat=None, machine=certificat.machine(refresh=True))) + except Continue: + raise + except Exception as e: + self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) + raise Continue(cont) + else: + raise Continue(cont(certificat=certificat)) + + def modif_machine_certificat(self, machine, cont, tag=None, certificat=None): + """Permet l'édition d'un certificat d'une machine""" + self_cont = TailCall(self.modif_machine_certificat, machine=machine, cont=cont, certificat=certificat) + if certificat is None: + certificat_index = self.edit_certificat_select(machine=machine, title="Modification des certificats de %s" % machine['host'][0], cont=cont) + if certificat_index == 'new': + raise Continue(TailCall(self.create_certificat, machine=machine, cont=self_cont)) + certificat = machine.certificats()[certificat_index] + menu = { + 'Hostname' : {'text':"Noms d'hôte utilisant le certificat", "attribut":attributs.hostCert}, + 'TLSA' : {'text':"Paramètres pour les champs dns TLSA", "callback":self.certificat_tlsa}, + 'Autre': {'text' : "Modifier les attribut booléen comme revocked", "callback":self.modif_certificat_boolean}, + 'Supprimer' : {'text' : "Supprimer le certificat", "callback":self.delete_certificat}, + } + menu_order = ['Hostname', 'TLSA', 'Autre', 'Supprimer'] + def box(default_item=None): + return self.dialog.menu( + "Paramètres pour le certificat N°0x%X émis par %s, valable du %s au %s" % ( + int(str(certificat['serialNumber'][0])), + certificat['issuerCN'][0], + time.strftime("%d/%m/%Y", time.localtime(int(certificat['start'][0]))), + time.strftime("%d/%m/%Y", time.localtime(int(certificat['end'][0]))) + ), + width=0, + height=0, + menu_height=0, + item_help=0, + default_item=str(default_item), + title="Modification des certificats de %s" % certificat.machine()['host'][0], + scrollbar=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=[(key, menu[key]['text']) for key in menu_order]) + (code, tag) = box(tag) + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + if certificat is None: + raise Continue(cont(machine=machine)) + else: + raise Continue(self_cont(certificat=None)) + elif not tag in menu_order: + raise Continue(self_cont(certificat=certificat)) + else: + if 'callback' in menu[tag]: + raise Continue(TailCall(menu[tag]['callback'], certificat=certificat, cont=self_cont(certificat=certificat, tag=tag))) + elif 'attribut' in menu[tag]: + raise Continue(TailCall(self.modif_certificat_attributs, certificat=certificat, cont=self_cont(certificat=certificat, tag=tag), attr=menu[tag]['attribut'].ldap_name)) + else: + raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) + + @tailcaller + def edit_certificat_select(self, machine, title, cont): + """Permet de choisir un certificat existant ou nouveau d'une machine""" + def box(default_item=None): + index=0 + choices = [('new', 'Ajouter un nouveau certificat')] + for cert in machine.certificats(): + choices.append((str(index), "Emit par %s pour %s du %s au %s" % (cert['issuerCN'][0], ', '.join(str(cn) for cn in cert['hostCert']), time.strftime("%d/%m/%Y", time.localtime(int(cert['start'][0]))), time.strftime("%d/%m/%Y", time.localtime(int(cert['end'][0])))))) + index+=1 + return self.dialog.menu( + "Modifier ou ajouter un certificat ?", + width=0, + height=0, + menu_height=0, + item_help=0, + title="Modification des certificats de %s" % machine['host'][0], + scrollbar=True, + default_item=str(default_item), + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=choices) + + (code, tag) = box() + retry_cont = TailCall(self.edit_certificat_select, machine=machine, title=title, cont=cont) + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont) + elif code in [self.dialog.DIALOG_OK]: + if tag == 'new': + return tag + else: + return int(tag) + else: + self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10) + raise Continue(retry_cont) return cont - def modif_machine_alias(self, machine, cont): - self.dialog.msgbox("todo", width=0, height=0) - return cont + def modif_machine_attributs(self, machine, attr, cont): + """Juste un raccourci vers edit_attributs spécifique aux machines""" + return self.edit_attributs(obj=machine, update_obj='machine', attr=attr, title="Modification de la machine %s" % machine['host'][0], cont=cont) - def modif_machine_exemption(self, machine, cont): - self.dialog.msgbox("todo", width=0, height=0) - return cont + def modif_certificat_attributs(self, certificat, attr, cont): + """Juste un raccourci vers edit_attributs spécifique aux certificats""" + return self.edit_attributs(obj=certificat, update_obj='certificat', attr=attr, title="Modification d'un certificat de la machine %s" % certificat.machine()['host'][0], cont=cont) - def modif_machine_remarque(self, machine, cont): - self.dialog.msgbox("todo", width=0, height=0) - return cont + def modif_machine_boolean(self, machine, cont): + """Juste un raccourci vers edit_boolean_attributs spécifique aux machines""" + a = attributs + attribs = [a.dnsIpv6] + return self.edit_boolean_attributs( + obj=machine, + attribs=attribs, + title="Édition des attributs booléen de la machine %s" % machine['host'][0], + update_obj='machine', + cont=cont) + + def modif_certificat_boolean(self, certificat, cont): + """Juste un raccourci vers edit_boolean_attributs spécifique aux certificats""" + a = attributs + attribs = [a.revocked] + return self.edit_boolean_attributs( + obj=certificat, + attribs=attribs, + title="Édition des attributs booléen d'un certificat de la machine %s" % certificat.machine()['host'][0], + update_obj='certificat', + cont=cont) - @tailCaller def modif_machine(self, cont, machine=None, tag=None): + """ + Permet d'éditer une machine. Si fournie en paramètre on éditer en place, + sinon, on en cherche une dans la base ldap + """ if machine is None: machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour modification", cont=cont) + a = attributs menu = { - 'Information' : {'text' : "Modifier le nom de machine, l'IP, adresse MAC", "callback":self.modif_machine_information}, + 'Information' : {'text' : "Modifier le nom de machine, l'IP, adresse MAC", "callback":self.machine_information}, + 'Autre' : {'text' : "Modifier les attribut booléen comme dsnIpv6", "callback":self.modif_machine_boolean}, 'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_machine_blacklist}, - 'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'callback':self.modif_machine_alias}, - 'Exemption' : {'text':"Modifier la liste d'exemption d'upload de la machine", 'callback':self.modif_machine_exemption}, - 'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'callback':self.modif_machine_remarque}, + 'Certificat' : {'text': 'Modifier les certificats de la machine', 'callback':self.modif_machine_certificat}, + 'Exemption' : {'text':"Modifier la liste d'exemption d'upload de la machine", 'attribut':attributs.exempt}, + 'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'attribut':attributs.hostAlias}, + 'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info}, + 'SshKey' : {'text':'Ajouter ou supprimer une clef ssh pour la machine', 'attribut':attributs.sshFingerprint}, } - menu_order = ['Information', 'Blackliste', 'Alias', 'Exemption', 'Remarques'] + menu_order = ['Information', 'Blackliste', 'Certificat', 'Alias', 'Exemption', 'SshKey', 'Autre', 'Remarques'] def box(default_item=None): return self.dialog.menu( "Que souhaitez vous modifier ?", @@ -376,59 +1103,119 @@ class GestCrans(object): choices=[(key, menu[key]['text']) for key in menu_order]) (code, tag) = box(tag) if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): - return cont(machine=machine) + raise Continue(cont(machine=machine)) elif not tag in menu_order: - return TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag) + raise Continue(TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag)) else: - return TailCall(menu[tag]['callback'], machine=machine, cont=TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag)) + cont_ret = TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag) + if 'callback' in menu[tag]: + raise Continue(TailCall(menu[tag]['callback'], machine=machine, cont=cont_ret)) + elif 'attribut' in menu[tag]: + raise Continue(TailCall(self.modif_machine_attributs, machine=machine, cont=cont_ret, attr=menu[tag]['attribut'].ldap_name)) + else: + raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag) - @tailCaller - def create_machine_adherent(self, cont, adherent=None): + def create_machine_adherent(self, cont, adherent=None, tag=None): + """ + Permet d'ajouter une machine à un adhérent. + On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment) + """ if adherent is None: adherent = self.select(["adherent"], "Recherche d'un adhérent pour lui ajouter une machine", cont=cont) - self.dialog.msgbox("todo", width=0, height=0) - return cont(proprio=adherent) + menu = { + 'Fixe' : {'text' : "Machine filaire", 'objectClass':'machineFixe', 'realm':'adherents'}, + 'Wifi' : {'text': 'Machine sans fil', 'objectClass':'machineWifi', 'realm':'wifi-adh'}, + } + menu_order = ['Fixe', 'Wifi'] + def box(default_item=None): + return self.dialog.menu( + "Type de Machine ?", + width=0, + height=0, + menu_height=0, + item_help=0, + default_item=str(default_item), + title="Création de machines", + scrollbar=True, + cancel_label="Retour", + backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, + choices=[(key, menu[key]['text']) for key in menu_order]) + (code, tag) = box(tag) + if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): + raise Continue(cont(proprio=adherent)) + elif not tag in menu_order: + raise Continue(TailCall(self.create_machine_adherent, cont=cont, adherent=adherent, tag=tag)) + else: + return self.machine_information( + cont=cont(proprio=adherent), + machine=None, + objectClass=menu[tag]['objectClass'], + parent=adherent, + realm=menu[tag]['realm'] + ) - @tailCaller - def delete_machine(self, cont): - machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour supression", cont=cont) - self.dialog.msgbox("todo", width=0, height=0) + def delete_machine(self, cont, machine=None): + """Permet la suppression d'une machine de la base ldap""" + if machine is None: + machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour supression", cont=cont) + if self.confirm_item(item=machine, title="Voulez vous vraiement supprimer la machine ?", defaultno=True): + try: + with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine: + machine.delete() + self.dialog.msgbox("La machine a bien été supprimée", title="Suppression d'une machine") + raise Continue(cont(machine=None)) + except Continue: + raise + except Exception as e: + self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10) + raise Continue(cont) + else: + raise Continue(cont) return cont - @tailCaller + def modif_adherent(self, cont, adherent=None): + if adherent is None: + adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont) + self.dialog.msgbox("todo", width=0, height=0) + return cont(proprio=adherent) + + def create_adherent(self, cont): + self.dialog.msgbox("todo", width=0, height=0) + return cont + + def delete_adherent(self, cont): + adherent = self.select(["adherent"], "Recherche d'un adhérent pour supression", cont=cont) + self.dialog.msgbox("todo", width=0, height=0) + return cont + def create_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont - - @tailCaller def delete_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont - @tailCaller def modif_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont - @tailCaller def create_machine_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont - @tailCaller def create_machine_crans(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont - @tailCaller def create_borne(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont - @tailCaller + @tailcaller def menu_principal(self, tag=None, machine=None, proprio=None): + """Menu principal de l'application affiché au lancement""" menu = { 'aA' : {'text':"Inscrire un nouvel adhérent", 'callback': self.create_adherent}, 'mA' : {'text':"Modifier l'inscription d'un adhérent", 'callback': self.modif_adherent, 'help':"Changer la chambre, la remarque, la section, la carte d'étudiant ou précâbler."}, @@ -442,11 +1229,11 @@ class GestCrans(object): 'dC' : {'text':"Détruire un club", 'callback': self.delete_club}, 'aKM': {'text':"Ajouter une machine à l'association", 'callback': self.create_machine_crans}, 'aKB': {'text':"Ajouter une borne wifi", 'callback': self.create_borne}, - '' : {'text':"---------------------------------------",'callback': None}, + '' : {'text':"---------------------------------------",'callback': None}, } ### Les clef qui n'existe pas sont toute renvoyé sur la clef '' menu_order = ["aA", "mA", "aMA", "dA", "", "mM", "dM", " ", "aC", "mC", "aMC", "dC", " ", "aKM", "aKB"] - + if machine and not proprio: proprio = machine.proprio() if machine or proprio: @@ -493,10 +1280,10 @@ class GestCrans(object): (code, tag) = box(tag) callback = menu.get(tag, menu[''])['callback'] if handle_exit_code(self.dialog, code) and callback: - return TailCall(callback, cont=TailCall(self.menu_principal, tag, machine=machine, proprio=proprio)) + return TailCall(callback, cont=TailCall(self.menu_principal, tag=tag, machine=machine, proprio=proprio)) else: - return TailCall(self.menu_principal, tag, proprio=proprio, machine=machine) + return TailCall(self.menu_principal, tag=tag, proprio=proprio, machine=machine) if __name__ == '__main__': - GestCrans() + GestCrans().menu_principal() os.system('clear')