[gest_crans_lc] (par Valentin) Je préfère commiter ça pour qu'on ait une trace, quitte à revert.

This commit is contained in:
Pierre-Elliott Bécue 2014-03-10 10:09:10 +01:00
parent ffe5f8ddf3
commit 82804b7924

View file

@ -12,6 +12,7 @@ Licence : GPLv3
import os import os
import sys import sys
import signal import signal
import collections
from pythondialog import Dialog from pythondialog import Dialog
from gestion.affich_tools import get_screen_size, coul from gestion.affich_tools import get_screen_size, coul
@ -19,6 +20,49 @@ from gestion.affich_tools import get_screen_size, coul
import lc_ldap.shortcuts import lc_ldap.shortcuts
import lc_ldap.printing as printing import lc_ldap.printing as printing
# Implémentation "à la main" de la tail récursion en python
# voir http://kylem.net/programming/tailcall.html
# je trouve ça assez sioux
class TailCaller(object) :
def __init__(self, f) :
self.f = f
def __call__(self, *args, **kwargs) :
ret = self.f(*args, **kwargs)
while isinstance(ret, TailCall) :
ret = ret.handle()
return ret
def tailCaller(f):
f.tailCaller = True
return f
class TailCall(object) :
def __init__(self, call, *args, **kwargs) :
if isinstance(call, TailCall):
call.update(**kwargs)
kwargs = call.kwargs
args = call.args + args
call = call.call
self.call = call
self.args = args
self.kwargs = kwargs
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)
def handle_exit_code(d, code): def handle_exit_code(d, code):
if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
if code == d.DIALOG_CANCEL: if code == d.DIALOG_CANCEL:
@ -37,8 +81,35 @@ def handle_exit_code(d, code):
return True # code est d.DIALOG_OK return True # code est d.DIALOG_OK
class GestCrans(object):
def search(dialog, objectClassS, title, values={}): def __getattribute__(self, attr):
ret = super(GestCrans, self).__getattribute__(attr)
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)
# On initialise le moteur de rendu en spécifiant qu'on va faire du dialog
printing.template(dialog=True)
# On ouvre une connexion lc_ldap
self.conn = lc_ldap.shortcuts.lc_ldap_admin()
# On vérifie que l'utilisateur système existe dans ldap (pour la gestion des droits)
luser=self.conn.search(u'(&(uid=%s)(objectClass=cransAccount))' % self.conn.current_login)
if not luser:
sys.stderr.write("L'utilisateur %s n'existe pas dans la base de donnée" % self.conn.current_login)
sys.exit(1)
self.conn.droits = [str(d) for d in luser[0]['droits']]
self.conn.dn = luser[0].dn
self.dialog = Dialog()
self.menu_principal()
@tailCaller
def search(self, objectClassS, title, values={}, cont=None):
""" """
Rechercher des adhérents ou des machines dans la base ldap 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) retourne le tuple (code de retour dialog, valeurs entrée par l'utilisateur, liste d'objets trouvés)
@ -75,15 +146,16 @@ def search(dialog, objectClassS, title, values={}):
cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']]) cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']])
cmd.extend(["Les champs vides sont ignorés.", '7', '1', "", '0', '0', '0', '0']) cmd.extend(["Les champs vides sont ignorés.", '7', '1', "", '0', '0', '0', '0'])
# On utilise quand même la fonction de la bibliothèques pour passer les arguments # On utilise quand même la fonction de la bibliothèques pour passer les arguments
(code, output) = dialog._perform(*(cmd,), title=title, backtitle="Entrez vos paramètres de recherche") (code, output) = self.dialog._perform(*(cmd,), title=title, backtitle="Entrez vos paramètres de recherche")
if output: if output:
return (code, output.split('\n')[:-1]) return (code, output.split('\n')[:-1])
else: # empty selection else: # empty selection
return (code, []) return (code, [])
(code, dialog_values) = box() (code, dialog_values) = box()
if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): # Si il a appuyé sur annuler ou sur escape, on saute sur la continuation
return code, values, [] if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
return cont
else: else:
# Transformation de la liste des valeures entrée en dictionnnaire # Transformation de la liste des valeures entrée en dictionnnaire
dialog_values = dict(zip(select_adherent + select_machine, dialog_values)) dialog_values = dict(zip(select_adherent + select_machine, dialog_values))
@ -104,8 +176,8 @@ def search(dialog, objectClassS, title, values={}):
filter_machine=u"(&%s)" % "".join(filter_machine) filter_machine=u"(&%s)" % "".join(filter_machine)
# Récupération des adhérents et des machines # Récupération des adhérents et des machines
adherents=conn.search(filter_adherent) if filter_adherent else [] adherents=self.conn.search(filter_adherent) if filter_adherent else []
machines=conn.search(filter_machine) if filter_machine else [] machines=self.conn.search(filter_machine) if filter_machine else []
# Filtrage des machines en fonction des adhérents # Filtrage des machines en fonction des adhérents
if filter_adherent: if filter_adherent:
@ -134,172 +206,297 @@ def search(dialog, objectClassS, title, values={}):
adherents_f = adherents adherents_f = adherents
# On filtre sur les objectClassS # On filtre sur les objectClassS
return code, ldap_values, [ o for objectClass in objectClassS for o in machines_f+adherents_f if objectClass in o['objectClass'] ] return ldap_values, [ o for objectClass in objectClassS for o in machines_f+adherents_f if objectClass in o['objectClass'] ]
def select_one(dialog, items, default_item=None): # 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""" """Fait selectionner un item parmis une liste d'items à l'utisateur"""
### TODO afficher correctement les objets dans items
choices=[] choices=[]
olist={}
count = 0 count = 0
# On sépare les item d'items en fonction de leur type
for o in items:
olist[o.__class__] = olist.get(o.__class__, []) + [o]
default_tag = items.index(default_item) if default_item in items else default_item default_tag = items.index(default_item) if default_item in items else default_item
for i in items:
choices.append((str(count), str(i), 1 if default_tag == count else 0)) # On se débrouille pour faire corresponde l'ordre d'affichache des objets
count+=1 # et leur ordre dans la liste items. On donne la largeur de l'affichage à la main
tag='' # pour prendre en compte la largeur du widget dialog
while not tag: items=[]
items_id = {}
(line, col) = get_screen_size() (line, col) = get_screen_size()
(code, tag) = dialog.radiolist( for c in olist.keys():
"Choisir", items.extend(olist[c])
choices=choices, items_s = printing.sprint_list(olist[c], col-20).encode('utf-8').split('\n')
choices.append(("", str(items_s[0])))
choices.append(("", str(items_s[1])))
for i in items_s[2:]:
choices.append((str(count), str(i)))
count+=1
# On laisse une ligne vide pour séparer les listes d'objets de type différent
choices.append(("", ""))
# On supprime la dernière ligne qui est vide
del(choices[-1])
(code, tag) = self.dialog.menu(
"Que souhaitez vous faire ?",
width=0,
height=0,
menu_height=0,
item_help=0,
default_item=str(default_tag), default_item=str(default_tag),
width=col-4, title=title,
height=line-3, scrollbar=True,
list_height=line-3, choices=choices,
scrollbar=True) colors=True)
if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC):
return code, None
if not tag:
dialog.msgbox("Merci de choisir l'un des item de la liste ou d'annuler", title="Sélection", width=0, height=0)
return code, items[int(tag)]
def select_confirm(dialog, item, title): # Si l'utilisateur à annulé, on coninue avec la continuation
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
return 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)
# Sinon on retourne l'item choisis
elif self.select_confirm(items[int(tag)], title):
return items[int(tag)]
else:
return cont
def select_confirm(self, item, title):
"""Affiche un item et demande si c'est bien celui là que l'on veux (supprimer, éditer, créer,...)""" """Affiche un item et demande si c'est bien celui là que l'on veux (supprimer, éditer, créer,...)"""
return dialog.yesno(printing.sprint(item), no_collapse=True, colors=True, title=title, width=0, height=0, backtitle="Appuyez sur MAJ pour selectionner du texte") == dialog.DIALOG_OK return self.dialog.yesno(
printing.sprint(item),
no_collapse=True,
colors=True,
title=title,
width=0, height=0,
backtitle="Appuyez sur MAJ pour selectionner du texte"
) == self.dialog.DIALOG_OK
def select(dialog, objectClassS, title, values={}): @tailCaller
def select(self, objectClassS, title, values={}, cont=None):
"""Permet de choisir un objet adhérent ou machine dans la base ldap""" """Permet de choisir un objet adhérent ou machine dans la base ldap"""
while True:
try: try:
# On fait effectuer une recherche à l'utilisateur # On fait effectuer une recherche à l'utilisateur
code, values, items = search(dialog, objectClassS, title, values) values, items = self.search(objectClassS, title, values, cont=cont)
# Si il a appuyé sur annuler ou sur escape, on annule
if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC):
return code, None
# S'il n'y a pas de résultas, on recommence # S'il n'y a pas de résultas, on recommence
elif not items: if not items:
dialog.msgbox("Aucun Résultat", title="Recherche", width=0, height=0) self.dialog.msgbox("Aucun Résultat", title="Recherche", width=0, height=0)
return TailCall(self.select, objectClassS, title, values, cont=cont)
# S'il y a plusieurs résultats # S'il y a plusieurs résultats
elif len(items)>1: elif len(items)>1:
item = None # On en fait choisir un, si c'est une continuation qui est renvoyé, elle est gérée par select
while True: return self.select_one(items, title, cont=TailCall(self.select, objectClassS, title, values, cont))
# On en fait choisir un
code, item = select_one(dialog, items, item)
# Si il à appuyer sur annuler ou sur escape, on recommence la recherche
if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC):
break
# Sinon, on fait confirmer son choix à l'utilisateur
elif select_confirm(dialog, item, title):
return code, item
# S'il y a exactement 1 résultat à la recherche, on fait confirmer son choix à l'utilisateur # S'il y a exactement 1 résultat à la recherche, on fait confirmer son choix à l'utilisateur
elif len(items) == 1 and select_confirm(dialog, items[0], title): elif len(items) == 1:
return code, items[0] item=items[0]
# On fait confirmer son choix à l'utilisateur
if self.select_confirm(item, title):
return item
else:
return TailCall(self.select, objectClassS, title, values, cont=cont)
except Exception as e: except Exception as e:
dialog.msgbox("%r" % e, title="Erreur rencontrée", width=0, height=0) self.dialog.msgbox("%r" % e, title="Erreur rencontrée", width=0, height=0)
return TailCall(self.select, objectClassS, title, values, cont=cont)
def modif_adherent(dialog): @tailCaller
code, adherent = select(dialog, ["adherent"], "Recherche d'un adhérent") def modif_adherent(self, cont, adherent=None):
if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): if adherent is None:
return code adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont)
dialog.msgbox("todo", width=0, height=0) self.dialog.msgbox("todo", width=0, height=0)
return cont(proprio=adherent)
def create_adherent(dialog): @tailCaller
dialog.msgbox("todo", width=0, height=0) def create_adherent(self, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def delete_adherent(dialog): @tailCaller
code, adherent = select(dialog, ["adherent"], "Recherche d'un adhérent") def delete_adherent(self, cont):
if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): adherent = self.select(["adherent"], "Recherche d'un adhérent pour supression", cont=cont)
return code self.dialog.msgbox("todo", width=0, height=0)
dialog.msgbox("todo", width=0, height=0) return cont
def modif_machine(dialog): def modif_machine_information(self, machine, cont):
code, machine = select(dialog, ["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine") self.dialog.msgbox("todo", width=0, height=0)
if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): (code, tag) = self.dialog.form(
return code text="",
dialog.msgbox("todo", width=0, height=0) 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
def create_machine_adherent(dialog): def modif_machine_blacklist(self, machine, cont):
dialog.msgbox("todo", width=0, height=0) self.dialog.msgbox("todo", width=0, height=0)
return cont
def delete_machine(dialog): def modif_machine_alias(self, machine, cont):
code, machine = select(dialog, ["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine") self.dialog.msgbox("todo", width=0, height=0)
if code in (dialog.DIALOG_CANCEL, dialog.DIALOG_ESC): return cont
return code
dialog.msgbox("todo", width=0, height=0)
def modif_machine_exemption(self, machine, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def create_club(dialog): def modif_machine_remarque(self, machine, cont):
dialog.msgbox("todo", width=0, height=0) self.dialog.msgbox("todo", width=0, height=0)
return cont
@tailCaller
def delete_club(dialog): def modif_machine(self, cont, machine=None, tag=None):
dialog.msgbox("todo", width=0, height=0) if machine is None:
machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour modification", cont=cont)
def modif_club(dialog):
dialog.msgbox("todo", width=0, height=0)
def create_machine_club(dialog):
dialog.msgbox("todo", width=0, height=0)
def create_machine_crans(dialog):
dialog.msgbox("todo", width=0, height=0)
def create_borne(dialog):
dialog.msgbox("todo", width=0, height=0)
def menu_principal(dialog):
menu = { menu = {
'aA' : {'text':"Inscrire un nouvel adhérent", 'callback': create_adherent}, 'Information' : {'text' : "Modifier le nom de machine, l'IP, adresse MAC", "callback":self.modif_machine_information},
'mA' : {'text':"Modifier l'inscription d'un adhérent", 'callback': modif_adherent, 'help':"Changer la chambre, la remarque, la section, la carte d'étudiant ou précâbler."}, 'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_machine_blacklist},
'aMA': {'text':"Ajouter une machine à un adhérent", 'callback': create_machine_adherent}, 'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'callback':self.modif_machine_alias},
'dA' : {'text':"Détruire un adhérent", 'callback': delete_adherent, 'help':"Suppression de l'adhérent ainsi que de ses machines"}, 'Exemption' : {'text':"Modifier la liste d'exemption d'upload de la machine", 'callback':self.modif_machine_exemption},
'mM' : {'text':"Modifier une machine existante", 'callback': modif_machine, 'help':"Changer le nom ou la MAC d'une machine."}, 'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'callback':self.modif_machine_remarque},
'dM' : {'text':"Détruire une machine", 'callback': delete_machine}, }
'aC' : {'text':"Inscrire un nouveau club", 'callback': create_club}, menu_order = ['Information', 'Blackliste', 'Alias', 'Exemption', 'Remarques']
'mC' : {'text':"Modifier un club", 'callback': modif_club}, def box(default_item=None):
'aMC': {'text':"Ajouter une machine à un club", 'callback': create_machine_club}, return self.dialog.menu(
'dC' : {'text':"Détruire un club", 'callback': delete_club}, "Que souhaitez vous modifier ?",
'aKM': {'text':"Ajouter une machine à l'association", 'callback': create_machine_crans}, width=0,
'aKB': {'text':"Ajouter une borne wifi", 'callback': create_borne}, height=0,
'' : {'text':"---------------------------------------",'callback': lambda x: x}, menu_height=0,
item_help=0,
default_item=str(default_item),
title="Modification de %s" % 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):
return cont(machine=machine)
elif not tag in menu_order:
return 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))
@tailCaller
def create_machine_adherent(self, cont, adherent=None):
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)
@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)
return cont
@tailCaller
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
def menu_principal(self, tag=None, machine=None, proprio=None):
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."},
'aMA': {'text':"Ajouter une machine à un adhérent", 'callback': self.create_machine_adherent},
'dA' : {'text':"Détruire un adhérent", 'callback': self.delete_adherent, 'help':"Suppression de l'adhérent ainsi que de ses machines"},
'mM' : {'text':"Modifier une machine existante", 'callback': self.modif_machine, 'help':"Changer le nom ou la MAC d'une machine."},
'dM' : {'text':"Détruire une machine", 'callback': self.delete_machine},
'aC' : {'text':"Inscrire un nouveau club", 'callback': self.create_club},
'mC' : {'text':"Modifier un club", 'callback': self.modif_club},
'aMC': {'text':"Ajouter une machine à un club", 'callback': self.create_machine_club},
'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},
} }
### Les clef qui n'existe pas sont toute renvoyé sur la clef '' ### Les clef qui n'existe pas sont toute renvoyé sur la clef ''
medu_order = ["aA", "mA", "aMA", "dA", "", "mM", "dM", " ", "aC", "mC", "aMC", "dC", " ", "aKM", "aKB"] 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:
menu_order = [' '] + menu_order
if machine:
menu_machine = {
'mMc' : {
'text':"Modifier la machine %s" % machine['host'][0],
'callback': TailCall(self.modif_machine, machine=machine),
'help':"Changer le nom ou la MAC d'une machine."
},
}
menu_machine_order = ['mMc']
menu.update(menu_machine)
menu_order = menu_machine_order + menu_order
if proprio:
menu_proprio = {
'mAc' : {
'text':"Modifier l'inscription de %s" % proprio.get("cn", proprio["nom"])[0],
'callback': TailCall(self.modif_adherent, adherent=proprio)
},
'aMc' : {
'text':"Ajouter une machine à %s" % proprio.get("cn", proprio["nom"])[0],
'callback': TailCall(self.create_machine_adherent, adherent=proprio)
},
}
menu_proprio_order = ['mAc', 'aMc']
menu.update(menu_proprio)
menu_order = menu_proprio_order + menu_order
def box(default_item=None): def box(default_item=None):
return dialog.menu( return self.dialog.menu(
"Que souhaitez vous faire ?", "Que souhaitez vous faire ?",
width=55, width=0,
height=0, height=0,
menu_height=15, menu_height=0,
item_help=1, item_help=1,
default_item=str(default_item), default_item=str(default_item),
title="Menu principal", title="Menu principal",
scrollbar=True, scrollbar=True,
cancel_label="Quitter", cancel_label="Quitter",
backtitle=u"Vous êtes connecté en tant que %s" % conn.current_login, backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=[(key, menu.get(key, menu[''])['text'], menu.get(key, menu['']).get('help', "")) for key in medu_order]) choices=[(key, menu.get(key, menu[''])['text'], menu.get(key, menu['']).get('help', "")) for key in menu_order])
tag=None
while True:
(code, tag) = box(tag) (code, tag) = box(tag)
if handle_exit_code(dialog, code): callback = menu.get(tag, menu[''])['callback']
menu.get(tag, menu[''])['callback'](dialog) if handle_exit_code(self.dialog, code) and callback:
return TailCall(callback, cont=TailCall(self.menu_principal, tag, machine=machine, proprio=proprio))
else:
return TailCall(self.menu_principal, tag, proprio=proprio, machine=machine)
if __name__ == '__main__': if __name__ == '__main__':
signal.signal(signal.SIGINT, signal.SIG_IGN) GestCrans()
# On initialise le moteur de rendu en spécifiant qu'on va faire du dialog
printing.template(dialog=True)
# On ouvre une connexion lc_ldap
conn = lc_ldap.shortcuts.lc_ldap_admin()
# On vérifie que l'utilisateur système existe dans ldap (pour la gestion des droits)
luser=conn.search(u"uid=%s" % conn.current_login)
if not luser:
sys.stderr.write("L'utilisateur %s n'existe pas dans la base de donnée" % conn.current_login)
sys.exit(1)
conn.droits = [str(d) for d in luser[0]['droits']]
conn.dn = luser[0].dn
dialog = Dialog()
menu_principal(dialog)
os.system('clear') os.system('clear')