scripts/gestion/gest_crans_lc.py

502 lines
23 KiB
Python
Executable file

#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
u"""
Interface utilisateur du système de gestion des machines
et adhérents du crans
Copyright (C) Valentin Samir
Licence : GPLv3
"""
import os
import sys
import signal
import collections
from pythondialog import Dialog
from gestion.affich_tools import get_screen_size, coul
import lc_ldap.shortcuts
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):
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" \
"Voulez vous quitter le programme ?"
os.system('clear')
sys.exit(0)
else:
msg = "Vous avez appuyer sur ESC dans la dernière fenêtre de dialogue.\n\n" \
"Voulez vous quitter le programme ?"
if d.yesno(msg, width=60) == d.DIALOG_OK:
os.system('clear')
sys.exit(0)
return False
else:
return True # code est d.DIALOG_OK
class GestCrans(object):
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
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 :
* 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
"""
select_dict = {
#label attribut ldap search for substring param dialog: line col valeur input-line icol len max-chars
'Nom' : {'ldap':'nom', 'sub':True, 'params' : [ 1, 1, values.get('nom', ""), 1, 13, 20, 20]},
'Prenom' : {'ldap':'prenom', 'sub':True, 'params' : [ 2, 1, values.get('prenom', ""), 2, 13, 20, 20]},
'Téléphone' : {'ldap':'tel', 'sub':True, 'params' : [ 3, 1, values.get('tel', ""), 3, 13, 10, 00]},
'Chambre' : {'ldap':'chbre','sub':True, 'params' : [ 4, 1, values.get('chbre',""), 4, 13, 05, 00]},
'aid' : {'ldap' : 'aid', 'sub':False, 'params' : [ 5, 1, values.get('aid',""), 5, 13, 05, 05]},
'mail' : {'ldap' : 'mail', 'sub':True, 'params' : [ 6, 1, values.get('mail',""), 6, 13, 20, 00]},
# seconde colone
'Machine' : {'ldap' : '*', 'sub':True, 'params' : [1, 35, "", 1, 43, 0, 0]},
'Host' : {'ldap' : 'host', 'sub':True, 'params' : [2, 37, values.get('host',""), 2, 43, 17, 17]},
'Mac' : {'ldap' : 'macAddress', 'sub':False, 'params' : [3, 37, values.get('macAddress',""), 3, 43, 17, 17]},
'IP' : {'ldap' : 'ipHostNumber', 'sub':False,'params' : [4, 37, values.get('ipHostNumber',""), 4, 43, 15, 15]},
'mid' : {'ldap' : 'mid', 'sub':False, 'params' : [5, 37, values.get('mid',""), 5, 43, 5, 5]},
}
# On a besoin de l'ordre pour récupérer les valeurs ensuite
select_adherent = ['Nom', 'Prenom', 'Téléphone', 'Chambre', 'aid', 'mail']
select_machine = ['Host', 'Mac', 'IP', 'mid']
def box():
# On met les argument à dialog à la main ici, sinon, c'est difficile de choisir comment mettre une seconde colone
cmd = ["--form", "Entrez vos paramètres de recherche", '0', '0', '0']
for key in select_adherent:
cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']])
cmd.extend(['Machine :'] + [str(e) for e in select_dict['Machine']['params']])
for key in select_machine:
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'])
# On utilise quand même la fonction de la bibliothèques pour passer les arguments
(code, output) = self.dialog._perform(*(cmd,), title=title, backtitle="Entrez vos paramètres de recherche")
if output:
return (code, output.split('\n')[:-1])
else: # empty selection
return (code, [])
(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
else:
# Transformation de la liste des valeures entrée en dictionnnaire
dialog_values = dict(zip(select_adherent + select_machine, dialog_values))
ldap_values = dict([(select_dict[key]['ldap'], value) for key, value in dialog_values.items()])
# Construction des filtres ldap pour les adhérents et les machines
filter_adherent = []
filter_machine = []
for (key, value) in dialog_values.items():
if value:
if key in select_adherent:
filter_adherent.append((u"(%s=*%s*)" if select_dict[key]['sub'] else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8')))
elif key in select_machine:
filter_machine.append((u"(%s=*%s*)" if select_dict[key]['sub'] else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8')))
if filter_adherent:
filter_adherent=u"(&%s)" % "".join(filter_adherent)
if filter_machine:
filter_machine=u"(&%s)" % "".join(filter_machine)
# Récupération des adhérents et des machines
adherents=self.conn.search(filter_adherent) if filter_adherent else []
machines=self.conn.search(filter_machine) if filter_machine else []
# Filtrage des machines en fonction des adhérents
if filter_adherent:
if filter_machine:
# Si on filtre sur des adhérent et des machines, on calcule l'intersection
adherents_dn = set([a.dn for a in adherents])
machines_f = [m for m in machines if m.parent_dn in adherents_dn]
else:
# Sinon on filtre seulement sur les adhérents, récupère les machines des adhérents trouvés
machines_f = [m for a in adherents for m in a.machines()]
else:
# Sinon si on filtre seulement sur des machines
machines_f = machines
# Filtrage des adhérents en fonction des machines
if filter_machine:
if filter_adherent:
# Si on filtre sur des adhérents et des machines, on calcule l'intersection
machines_dn = set([m.parent_dn for m in machines])
adherents_f = [a for a in adherents if a.dn in machines_dn]
else:
# Sinon on récupères les proprios des machines trouvées
adherents_f = [m.proprio() for m in machines]
else:
# Sinon si on filtre seulement sur des adhérents
adherents_f = adherents
# 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=[]
olist={}
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
# On se débrouille pour faire corresponde l'ordre d'affichache des objets
# et leur ordre dans la liste items. On donne la largeur de l'affichage à la main
# pour prendre en compte la largeur du widget dialog
items=[]
items_id = {}
(line, col) = get_screen_size()
for c in olist.keys():
items.extend(olist[c])
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),
title=title,
scrollbar=True,
choices=choices,
colors=True)
# 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,...)"""
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
@tailCaller
def select(self, objectClassS, title, values={}, cont=None):
"""Permet de choisir un objet adhérent ou machine dans la base ldap"""
try:
# On fait effectuer une recherche à l'utilisateur
values, items = self.search(objectClassS, title, values, cont=cont)
# 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)
# 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))
# 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):
return item
else:
return TailCall(self.select, objectClassS, title, values, cont=cont)
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)
@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)
@tailCaller
def create_adherent(self, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
@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
def modif_machine_information(self, machine, cont):
self.dialog.msgbox("todo", width=0, height=0)
(code, tag) = 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
def modif_machine_blacklist(self, machine, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def modif_machine_alias(self, machine, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def modif_machine_exemption(self, machine, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def modif_machine_remarque(self, machine, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
@tailCaller
def modif_machine(self, cont, machine=None, tag=None):
if machine is None:
machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour modification", cont=cont)
menu = {
'Information' : {'text' : "Modifier le nom de machine, l'IP, adresse MAC", "callback":self.modif_machine_information},
'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},
}
menu_order = ['Information', 'Blackliste', 'Alias', 'Exemption', 'Remarques']
def box(default_item=None):
return self.dialog.menu(
"Que souhaitez vous modifier ?",
width=0,
height=0,
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 ''
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):
return self.dialog.menu(
"Que souhaitez vous faire ?",
width=0,
height=0,
menu_height=0,
item_help=1,
default_item=str(default_item),
title="Menu principal",
scrollbar=True,
cancel_label="Quitter",
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 menu_order])
(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))
else:
return TailCall(self.menu_principal, tag, proprio=proprio, machine=machine)
if __name__ == '__main__':
GestCrans()
os.system('clear')