365 lines
12 KiB
Python
Executable file
365 lines
12 KiB
Python
Executable file
#!/bin/bash /usr/scripts/python.sh
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Interface utilisateur du système de gestion des machines
|
|
et adhérents du crans
|
|
|
|
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 argparse
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
|
|
if '/usr/scripts' not in sys.path:
|
|
sys.path.append('/usr/scripts')
|
|
|
|
import lc_ldap.attributs as attributs
|
|
import lc_ldap.objets as objets
|
|
|
|
from dialog import adherent, club, machine
|
|
from dialog.CPS import TailCall, tailcaller
|
|
from dialog.lc import main
|
|
|
|
|
|
def handle_dialog_exit_code(dialog, code):
|
|
"""Gère les codes de retour du menu principal.
|
|
|
|
Paramètres:
|
|
- ``dialog``: (pythondialog.Dialog) instance courante de l'objet Dialog
|
|
- ``code`` : (int) code de retour de la fenêtre Dialog
|
|
|
|
Retourne:
|
|
- ``True`` quand dialog a quitté correctement (l'utilisateur a bien
|
|
saisi une valeur)
|
|
- ``False`` quand l'utilisateur annule la sortie du programme
|
|
(aucune valeur n'a été saisie)
|
|
|
|
Lève les exceptions:
|
|
- :py:exc:`SystemExit` quand la sortie du programme est confirmée
|
|
|
|
"""
|
|
|
|
def exit_from_program():
|
|
"""
|
|
Quitte le programme après avoir vidé la console
|
|
|
|
Lève l'exception :py:exc:`SystemExit`
|
|
"""
|
|
subprocess.call('clear')
|
|
sys.exit(0)
|
|
|
|
if code == dialog.DIALOG_CANCEL:
|
|
exit_from_program()
|
|
elif code == dialog.DIALOG_ESC:
|
|
msg = (
|
|
"Vous avez appuyé sur ESC ou CTRL+C dans la dernière fenêtre"
|
|
" de dialogue.\n\nVoulez vous quitter le programme ?"
|
|
)
|
|
if dialog.yesno(msg, width=60) == dialog.DIALOG_OK:
|
|
exit_from_program()
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
|
|
class GestCrans(adherent.Dialog, club.Dialog, machine.Dialog):
|
|
@tailcaller
|
|
def menu_principal(self, tag=None, machine=None, proprio=None):
|
|
"""Menu principal de l'application.
|
|
|
|
Paramètres:
|
|
- ``tag``: (str) clé du menu sélectionnée par défaut
|
|
- ``machine``: (lc_ldap.Machine) machine sélectionnée
|
|
- ``proprio``: (lc_ldap.Adherent ou lc_ldap.Club) proprio
|
|
sélectionné
|
|
|
|
Le menu principal est affiché lorsqu'on démarre l'application,
|
|
et lorsqu'on revient au menu principal suite à une action sur un
|
|
objet (arguments machine ou proprio).
|
|
|
|
Le menu est créé en deux étapes :
|
|
- Actions spécifiques à l'objet (`machine`, `proprio`) sélectionné
|
|
- Actions génériques
|
|
|
|
"""
|
|
|
|
# Droits nécessaires pour effectuer les différentes actions.
|
|
# Par défaut, on vérifie les droits ''.
|
|
# La clé correspond à la clé dans le dictionnaire menu.
|
|
menu_droits = {
|
|
'': [attributs.cableur, attributs.nounou],
|
|
'aKM': [attributs.nounou],
|
|
}
|
|
|
|
# On initialise le menu avec des actions "génériques"
|
|
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,
|
|
},
|
|
|
|
'': {
|
|
'text': "---------------------------------------",
|
|
'callback': None,
|
|
},
|
|
}
|
|
|
|
# On liste les actions pour définir leur ordre d'affichage
|
|
menu_order = [
|
|
# Actions adhérent
|
|
"aA", "mA", "aMA", "",
|
|
# Actions machine
|
|
"mM", "",
|
|
# Actions club
|
|
"aC", "mC", "aMC", "",
|
|
# Actions Cr@ns
|
|
"aKM",
|
|
]
|
|
|
|
# Ajout des actions spécifiques en tête du menu
|
|
|
|
# On récupère le propriétaire de la machine pour ajouter les
|
|
# actions associées
|
|
if machine and not proprio:
|
|
proprio = machine.proprio()
|
|
|
|
# Sauf si le propriétaire est le Cr@ns
|
|
if isinstance(proprio, objets.AssociationCrans):
|
|
proprio = None
|
|
|
|
# On a sélectionné un objet. On ajoute les actions spécifiques à
|
|
# l'objet avant les actions génériques.
|
|
if machine or proprio:
|
|
menu_order = [''] + menu_order
|
|
|
|
# Actions spécifiques à une machine
|
|
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
|
|
|
|
# Actions spécifiques à un proprio (adhérent ou club)
|
|
if proprio:
|
|
if 'adherent' in proprio['objectClass']:
|
|
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']
|
|
elif 'club' in proprio['objectClass']:
|
|
menu_proprio = {
|
|
'mCc': {
|
|
'text': ("Modifier l'inscription de %s" %
|
|
proprio.get("cn", proprio["nom"])[0]),
|
|
'callback': TailCall(self.modif_club, club=proprio)
|
|
},
|
|
'aMc': {
|
|
'text': ("Ajouter une machine à %s" %
|
|
proprio.get("cn", proprio["nom"])[0]),
|
|
'callback': TailCall(self.create_machine_club,
|
|
club=proprio)
|
|
},
|
|
}
|
|
menu_proprio_order = ['mCc', 'aMc']
|
|
else:
|
|
raise EnvironmentError(
|
|
"Le proprio sélectionné doit être un adhérent ou un club."
|
|
)
|
|
menu.update(menu_proprio)
|
|
menu_order = menu_proprio_order + menu_order
|
|
|
|
def dialog_menu_principal(default_item=None):
|
|
"""Renvoie la boîte de dialogue telle que définie avec le menu
|
|
précédent."""
|
|
|
|
# On construit une liste de triplets (clé, titre, texte
|
|
# d'aide), passée à dialog, à partir de la liste des clés du
|
|
# menu et des fonctions associées
|
|
choices = []
|
|
|
|
# On reprend les clés dans l'ordre défini par la liste menu_order
|
|
for key in menu_order:
|
|
# Vérification des droits
|
|
if self.has_right(menu_droits.get(key, menu_droits[''])):
|
|
choices.append((
|
|
key,
|
|
menu[key]['text'],
|
|
menu[key].get('help', ""),
|
|
))
|
|
|
|
# On enlève les lignes de séparation qui seraient au début
|
|
# ou à la fin de la boîte de dialogue
|
|
while choices[0][0] == '':
|
|
choices = choices[1:]
|
|
while choices[-1][0] == '':
|
|
choices = choices[:-1]
|
|
|
|
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,
|
|
timeout=self.timeout,
|
|
cancel_label="Quitter",
|
|
backtitle=self._connected_as(),
|
|
choices=choices,
|
|
)
|
|
|
|
# Appel de dialog pour afficher le menu construit précédemment.
|
|
# Oui, il faut passer la fonction qui gère une annulation d'abord...
|
|
(code, tag) = self.handle_dialog(
|
|
TailCall(
|
|
handle_dialog_exit_code,
|
|
self.dialog,
|
|
self.dialog.DIALOG_ESC,
|
|
),
|
|
dialog_menu_principal,
|
|
tag,
|
|
)
|
|
|
|
# Appel qui permet de revenir au menu principal
|
|
rappel_menu_principal = TailCall(
|
|
self.menu_principal,
|
|
tag=tag,
|
|
proprio=proprio,
|
|
machine=machine,
|
|
)
|
|
|
|
# On récupère le callback de la ligne sélectionnée par
|
|
# l'utilisateur
|
|
fonction_selectionnee = menu.get(tag, menu[''])['callback']
|
|
|
|
# On vérifie si l'utilisateur a appelé une ligne non-séparateur
|
|
if (
|
|
handle_dialog_exit_code(self.dialog, code) and
|
|
fonction_selectionnee
|
|
):
|
|
# On appelle la fonction sélectionnée puis on revient au
|
|
# menu principal
|
|
return TailCall(fonction_selectionnee, cont=rappel_menu_principal)
|
|
else:
|
|
# La ligne est un séparateur ou l'utilisateur a annulé, on
|
|
# revient au menu principal immédiatement
|
|
return rappel_menu_principal
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(
|
|
description=(
|
|
'Interface utilisateur du système de gestion '
|
|
'des machines et adhérents du Cr@ns'
|
|
)
|
|
)
|
|
parser.add_argument(
|
|
'--test',
|
|
help='Utiliser la base de test',
|
|
dest='ldap_test',
|
|
default=False,
|
|
action='store_true',
|
|
)
|
|
parser.add_argument(
|
|
'--debug',
|
|
help='Afficher des info de débug comme les tracebacks',
|
|
dest='debug_enable',
|
|
default=False,
|
|
action='store_true',
|
|
)
|
|
parser.add_argument(
|
|
'login',
|
|
help="Se connecter en tant qu'un autre utilisateur",
|
|
type=str,
|
|
default=None,
|
|
nargs='?',
|
|
)
|
|
args = parser.parse_args()
|
|
main(GestCrans(
|
|
ldap_test=args.ldap_test,
|
|
debug_enable=args.debug_enable,
|
|
custom_user=args.login,
|
|
))
|
|
os.system('clear')
|