diff --git a/gestion/ldap_crans.py b/gestion/ldap_crans.py index 7fb905bc..2d5b10da 100755 --- a/gestion/ldap_crans.py +++ b/gestion/ldap_crans.py @@ -22,7 +22,7 @@ from numeros_disponibles import lister_ip_dispo date_format = '%d/%m/%Y %H:%M' hostname = gethostname().split(".")[0] -smtpserv = "rouge.crans.org" +smtpserv = "rouge.crans.org" random.seed() # On initialise le générateur aléatoire @@ -48,7 +48,7 @@ elif cur_user == 'freerad': uri = '' ro_uri = 'ldapi://%2fvar%2frun%2fslapd%2fldapi/' ldap_auth_dn = ldap_password = '' - + else: # pour les autres on utilise ldap.adm.crans.org en rw uri = 'ldap://ldap.adm.crans.org/' @@ -68,15 +68,15 @@ else: ro_uri = uri ################################################################################## -### Items de la blackliste -blacklist_items = { u'bloq': u'Bloquage total de tous les services', +### Items de la blackliste +blacklist_items = { u'bloq': u'Bloquage total de tous les services', u'virus': u'Bloquage sur squid', u'upload': u"Bloquage total de l'accès à l'extérieur", u'warez': u'Bloquage sur squid', u'p2p': u"Bloquage total de l'accès à l'extérieur", u'autodisc_upload': u'Autodisconnect pour upload', u'autodisc_p2p': u'Autodisconnect pour P2P' } - + ################################################################################## ### Droits possibles droits_possibles = [ u'Nounou', u'Apprenti', u'Moderateur', u'Cableur', @@ -92,7 +92,7 @@ droits_critiques = [ u'Nounou', u'Apprenti', u'WebRadio'] ### cotise plus droits_vieux = [u'Nounou', u'Bureau'] -################################################################################## +################################################################################## ### Variables internes diverses #isadm = user_tests.isadm() #isdeconnecteur = user_tests.isdeconnecteur() @@ -150,18 +150,18 @@ def preattr(val): * un entier * une chaîne * une liste avec un seul entier ou une seule cha-Aîne-b - + Retourne [ len(str(val).strip), str(val).strip en utf-8 ] """ t = type(val) - + if t == list and len(val) == 1: return preattr(val[0]) - + elif t == str or t == int: val = str(val).strip() - # On passe tout en utf-8 pour ne pas avoir de problèmes + # On passe tout en utf-8 pour ne pas avoir de problèmes # d'accents dans la base return [len(val), unicode(val, 'iso-8859-1').encode('utf-8')] elif t == unicode: @@ -169,8 +169,8 @@ def preattr(val): return [len(val), val.encode('utf-8')] else: raise TypeError - - + + def is_actif(sanction): """ Retourne True ou False suivant si la sanction fournie (chaîne @@ -187,8 +187,8 @@ def is_actif(sanction): def format_mac(mac): - """ - Formatage des adresses mac + """ + Formatage des adresses mac Transforme une adresse pour obtenir la forme xx:xx:xx:xx:xx:xx Le séparateur original peut être :, - ou rien Retourne la mac formatée. @@ -198,17 +198,17 @@ def format_mac(mac): if mac.count(":") == 5: # On a une adresse de la forme 0:01:02:18:d1:90 # On va compléter s'il manque des 0 - mac = ":".join(map(lambda x: x.replace(' ', '0'), + mac = ":".join(map(lambda x: x.replace(' ', '0'), map(lambda x: "%02s" % x, mac.split(":")))) mac = mac.replace(':', '').lower() if len(mac) != 12: raise ValueError(u"Longueur de l'adresse mac incorrecte.") for c in mac[:]: - if c not in string.hexdigits: + if c not in string.hexdigits: raise ValueError(u"Caractère interdit '%s' dans l'adresse mac." % c) if mac == '000000000000': raise ValueError(u"MAC nulle interdite\nIl doit être possible de modifier l'adresse de la carte.") - + # Formatage mac = "%s:%s:%s:%s:%s:%s" % (mac[:2], mac[2:4], mac[4:6], mac[6:8], mac[8:10], mac[10:]) @@ -225,17 +225,17 @@ class Service: Liste des arguments Liste d'horaires de démarrages """ - + self.nom = nom self.args = args self.start = map(int, start) - + def __str__(self): starting = self.start starting.sort() dates = ' et '.join(map(lambda t: t < time.time() and \ "maintenant" or time.strftime(date_format, - time.localtime(t)), + time.localtime(t)), self.start)) dates = " à partir d%s %s" % (dates.startswith("maintenant") and "e" or "u", dates) @@ -251,7 +251,7 @@ class CransLdap: base_dn = 'ou=data,dc=crans,dc=org' base_lock = 'ou=lock,dc=crans,dc=org' base_services = 'ou=services,dc=crans,dc=org' - + ### Configuration de la recheche # Dictionnaire de transformation des champs trans = { 'prénom': 'prenom', @@ -262,14 +262,14 @@ class CransLdap: 'ip': 'ipHostNumber', 'telephone': 'tel', 'position': 'positionBorne' } - + # Les différentes classes LDAP de machines ldap_machines_classes = ['machineFixe', 'machineWifi', 'machineCrans', 'borneWifi'] # Champs de recherche pour la recherche automatique auto_search_machines_champs = \ ['macAddress', 'host', 'ipHostNumber', 'hostAlias'] - + auto_search_champs = { \ 'adherent': \ ['nom', 'prenom', 'tel', 'mail', 'chbre', 'mailAlias', 'canonicalAlias'], @@ -279,12 +279,12 @@ class CransLdap: 'machineCrans': auto_search_machines_champs, 'borneWifi': auto_search_machines_champs, 'facture' : [] } - + # Champs de recherche pour la recherche manuelle (en plus de la recherche auto) non_auto_search_machines_champs = \ ['mid', 'historique', 'blacklist', 'info', 'exempt', 'mblacklist', 'portTCPin', 'portTCPout', 'portUDPin', 'portUDPout'] - + non_auto_search_champs = { \ 'adherent': \ ['etudes', 'paiement', 'carteEtudiant', 'aid', 'postalAddress', @@ -301,12 +301,12 @@ class CransLdap: ['prise', 'puissance', 'canal', 'hotspot', 'positionBorne', 'nvram'], 'machineWifi': non_auto_search_machines_champs + ['ipsec'], 'facture': ['fid']} - + # tous les champs de recherche search_champs = {} for i in auto_search_champs.keys(): search_champs[i] = auto_search_champs[i] + non_auto_search_champs[i] - + # Profondeur des différentes recherches (scope) scope = { 'adherent': 1, 'club': 1, @@ -315,14 +315,14 @@ class CransLdap: 'machineCrans': 2, 'borneWifi': 2, 'facture': 2} - + def __init__(self, readonly=False): self.connect(readonly) def __del__(self): # Destruction des locks résiduels if hasattr(self, '_locks'): - for lock in self._locks: + for lock in self._locks: self.remove_lock(lock) def connect(self, readonly=False): @@ -330,7 +330,7 @@ class CransLdap: if readonly: self.conn = ldap.initialize(ro_uri) return - + self.conn = ldap.initialize(uri) nbessais = 0 while True: @@ -356,11 +356,11 @@ class CransLdap: Si il y a existence, on retourne la liste des dn correspondants Sinon retourne une liste vide - + Exemple : exist('chbre=Z345') vérifie si il y a un adhérent en Z345 """ r = [] - + # Premier test: dans les objets déjà inscrits ret = self.conn.search_s(self.base_dn, 2, arg) for res in ret: @@ -371,17 +371,17 @@ class CransLdap: except: pass r.append(res[0]) - + # Deuxième test : lock ? ret = self.conn.search_s(self.base_lock, 1, arg) lockid = '%s-%s' % (hostname, os.getpid()) for res in ret: # Lock encore actif ? l = res[1]['lockid'][0] - if l == lockid: continue + if l == lockid: continue # C'est locké par un autre process que le notre # il tourne encore ? - try: + try: if l.split('-')[0] == hostname and os.system('ps %s > /dev/null 2>&1' % l.split('-')[1]): # Il ne tourne plus self.remove_lock(res[0]) # delock @@ -389,9 +389,9 @@ class CransLdap: except: pass r.append(res[0]) - + return r - + def lock(self, item, valeur): """ Lock un item avec la valeur valeur, les items possibles @@ -402,44 +402,44 @@ class CransLdap: Retourne le dn du lock """ - + valeur = valeur.encode('utf-8') - + if not valeur: # On le lock pas un truc vide return True - + lock_dn = '%s=%s,%s' % (item, valeur, self.base_lock) lockid = '%s-%s' % (hostname, os.getpid()) modlist = ldap.modlist.addModlist({ 'objectClass': 'lock', 'lockid': lockid, item: valeur }) - + try: self.conn.add_s(lock_dn, modlist) except ldap.ALREADY_EXISTS: # Pas de chance, le lock est déja pris - try: + try: res = self.conn.search_s(lock_dn, 2, 'objectClass=lock')[0] l = res[1]['lockid'][0] except: l = '%s-1' % hostname - if l != lockid: + if l != lockid: # C'est locké par un autre process que le notre # il tourne encore ? if l.split('-')[0] == hostname and os.system('ps %s > /dev/null 2>&1' % l.split('-')[1] ): # Il ne tourne plus self.remove_lock(res[0]) # delock - return self.lock(item, valeur) # relock + return self.lock(item, valeur) # relock raise EnvironmentError(u'Objet (%s=%s) locké, patienter.' % (item, valeur), l) else: if not hasattr(self, '_locks'): self._locks = [lock_dn] else: self._locks.append(lock_dn) - + def remove_lock(self, lockdn): """ - Destruction d'un lock + Destruction d'un lock Destruction de tous les locks si lockdn=* """ # Mettre des verifs ? @@ -454,29 +454,29 @@ class CransLdap: locks = self.list_locks() for l in locks: self.conn.delete_s(l[0]) - + def list_locks(self): """ Liste les locks """ return self.conn.search_s(self.base_lock, 1, 'objectClass=lock') - + def services_to_restart(self, new=None, args=[], start=0): """ start indique la date (en secondes depuis epoch) à partir du moment où cette action sera effectuée. Si new = None retourne la liste des services à redémarrer. - + Si new est fourni, mais ne commence pas par '-', on ajoute le service à la liste avec les arguments args (args doit être une liste). - + Si new commence par '-', on supprime le service si son start est dans le futur. Si new commence par '--', on supprime le service de condition. """ if new: new = preattr(new)[1] - + # Quels services sont déjà à redémarrer ? serv = {} # { service: [ arguments ] } serv_dates = {} # { service: [ dates de restart ] } @@ -486,13 +486,13 @@ class CransLdap: serv[s['cn'][0]] = s.get('args', []) serv_dates[s['cn'][0]] = s.get('start', []) services.append(Service(s['cn'][0], s.get('args', []), s.get('start', []))) - + # Retourne la liste des services à redémarrer if not new: return services - + # Effacement d'un service if new[0] == '-': - if new[1] == '-': + if new[1] == '-': # Double -- on enlève quelque soit la date remove_dn = 'cn=%s,%s' % (new[2:], self.base_services) else: @@ -508,7 +508,7 @@ class CransLdap: if keep_date: self.conn.modify_s(remove_dn, ldap.modlist.modifyModlist({'start': serv_dates[new[1:]]}, { 'start': keep_date })) remove_dn = None - + if remove_dn: # Suppression try: self.conn.delete_s(remove_dn) @@ -517,16 +517,16 @@ class CransLdap: return serv_dn = 'cn=%s,%s' % (new, self.base_services) - + # Conversion avant stockage dans la base if type(args) == str: args = [args] args = map(lambda x:preattr(x)[1], args) if type(start) == int: start = [start] start = map(lambda x:preattr(x)[1], start) - + if new in serv.keys(): modlist = [] - + new_args = [] # Nouveaux arguments ? for arg in args: @@ -535,7 +535,7 @@ class CransLdap: if new_args: modlist += ldap.modlist.modifyModlist({'args': serv[new]}, {'args': serv[new] + new_args}) - + new_date = [] # Nouvelle date ? for date in start: @@ -563,7 +563,7 @@ class CransLdap: except ldap.ALREADY_EXISTS: # Existe déja => rien à faire pass - + def make(self, entry, mode=''): """ @@ -584,7 +584,7 @@ class CransLdap: def search(self, expression, mode=''): """ Recherche dans la base LDAP, expression est une chaîne : - * soit une expression : champ1=expr1&champ2=expr2&champ3!=expr3... + * soit une expression : champ1=expr1&champ2=expr2&champ3!=expr3... * soit un seul terme, dans ce cas cherche sur les champs de auto_search_champs Si mode='w', les instances crées seront en mode d'écriture @@ -594,7 +594,7 @@ class CransLdap: openlog('CransLdap.search') syslog('(%s,%s) %s' % (script_utilisateur, mode, expression)) closelog() - + if type(expression) == str: # Transformation de l'expression en utf-8 expression = unicode(expression, 'iso-8859-15').encode('utf-8') @@ -602,19 +602,19 @@ class CransLdap: expression = expression.encode('utf-8') else: raise TypeError(u'Chaîne attendue') - + if not expression: return [] # On échappe les caractères spéciaux expression = expression.replace('\\', '\\\\').replace('(', '\\(').replace(')', '\\)') - # Il faut un filtre par type d'objet de la base + # Il faut un filtre par type d'objet de la base filtres = self.auto_search_champs.keys() result = {} for i in filtres: result[i] = [] - + # Fonction utile def build_filtre(champ, expr, neg=False): """ @@ -660,7 +660,7 @@ class CransLdap: el = '(%s=%s)' % (champ, expr) if neg: el = '(!%s)' % el return el - + if '=' in expression: #### Recherche avec conditions explicites ## Construction des filtres @@ -674,9 +674,9 @@ class CransLdap: for i in filtres: filtre[i] = '' filtre_seul[i] = False - + conds = expression.split('&') - + # Test de l'expression de recherche et classement par filtres for cond in conds: neg = False @@ -688,20 +688,20 @@ class CransLdap: neg = True except: raise ValueError(u'Syntaxe de recherche invalide (%s)' % cond) - + # Transformation de certains champs champ = self.trans.get(champ, champ) if expr == '': expr = '*' - + ok = False - + # si c'est un champ uniquement adherent (genre droit), on ignore les clubs et vice versa if champ in self.search_champs['club'] and champ not in self.search_champs['adherent']: if 'adherent' not in ignore_filtre: ignore_filtre.append('adherent') if champ in self.search_champs['adherent'] and champ not in self.search_champs['club']: if 'club' not in ignore_filtre: ignore_filtre.append('club') - + # Construction du filtre for i in filtres: if champ in self.search_champs[i]: @@ -710,7 +710,7 @@ class CransLdap: if champ not in self.auto_search_machines_champs \ and champ not in self.non_auto_search_machines_champs: filtre_seul[i] = True - + if not ok: raise ValueError(u'Champ de recherche inconnu (%s)' % champ) @@ -727,7 +727,7 @@ class CransLdap: for i in self.ldap_machines_classes: if not filtre_seul[i]: filtre[i] = '' - + for i in filtres: if i in ignore_filtre or filtre[i] == '': # Filtre vide ou à ignorer @@ -737,7 +737,7 @@ class CransLdap: # Filtre valide filtre[i] = '(&(objectClass=%s)%s)' % (i, filtre[i]) r[i] = self.conn.search_s(self.base_dn, self.scope[i], filtre[i]) - + ## On a alors une liste de résultats ## r = {categorie1: [(result1), (result2), ...], ...} @@ -746,16 +746,16 @@ class CransLdap: if (r['machineFixe'] != None or r['machineWifi'] != None) \ and (r['adherent'] != None or r['club'] != None) \ and len(conds) > 1: - + # On renvoie toutes les machineCrans et borneWifi for i in 'machineCrans', 'borneWifi': if r[i] == None: continue for res in r[i]: result[i].append(self.make(res, mode)) - + # On croise maintenant les résultats machine et propriétaire - + # Traitement des machines mach_adh = [] # liste de dn d'adhérents et de clubs for res in r['machineFixe'] + r['machineWifi']: @@ -764,7 +764,7 @@ class CransLdap: continue if dn not in mach_adh: mach_adh.append(dn) - + # Croisement bons_dn = [] # liste des dn d'adhérents qui correspondent aux critères for i in 'adherent', 'club': @@ -774,7 +774,7 @@ class CransLdap: if a[0] in mach_adh and not a[0] in bons_dn: bons_dn.append(a[0]) result[i].append(self.make(a, mode)) - + # Maintenant c'est au tour des bonnes machines bons_dn2 = [] for i in 'machineFixe', 'machineWifi': @@ -793,7 +793,7 @@ class CransLdap: continue for res in r[i]: result[i].append(self.make(res, mode)) - + else: ### Recherche d'une chaine sur tous les champs for i in filtres: @@ -802,16 +802,16 @@ class CransLdap: for champ in self.auto_search_champs[i]: filtre += build_filtre(champ, expression) filtre = '(&(|%s)(objectClass=%s))' % (filtre, i) - + # Recherche for r in self.conn.search_s(self.base_dn, self.scope[i], filtre): result[i].append(self.make(r, mode)) - + # Backward-compatibilité result['machine'] = [] for i in self.ldap_machines_classes: result['machine'] += result[i] - + return result def getProprio(self, uid, mode=''): @@ -821,7 +821,7 @@ class CransLdap: """ recherche = self.search(("uid=%s" % uid),mode) proprio = None - + if len(recherche['club']) > 0: proprio = recherche['club'][0] if len(recherche['adherent']) > 0: @@ -830,7 +830,7 @@ class CransLdap: if uid == "grosminet": proprio = self.search("nom=grosminet", mode)['adherent'][0] return proprio - + __machines = () def all_machines(self, graphic=False): """ @@ -857,11 +857,11 @@ class CransLdap: self.__machines += adh.machines() if graphic: a.reinit() if graphic: print OK - + return self.__machines ############################################################################# - + class BaseClasseCrans(CransLdap): """ Méthodes de base des classes machines, et BaseProprietaire """ @@ -869,7 +869,7 @@ class BaseClasseCrans(CransLdap): """ Test d'égalité de deux instances de club/adhérent/machine, retourne True s'il s'agit du même club/adhérent/machine, False sinon """ return self.__class__ == autre.__class__ and self.id() == autre.id() - + def id(self): """ Retourne la valeur de l'attribu caractéristique de la classe (aid,mid,cid)""" try: @@ -878,11 +878,11 @@ class BaseClasseCrans(CransLdap): return s[1] except: return '' - + def __str__(self): """ Chainde identifiant l'objet de la forme 'uid=1245' """ return '%s=%s' % (self.idn, self.id()) - + def blacklist_actif(self): """ Vérifie si l'instance courante est blacklistée. @@ -904,15 +904,15 @@ class BaseClasseCrans(CransLdap): ex: {'upload': [(1143336210, 1143509010), ...]} """ bl_liste = self._data.get('blacklist', []) - + if isinstance(self, Machine): # Il faut aussi regarder la blackliste du propriétaire p = self.proprietaire() bl_liste += p.blacklist() - + actifs = {} inactifs = {} - + for sanction in bl_liste: champs = sanction.split('$') s = champs[2] @@ -939,7 +939,7 @@ class BaseClasseCrans(CransLdap): liste = self._data.setdefault('blacklist', [])[:] if new == None: return map(decode, liste) - + if type(new) == tuple: # Modification index = new[0] @@ -950,10 +950,10 @@ class BaseClasseCrans(CransLdap): return liste else: index = -1 - + if type(new) != list or len(new) != 4: raise TypeError - + # Verification que les dates sont OK if new[0] == 'now': debut = new[0] = int(time.time()) @@ -968,23 +968,23 @@ class BaseClasseCrans(CransLdap): else: try: fin = new[1] = int(new[1]) except: raise ValueError(u'Date de fin blacklist invalide') - + if debut == fin: raise ValueError(u'Dates de début et de fin identiques') elif fin != -1 and debut > fin: raise ValueError(u'Date de fin avant date de début') - + # On dépasse la fin de sanction d'1min pour être sûr qu'elle est périmée. fin = fin + 60 - + new_c = '$'.join(map(str, new)) new_c = preattr(new_c)[1] - + if index != -1: liste[index] = new_c else: liste.append(new_c) - + if liste != self._data['blacklist']: self._data['blacklist'] = liste self.modifs.setdefault('blacklist_' + new[2], None) @@ -995,21 +995,21 @@ class BaseClasseCrans(CransLdap): restart.append(debut) if fin != -1 and fin not in restart: restart.append(fin) - + return liste - + def restore(self): """ Restore les données à l'état initial (ou pas) """ self._data = self._init_data.copy() self.modifs = {} - + def historique(self): """ Retourne l'historique de l'objet """ return map(decode, self._data.get('historique', [])) - + def info(self, new=None): """ - Pour ajouter une remarque new doit être la chaîne + Pour ajouter une remarque new doit être la chaîne représentant la remarque à ajouter Pour modifier new doit être une liste de la forme: [ index de la remarque à modifier, nouvelle remarque ] @@ -1019,7 +1019,7 @@ class BaseClasseCrans(CransLdap): self._data['info'] = [] liste = list(self._data['info']) if new == None: return map(decode, liste) - + if type(new) == list: # Modif index = new[0] @@ -1043,17 +1043,17 @@ class BaseClasseCrans(CransLdap): self._set('info', liste) return liste - + def _save(self): """ Sauvegarde dans la base LDAP """ if not self.modifs: # Rien à faire return [] - + if not self.dn: # Enregistrement à placer en tête de base self.dn = self.base_dn - + # Construction de l'historique if not self._init_data: if self.idn=='fid': @@ -1064,7 +1064,7 @@ class BaseClasseCrans(CransLdap): ### ON NE TOUCHE PAS A SELF.MODIFS, IL EST UTILISÉ PLUS LOIN !!!!!!! # Dictionnaire local des modifs modif = {} - + # Cas spécial if "solde" in self.modifs: diff = float(self._init_data.get('solde', [0])[0]) - float(self._data.get('solde', [0])[0]) @@ -1072,7 +1072,7 @@ class BaseClasseCrans(CransLdap): modif['solde'] = "debit %s Euros" % str(diff) else: modif['solde'] = "credit %s Euros" % str(-diff) - + # Formate les entrées de l'historique de la forme champ (ancien -> nouveau) # On suppose que le champ apparaît forcément dans l'enregistrement for champ in ['chbre', 'nom', 'prenom', 'mail', 'tel', @@ -1091,8 +1091,8 @@ class BaseClasseCrans(CransLdap): valeur_finale = self._data[champ][0] modif[champ] = '%s (%s -> %s)' % (champ, valeur_initiale, - valeur_finale) - + valeur_finale) + # Formate les entrées de l'historique de la forme champ+diff-diff for champ in ['droits', 'controle', 'paiement', 'carteEtudiant', 'mailAlias', 'hostAlias', 'exempt', 'nvram', @@ -1109,12 +1109,12 @@ class BaseClasseCrans(CransLdap): # Là, on bosse directement sur les listes renvoyées par get ancien = self._init_data.get(champ, []) nouveau = self._data.get(champ, []) - + # On établit le diff diff = ''.join([ '+%s' % decode(d) for d in nouveau if d not in ancien ]) diff += ''.join([ '-%s' % decode(d) for d in ancien if d not in nouveau ]) modif[champ] = champ + diff - + # On recolle tous les morceaux liste_historique = [] for champ in self.modifs.keys(): @@ -1126,7 +1126,7 @@ class BaseClasseCrans(CransLdap): ligne += ' [%s]' % self.modifs[champ] liste_historique.append(ligne) modif = ', '.join(liste_historique) - + timestamp = time.localtime() hist = "%s, %s" % ( time.strftime(date_format, timestamp), script_utilisateur ) @@ -1136,7 +1136,7 @@ class BaseClasseCrans(CransLdap): self._data['historique'] = [x for x in self._data['historique'] if 'derniereConnexion' not in x] - + # On loggue try: fd = file('%s/%s_%s_%s' % ("%s/logs" % config.cimetiere, str(self.__class__).split('.')[-1], @@ -1145,7 +1145,7 @@ class BaseClasseCrans(CransLdap): fd.close() except: pass - + # Suffit-t-il d'ajouter un item au dernier élément de l'historique ? try: if modif: @@ -1162,7 +1162,7 @@ class BaseClasseCrans(CransLdap): except: # Nouvelle inscription self._data['historique'] = [ "%s : %s" % ( hist, modif ) ] - + if not self._init_data: ### Nouvel enregistrement # L'enregistrement peut échouer en cas de choix de dn concurrents @@ -1200,34 +1200,34 @@ class BaseClasseCrans(CransLdap): raise if i == 4: raise - + else: ### Modification entrée if not self._modifiable: raise RuntimeError(u'Objet non modifiable : %s' % str(self)) modlist = ldap.modlist.modifyModlist(self._init_data, self._data) - try: + try: self.conn.modify_s(self.dn, modlist) except ldap.TYPE_OR_VALUE_EXISTS, c: champ = c.args[0]['info'].split(':')[0] raise RuntimeError(u'Entrée en double dans le champ %s' % champ) - + ### Génération de la liste de services à redémarrer # Quasiement tout est traité dans les classes filles. if hasattr(self, "_blacklist_restart"): for n, t in self._blacklist_restart.items(): self.services_to_restart("blacklist_%s"%n, [], t) - + # Reinitialisation self._init_data = self._data.copy() - + def _delete(self, dn, comment=''): """ Sauvegarde puis destruction du dn (et des sous-dn) fourni """ # Commentaires comment = preattr(comment)[1] self.modifs.setdefault('destruction (%s)' % comment, None) self._save() - + # Sauvegarde t = str(self.__class__).split('.')[-1] fd = open('%s/%s/%s_%s' % (config.cimetiere, t, time.strftime('%Y-%m-%d-%H:%M'), self.nom()), 'wb') @@ -1235,22 +1235,22 @@ class BaseClasseCrans(CransLdap): cPickle.dump(self, fd, 2) fd.close() index = u"%s, %s : %s %s # %s\n" % (time.strftime(date_format), script_utilisateur, t, self.Nom() , decode(comment)) - + self.connect() # Reconnexion à la base # Destruction data = self.conn.search_s(dn, 2) - - data.reverse() # Necessaire pour détruire d'abord les sous-dn + + data.reverse() # Necessaire pour détruire d'abord les sous-dn for r in data: self.conn.delete_s(r[0]) - + try: log = open(config.cimetiere + '/index', 'a') log.write(index) log.close() except: pass - + def _set(self, champ, val, comment=None): """ Met à jour les données de _data et modifie modifs si besoin. @@ -1277,8 +1277,8 @@ class BaseClasseCrans(CransLdap): # Ici, self.modifs[champ] et comment devraient être tous deux # des chaînes de caractères self.modifs[champ] += ', ' + comment - - + + ############################################################################# class BaseProprietaire(BaseClasseCrans): @@ -1293,21 +1293,21 @@ class BaseProprietaire(BaseClasseCrans): Attention, si mode ='w' mais si l'objet est déja locké il n'y a pas d'erreur vérifier l'obtetion du lock grace à la valeur de _modifiable (si =w c'est bon) Il est inutile de préciser le mode pour un nouveau proprietaire - + conn est une instance de la classe de connexion à la base LDAP """ self.conn = conn if not self.conn: self.connect() - + if type(data) != tuple: raise TypeError - + self.modifs = {} if data: self.dn = data[0] if mode == 'w': - try: + try: self.lock(self.idn, self.id()) self._modifiable = 'w' except EnvironmentError , c: @@ -1323,7 +1323,7 @@ class BaseProprietaire(BaseClasseCrans): self._data = { 'objectClass': [ self.objectClass ] } self._init_data = {} self._modifiable = 'w' - + def chsh(self, new=None): """ Retourne ou change le shell de l'adhérent """ if new == None: @@ -1333,7 +1333,7 @@ class BaseProprietaire(BaseClasseCrans): new = preattr(new)[1] self._set('loginShell', [new]) return new - + def alias(self, new=None): """ Création ou visualisation des alias mail @@ -1342,9 +1342,9 @@ class BaseProprietaire(BaseClasseCrans): if not self._data.has_key('mailAlias'): self._data['mailAlias'] = [] liste = list(self._data['mailAlias']) - if new == None: + if new == None: return map(decode, liste) - + if type(new) == list: # Modif index = new[0] @@ -1357,31 +1357,31 @@ class BaseProprietaire(BaseClasseCrans): else: new = new.replace('@crans.org', '') index = -1 - + # Tests l, new = preattr(new) new = new.lower() if l<2: raise ValueError(u"Alias trop court.") for c in new[:]: - if not c in (string.letters + string.digits + '-_.'): + if not c in (string.letters + string.digits + '-_.'): raise ValueError(u"Alias : seuls les caractères alphanumériques, le -, le _ et le . sont autorisés." ) if new[0] not in string.letters: raise ValueError(u"Le premier caractère de l'alias doit être alphabétique.") - if mailexist(new): + if mailexist(new): raise ValueError(u"Alias existant ou correspondand à un compte.") - + if index != -1: liste[index] = new else: liste = liste + [ new ] - + # Lock de mailAlias self.lock('mailAlias', new) - + self._set('mailAlias', liste) return liste - + def homepageAlias(self, new=None): """ Creation ou visualisation des alias de page perso. @@ -1401,7 +1401,7 @@ class BaseProprietaire(BaseClasseCrans): # ...peut-etre faire une verification de domaine ici... # On convertit en str (il ne devrait plus y avoir de caractere Unicode) return str(alias) - + if type(new) == list: # Modif index = new[0] @@ -1473,28 +1473,28 @@ class BaseProprietaire(BaseClasseCrans): return res else: return [] - + def solde(self, operation=None, comment=None): - """ Retourne ou modifie le solde d'un propriétaire - operation doit être un nombre positif ou négatif + """ Retourne ou modifie le solde d'un propriétaire + operation doit être un nombre positif ou négatif (string ou int ou float) comment est un commentaire à rajouter dans l'historique """ solde = float(self._data.get('solde', [0])[0]) - - if operation == None: + + if operation == None: return solde - + # On effectue une opération try: new = solde + float(str(operation).replace(',', '.')) except ValueError: raise ValueError(u"Il faut donner un nombre en argument.") - + # découvert accepté if new < config.impression.decouvert: raise ValueError(u"Solde minimal atteint, opération non effectuée.") - + self._set('solde', [str(new)], comment) return new @@ -1516,7 +1516,7 @@ class BaseProprietaire(BaseClasseCrans): if not sre.match(r'^[+-][pck]$', new): raise ValueError('modification de controle incorrecte') - + for c in 'pck': if new == '+%s' % c and c not in actuel: actuel += c @@ -1533,7 +1533,7 @@ class BaseProprietaire(BaseClasseCrans): self.modifs.setdefault('controle', None) else: self._set('controle', [actuel]) - + return actuel def contourneGreylist(self, contourneGreylist=None): @@ -1544,11 +1544,11 @@ class BaseProprietaire(BaseClasseCrans): Au niveau de la base, on considère la présence ou l'absence d'un champ contourneGreylist=OK. """ - + # Pour postfix il faut retourner : # OK : contourne la greyliste # cf. man 5 access - + # si l'adhérent n'a pas de compte, on lève une exception if not self.compte(): raise NotImplementedError, u"L'adhérent n'a pas de compte" @@ -1580,7 +1580,7 @@ class BaseProprietaire(BaseClasseCrans): if not self.compte(): raise NotImplementedError, u"L'adhérent n'a pas de compte" self._set('userPassword', [ldap_passwd.mkpasswd(passwd)]) - + def forward(self, new = None): """ Modifie ou retourne l'adresse de forward de l'adhérent @@ -1590,7 +1590,7 @@ class BaseProprietaire(BaseClasseCrans): if not self.compte(): raise NotImplementedError, u"L'adhérent n'a pas de compte" return config_mail.MailConfig(uid=self._data['uid'][0], forward = new)['forward'] - + def spam(self, new = None): """ Modifie ou retourne le traitement des spams de l'adhérent @@ -1601,7 +1601,7 @@ class BaseProprietaire(BaseClasseCrans): if not self.compte(): raise NotImplementedError, u"L'adhérent n'a pas de compte" return config_mail.MailConfig(uid=self._data['uid'][0], spam = new)['spam'] - + def home(self): """ Retourne le home de l'adhérent """ if not self.compte(): @@ -1619,7 +1619,7 @@ class BaseProprietaire(BaseClasseCrans): new = preattr(new)[1] self._set('uidNumber', [new]) return new - + def paiement(self, action=None): """ Action est un entier représentant une année @@ -1627,20 +1627,20 @@ class BaseProprietaire(BaseClasseCrans): si négatif le supprime """ return self._an('paiement', action) - + def delete(self, comment=''): """Destruction du proprietaire""" - + for m in self.machines(): # Destruction machines m.delete(comment) - + for f in self.factures(): # Destruction factures f.delete(comment) - + self._delete(self.dn, comment) - + try: if self.compte(): args = self._data['uid'][0] + ',' @@ -1649,7 +1649,7 @@ class BaseProprietaire(BaseClasseCrans): except: # Si ne peux avoir de compte pass - + def save(self): """ Enregistre l'adhérent ou le club courant dans la base LDAP @@ -1657,7 +1657,7 @@ class BaseProprietaire(BaseClasseCrans): Retourne une chaîne indiquant les opération effectuées. """ # Note: un peu trop de fonctions pour un club mais ce n'est pas génant - + ret = '' if self._init_data: nouveau = 0 @@ -1666,27 +1666,27 @@ class BaseProprietaire(BaseClasseCrans): if 'chbre' in self.modifs and '????' in [ self._init_data.get("chbre", [''])[0] , self._init_data.get("chbre", [''])[0] ]: self.services_to_restart('bl_chbre_invalide') - + # Enregistrement self._save() - + # Message de sortie - if nouveau: + if nouveau: ret += coul(u"%s inscrit avec succès." % self.Nom(), 'vert') - + if self.idn !='cid': # Mail de bienvenue self.services_to_restart('mail_bienvenue', [self.mail().encode('iso-8859-15')]) - + else: ret += coul(u"Modification %s effectuée avec succès." % self.Nom(), 'vert') - + # Changements administratifs test_carte = 'carteEtudiant' in self.modifs if test_carte and self.machines(): self.services_to_restart('bl_carte_etudiant') - + if 'paiement' in self.modifs or (config.bl_carte_et_definitif and test_carte): for m in self.machines(): self.services_to_restart('macip', [m.ip()] ) @@ -1703,24 +1703,24 @@ class BaseProprietaire(BaseClasseCrans): # Verif si machines avec bonnes ip err = 0 for m in self.machines(): - if isinstance(m, MachineWifi): + if isinstance(m, MachineWifi): # Machine Wifi continue # Machine fixe ip = m.ip() try: - # Tentative de changement d'IP de la machine + # Tentative de changement d'IP de la machine m.ip(ip) except ValueError: # IP invalide, on la change ret += "\nChangement d'IP machine %s : " % m.nom() - try: + try: ret += "%s -> %s" % ( ip, m.ip('') ) m.save() except Exception, c: ret += coul(u'ERREUR : %s' % c.args[0], rouge) err = 1 - + if err: ret += '\nEssayer de corriger les erreurs machines en éditant celles-ci.\n' # Faut-il créer un compte sur vert ? @@ -1735,7 +1735,7 @@ class BaseProprietaire(BaseClasseCrans): chgpass(self.dn) else: ret += coul(u' Il faudra penser à attribuer un mot de passe\n', 'jaune') - + # Modif des droits ? if 'droits' in self.modifs: self.services_to_restart('droits') @@ -1743,9 +1743,9 @@ class BaseProprietaire(BaseClasseCrans): # Remise à zero self.modifs = {} - + return ret - + def _an(self, champ, action): """ Champ est un champ contenant une liste d'entiers @@ -1763,7 +1763,7 @@ class BaseProprietaire(BaseClasseCrans): return trans if type(action) != int: raise TypeError - + touched = False if action>0 and action not in trans: trans.append(action) @@ -1773,36 +1773,36 @@ class BaseProprietaire(BaseClasseCrans): touched = True if touched and champ not in self.modifs: self.modifs.setdefault(champ, None) - + trans.sort() self._data[champ] = map(str, trans) - + return self._data[champ] ############################################################################# - + class Adherent(BaseProprietaire): """ Classe de définition d'un adhérent """ objectClass = 'adherent' idn = 'aid' filtre_idn = '(objectClass=adherent)' - + ### Méthodes Nom utilisée lors de l'affichage des propriétés ### (commune avec les classes AssociationCrans et Club) def Nom(self): """ Retourne prenom nom """ return "%s %s" % ( self.prenom() , self.nom() ) - + def nom(self, new=None): return self.__nom_prenom('nom', new) - + def prenom(self, new=None): return self.__nom_prenom('prenom', new) - + def __nom_prenom(self, champ, new): if new == None: return decode(self._data.get(champ, [''])[0]) - + l, new = preattr(new) new = new.capitalize() for c in new[:]: @@ -1812,53 +1812,53 @@ class Adherent(BaseProprietaire): raise ValueError(u"%s trop court." % champ.capitalize().replace(u'e', u'é')) if new[0] not in string.letters: raise ValueError(u"Le premier caractère du %s doit être une lettre" % champ.replace(u'e', u'é') ) - + self._set(champ, [new]) if self._data.has_key('gecos'): gecos = '%s %s' % tuple(map(lambda x: strip_accents(x.capitalize()), (self.prenom(), self.nom()))) self._data['gecos'] = [ preattr(gecos)[1] + ',,,' ] return new - + def tel(self, new=None): if new == None: return self._data.get('tel', [''])[0] - - if new != 'inconnu': + + if new != 'inconnu': l, new = preattr(new) if not new.isdigit() or l<6 or l>15: raise ValueError(u"Numéro de téléphone incorrect (il doit comporter uniquement des chiffres).") - + self._set('tel', [new]) return new - + def chbre(self, new=None): """ - Défini la chambre d'un adhérent, EXT pour personne extérieure au campus + Défini la chambre d'un adhérent, EXT pour personne extérieure au campus """ if new == None: return decode(self._data.get('chbre', [''])[0]) - + l, new = preattr(new) if l == 0: raise ValueError(u"Chambre incorrecte.") - + if new.upper() == 'EXT': # N'est pas ou plus sur le campus # Machine fixe ? # for m in self.machines(): # if not isinstance(m, MachineWifi): # raise ValueError(u'Un adhérent en dehors du campus ne doit pas avoir de machine fixe.') - + self._set('chbre', ['EXT']) return 'EXT' elif new.upper() == '????': # On ne sait pas ou est l'adhérent self._set('chbre', ['????']) return '????' - + new = new.capitalize() bat = new[0].lower() - + if bat in annuaires.chbre_prises.keys(): # On a la liste des chambres chbres = annuaires.chbre_prises[bat].keys() @@ -1867,20 +1867,20 @@ class Adherent(BaseProprietaire): aide = u"Chambre inconnue dans le batiment, les chambres valides sont :" a = 0 for c in chbres: - if len(c)>=3 and not c[:3].isdigit(): + if len(c)>=3 and not c[:3].isdigit(): # C'est un local club continue if int(a/14)>int((a-1)/14): aide += '\n ' - if c[0] != 'X': + if c[0] != 'X': aide += c.ljust(5) a = a+1 aide += u'\n' aide += u" " + annuaires.aide.get(bat, '') raise ValueError(aide) - - else: + + else: raise ValueError(u'Bâtiment inconnu.') - + # La chambre est valide, est-elle déja occupée ? test = self.exist('chbre=%s' % new) if test: @@ -1892,10 +1892,10 @@ class Adherent(BaseProprietaire): raise ValueError(u'Chambre déjà occupée.') else: raise ValueError(u'Chambre déjà occupée.', adh[0]) - + # Lock de la chambre self.lock('chbre', new) - + self._set('chbre', [new]) self._set('postalAddress', []) return new @@ -1908,39 +1908,39 @@ class Adherent(BaseProprietaire): if self.chbre() != 'EXT': # Personne sur le campus return [u'', u'', u'', u''] - else: + else: addr = self._data.get('postalAddress', ['','', '', ''])[:4] if len(addr) < 4: addr = addr + ['']*(4-len(addr)) return map(decode, addr) - + if type(new) != list and len(new) != 4: raise TypeError - + l_min = [ 2, 0, 5, 2 ] for i in range(0, 4): l, new[i] = preattr(new[i]) if l < l_min[i]: raise ValueError(u"Adresse incorrecte.") - + # Correction si necessaire if not new[1]: new[1] = ' ' - + self._set('postalAddress', new) return new - + def mail(self, new=None): if new == None: return decode(self._data.get('mail', [''])[0]) - + l, new = preattr(new) new = new.lower() - + #Emplacement de l'@ a = new.find('@') #Emplacement du . final b = new.rfind('.') - + # Les tests : # exactement un @ # 2 ou 3 caractères après le . final @@ -1949,12 +1949,12 @@ class Adherent(BaseProprietaire): or not ( l-b == 4 or l-b == 3) \ or a<1 or b-a<2: raise ValueError(u"Adresse mail incorrecte.") - + # Pas de caractèrs bizarres for l in new[:]: - if not l in (string.lowercase + string.digits + '-_.@'): + if not l in (string.lowercase + string.digits + '-_.@'): raise ValueError(u"Caractère interdits dans l'adresse mail (%s)." % l) - + # Pour les vicieux if sre.match('.*crans.(org|ens-cachan.fr)$', new): raise ValueError(u"Adresse mail @crans interdite ici") @@ -1962,13 +1962,13 @@ class Adherent(BaseProprietaire): # Il ne doit pas y avoir de compte self.supprimer_compte() self._set('mail', [new]) - + # L'adresse est supposée valide avant l'envoi du mail de bienvenue self.mail_invalide(False) - + # on renvoie le mail de bienvenue self.services_to_restart('mail_bienvenue', [new.encode('iso-8859-15')]) - + return new def email(self, new=None): @@ -1976,13 +1976,13 @@ class Adherent(BaseProprietaire): # pour la compatibilité entre les fonctions if new: self.mail(new) - + # ajout du @crans.org si nécessaire mail = self.mail() if not '@' in mail: mail += '@crans.org' return mail - + def mail_invalide(self, valeur=None): """ L'adresse est invalide. @@ -2040,7 +2040,7 @@ class Adherent(BaseProprietaire): """ self._set('mail', ['']) self._data['objectClass'] = ['adherent'] - + for c in [ 'uid', 'cn', 'shadowLastChange', 'shadowMax', 'shadowWarning', 'loginShell', 'userPassword', 'uidNumber', 'gidNumber', 'homeDirectory', 'gecos', @@ -2049,7 +2049,7 @@ class Adherent(BaseProprietaire): 'homepageAlias', 'derniereConnexion' ]: try: self._data.pop(c) except: pass - + def etudes(self, index_or_new): """ Retourne l'un des 3 champs études (selon index_or_new si entier) @@ -2069,16 +2069,16 @@ class Adherent(BaseProprietaire): # Pas grand chose à faire à part vérifier que ce sont bien des chaines if len(index_or_new) != 3: raise ValueError(u"Format études non valides.") - + new = ['', '', ''] for i in range(0, 3): n = preattr(index_or_new[i])[1] if n in new or n == '': raise ValueError(u"Etudes non valides.") - new[i] = n - + new[i] = n + self._set('etudes', new) - + return new def carteEtudiant(self, action=None): @@ -2101,10 +2101,10 @@ class Adherent(BaseProprietaire): return False return True - + def compte(self, login=None, uidNumber=0, hash_pass='', shell=config.login_shell): """ - Création d'un compte à un adhérent + Création d'un compte à un adhérent (la création se fait après l'écriture dans la base par la méthode save) Si login = None, retourne le compte de l'adhérent """ @@ -2113,13 +2113,13 @@ class Adherent(BaseProprietaire): return '' else: return self.mail() - + # Supression des accents et espaces login = strip_accents(login) - + l, login = preattr(login) login = login.lower() - + if login and not self.adherentPayant(): raise ValueError(u"L'adhérent ne paie pas de cotisation, il n'a pas droit à un compte.") @@ -2129,9 +2129,9 @@ class Adherent(BaseProprietaire): raise ValueError(u"L'adhérent à déjà un compte. Login : %s" % self._data['uid'][0]) else: return login - + for c in login[:]: - if not c in (string.letters + '-'): + if not c in (string.letters + '-'): raise ValueError(u"Seuls les caractères alphabétiques non accentués et le - sont autorisés dans le login.") if l < 2: raise ValueError(u"Login trop court.") @@ -2139,29 +2139,29 @@ class Adherent(BaseProprietaire): raise ValueError(u"Login trop long.") if login[0] == '-': raise ValueError(u"- interdit en première position.") - - if mailexist(login): + + if mailexist(login): raise ValueError(u"Login existant ou correspondant à un alias mail.", 1) home = '/home/' + login - if os.path.exists(home): + if os.path.exists(home): raise ValueError(u'Création du compte impossible : home existant', 1) - if os.path.exists("/var/mail/" + login): + if os.path.exists("/var/mail/" + login): raise ValueError(u'Création du compte impossible : /var/mail/%s existant' % login, 1) - + # Lock du mail self.lock('mail', login) self._data['mail'] = [login] if not 'compte' in self.modifs: self.modifs.setdefault('compte', None) - + # Création de l'alias canonique if self.nom() and self.prenom(): a = '%s.%s' % (self.prenom().capitalize(), self.nom().capitalize()) self.canonical_alias(a) - + self._data['objectClass'] = ['adherent', 'cransAccount', 'posixAccount', 'shadowAccount'] self._data['uid'] = [login] self._data['cn'] = [preattr(self.Nom())[1]] @@ -2171,7 +2171,7 @@ class Adherent(BaseProprietaire): self._data['loginShell'] = [shell] if hash_pass: self._data['userPassword'] = [hash_pass] - + if uidNumber: if self.exist('(uidNumber=%s)' % uidNumber): raise ValueError(u'uidNumber pris') @@ -2192,16 +2192,16 @@ class Adherent(BaseProprietaire): except: # Quelqu'un nous a piqué l'uid que l'on venait de choisir ! return self.compte(login, uidNumber, hash_pass, shell) - - self._data['uidNumber']= [str(uidNumber)] + + self._data['uidNumber']= [str(uidNumber)] self._data['gidNumber'] = [str(config.gid)] - + self._data['homeDirectory'] = [ preattr(home)[1] ] gecos = '%s %s' % tuple(map(lambda x: strip_accents(x.capitalize()), (self.prenom(), self.nom()))) self._data['gecos'] = [ preattr(gecos)[1] + ',,,' ] - + return decode(login) - + def canonical_alias(self, new=None): """ Retourne ou défini l'alias canonique""" if new == None: @@ -2210,35 +2210,35 @@ class Adherent(BaseProprietaire): else: a = strip_accents(new) a = preattr(a)[1] - + if not mailexist(a): # Attribution de l'alias, sinon on passe - + # Lock de canonicalAlias self.lock('canonicalAlias', a) - + # Attribution self._set('canonicalAlias', [a]) return a - + def droits(self, droits=None): """ droits est la liste des droits à donner à l'utilisateur """ """ ATTENTION : il y a une autre fonction droits_lights derrière celle-çi, pensez à faire vos modifs sur les deux """ - + if droits != None and 'cransAccount' not in self._data.get('objectClass', []): raise EnvironmentError(u'Il faut avoir un compte pour avoir des droits.') - + if droits == None: return map(decode, self._data.get('droits', [])) - + from user_tests import isadm if not isadm(): raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.') - - if type(droits) != list: + + if type(droits) != list: raise TypeError(u'Une liste est attendue') - + new = [] for droit in droits: if droit == '': continue @@ -2246,28 +2246,28 @@ class Adherent(BaseProprietaire): if droit not in droits_possibles: raise ValueError(u'Droit %s incorrect' % droit) new.append(droit.encode('utf-8')) - + ancien = self._data.get('droits',[]) for droit in new: if droit not in ancien: db.services_to_restart("mail_ajout_droits", self.compte().encode('latin-1') + ":" + droit) if new != self._data.get('droits', []): - self._set('droits', new) - + self._set('droits', new) + return new - + def droits_light(self, droits=None): """ droits_light ne verifie pas isadm et ne touche pas aux droits critiques """ - + if droits != None and 'cransAccount' not in self._data.get('objectClass', []): raise EnvironmentError(u'Il faut avoir un compte pour avoir des droits.') - + if droits == None: return map(decode, self._data.get('droits', [])) - - if type(droits) != list: + + if type(droits) != list: raise TypeError(u'Une liste est attendue') - + new = [] for droit in droits: if droit == '': continue @@ -2294,8 +2294,8 @@ class Adherent(BaseProprietaire): if droit in dc: raise("Droits critiques modifies (?)") if new != self._data.get('droits', []): - self._set('droits', new) - + self._set('droits', new) + return new def droitsGeles(self): @@ -2309,7 +2309,7 @@ class Adherent(BaseProprietaire): if d in l: reponse = False return reponse - + def rewriteMailHeaders(self, rewrite=None): """ Réécriture des entêtes mail avec l'alias canonique @@ -2318,7 +2318,7 @@ class Adherent(BaseProprietaire): Au niveau de la base, on considère l'absence ou la présence de rewriteMailHeaders=TRUE. """ - + # si l'adhérent n'a pas de compte, on lève une exception if not self.compte(): raise NotImplementedError, u"L'adhérent n'a pas de compte" @@ -2367,7 +2367,7 @@ class Adherent(BaseProprietaire): # Lors de l'inscription d'un nouvel adhérent, celui-ci n'a pas # encore d'historique. On retourne alors la date en cours. return time.time() - + def is_nounou( self ): return u"Nounou" in self.droits() @@ -2377,12 +2377,12 @@ class Club(BaseProprietaire): idn = 'cid' filtre_idn = '(objectClass=club)' objectClass = 'club' - + def Nom(self, new=None): """ Défini ou retourne le nom du club """ if new == None: return decode(self._data.get('nom', [''])[0]) - + l, new = preattr(new) new = new.capitalize() if l<2: @@ -2390,37 +2390,37 @@ class Club(BaseProprietaire): self._set('nom', [new]) return new - + def etudes(*args): """ Etudes bidon pour club """ return u'N/A' - + def nom(self): """ Retourne le nom du club, utilisé lors de la destruction """ return strip_accents(self.Nom()) - + def carteEtudiant(self, pd=None): return [ ann_scol ] - + def responsable(self, adher=None): """ Responsable du club, adher doit être une instance de la classe adhérent """ if adher == None: aid = decode(self._data.get('responsable', [''])[0]) - if aid: + if aid: return self.search('aid=%s' % aid)['adherent'][0] else: return '' - + if adher.__class__ != Adherent: raise ValueError - + if not adher.id(): raise AttributeError(u'Adhérent invalide') - + self._set('responsable', [adher.id()]) return adher def responsables(self, ajouter=None, retirer=None): - """ + """ NE PAS UTILISER CETTE FONCTION Elle correspond a un champ qui n'existe pas encore dans le schema @@ -2437,7 +2437,7 @@ class Club(BaseProprietaire): resultat.append(db.search('uid ='+uid)['adherent'][0]) return adher - + def chbre(self, new=None): """ Défini le local du club new doit être une des clefs de l'annuaire locaux_clubs""" @@ -2447,12 +2447,12 @@ class Club(BaseProprietaire): annu = annuaires.locaux_clubs() if new not in annu.keys(): raise ValueError(u'Local invalide', annu) - + self._set('chbre', [new]) return new - + def local(self): - """ Retourne le local à partir de la chambre enregistrée et + """ Retourne le local à partir de la chambre enregistrée et de la conversion avec l'annuaire locaux_clubs """ annu = annuaires.locaux_clubs() return decode(annu.get(self.chbre(), '')) @@ -2469,36 +2469,36 @@ class Club(BaseProprietaire): if not sre.match('^[a-z0-9]*[a-z]+[a-z0-9-]*$', login): raise ValueError(u'Login incorrect') login = preattr(login)[1] - + if 'posixAccount' in self._data['objectClass']: if login != self._data['uid']: # A déja un compte raise ValueError(u"Le club à déjà un compte. Login : %s" % self._data['uid'][0]) else: return login - + if mailexist(login) and not os.system('/usr/sbin/list_lists | grep -qi %s' % login): # la 2ème vérif est pour vérifier que ce n'est pas la ML du club raise ValueError(u"Login existant ou correspondant à un alias mail.", 1) home = '/home/' + login.replace('-', '/', 1) - if os.path.exists(home): + if os.path.exists(home): raise ValueError(u'Création du compte impossible : home existant', 1) if os.path.exists("/var/mail/"+login): raise ValueError(u'Création du compte impossible : /var/mail/%s existant'%login, 1) - + # Lock du mail self.lock('mail', login) if not 'compte' in self.modifs: self.modifs.setdefault('compte', None) - + self._data['objectClass'] = ['club', 'cransAccount', 'posixAccount', 'shadowAccount'] self._data['uid'] = [ login ] self._data['cn'] = [ preattr(self.Nom())[1] ] self._data['loginShell' ] = [ config.club_login_shell ] - + # Détermination de l'uid uidNumber = 1000 while self.exist('(uidNumber=%s)' % uidNumber): @@ -2508,32 +2508,32 @@ class Club(BaseProprietaire): except: # Quelqu'un nous a piqué l'uid que l'on venait de choisir ! return self.compte(login) - - self._data['uidNumber']= [ str(uidNumber) ] + + self._data['uidNumber']= [ str(uidNumber) ] self._data['gidNumber'] = [ str(config.club_gid) ] self._data['homeDirectory'] = [ preattr(home)[1] ] - + return decode(login) def email(self): """ Retourne l'adresse mail du responsable """ return self.responsable().email() - + class Machine(BaseClasseCrans): """ Classe de définition d'une machine """ idn = 'mid' filtre_idn = '(|(objectClass=machineFixe)(objectClass=machineWifi)' filtre_idn += '(objectClass=machineCrans)(objectClass=borneWifi))' - + def __init__(self, parent_or_tuple, mode='', conn=None): - """ + """ parent_or_tuple est : - * soit une instance d'une classe pouvant posséder une machine + * soit une instance d'une classe pouvant posséder une machine (Adherent, Club ou AssociationCrans), la nouvelle machine lui sera alors associée. - * soit directement le tuple définissant une machine (tel que + * soit directement le tuple définissant une machine (tel que retourné par les fonctions de recherche ldap) - + Pour l'édition d'une machine, mode devra être égal à 'w' Attention, si mode='w' mais si l'objet est déja locké il n'y a pas d'erreur, vérifier l'obtention du lock grâce à la valeur de @@ -2549,14 +2549,14 @@ class Machine(BaseClasseCrans): self.conn = conn if not self.conn: self.connect() - + self.modifs = {} t = parent_or_tuple.__class__ if t == tuple: # Initialisation avec données fournies self.dn = parent_or_tuple[0] if mode == 'w': - try: + try: self.lock(self.idn, self.id()) self._modifiable = 'w' except EnvironmentError , c: @@ -2566,10 +2566,10 @@ class Machine(BaseClasseCrans): # Utile pour construire l'instruction LDAP de modif self._init_data = parent_or_tuple[1].copy() self._data = parent_or_tuple[1] - + # Propriéraire inconnu mais ce n'est pas grave self.__proprietaire = None - + elif t in [Adherent, Club, AssociationCrans] and mode != 'w': # Machine vide self.__proprietaire = parent_or_tuple @@ -2577,36 +2577,36 @@ class Machine(BaseClasseCrans): self._data = {'objectClass': [self.objectClass]} self._init_data = {} self._modifiable = 'w' - + chbre = self.__proprietaire.chbre() # if chbre == 'EXT' and mode == 'fixe': # raise ValueError(u'Il faut une chambre pour pouvoir posséder une machine fixe') if chbre == '????': raise ValueError(u'ERREUR: la chambre du propriétaire est inconnue') - + else: raise TypeError(u'Arguments invalides') - + def Nom(self): """ Retourne le nom de la machine """ return self.nom() - + def mac(self, mac=None, multi_ok=0): """ Défini ou retourne l'adresse mac de la machine Adresse valide si: 12 caractères hexa avec - ou : comme séparateurs - non nulle + non nulle Stoque l'adresse sous la forme xx:xx:xx:xx:xx:xx Si multi_ok = 1 permet d'avoir plusieur fois la même mac sur le réseau """ - + if mac == None: return decode(self._data.get('macAddress', [''])[0]) - + mac = format_mac(mac) - + # La mac serait-elle déjà connue ? if not multi_ok and self.exist('macAddress=%s' % mac): raise ValueError(u'Mac déja utilisée sur le réseau.', 1) @@ -2616,7 +2616,7 @@ class Machine(BaseClasseCrans): raise ValueError(u"Il s'agit de l'unique adresse MAC achetée par nVidia pour ses cartes réseau. Il faut changer cette adresse.", 2) elif mac[0:11] == "44:45:53:54": raise ValueError(u"Il s'agit de l'adresse MAC d'une interface PPP.", 2) - + # Le test final : vendeur connu prefix = mac[:8].upper() + ' ' vendor = '' @@ -2631,7 +2631,7 @@ class Machine(BaseClasseCrans): if not multi_ok and not vendor: raise ValueError(u"Le constructeur correspondant à cette adresse MAC ne peut être trouvé.\nL'adresse MAC correspond peut-être à un pont réseau, désactivez ce pont réseau.\nContactez nounou si la MAC est bien celle d'une carte.", 3) - + # Lock de la mac self.lock('macAddress', mac) @@ -2642,52 +2642,52 @@ class Machine(BaseClasseCrans): """ Vérification de la validité d'un nom de machine """ # Supression des accents new = strip_accents(unicode(new, 'iso-8859-15')) - + l, new = preattr(new) new = new.lower() l = len(new.split('.')[0]) - + if l<2: raise ValueError(u"%s trop court." % champ.capitalize()) - + if self.proprietaire().__class__ != AssociationCrans: new = new.split('.', 1)[0] for c in new[:]: if not c in (string.letters + string.digits + '-'): raise ValueError(u"Seuls les caractères alphabétiques minuscules et les - sont autorisés pour %s" % champ) - + if l>17: raise ValueError(u"%s trop long." % champ.capitalize()) - - if not new[0].isalpha(): + + if not new[0].isalpha(): raise ValueError(u"Le premier caractère du champ %s doit être alphabétique" % champ) - + # Ajout du domaine si necessaire if new.find('.') == -1: - try: + try: new += '.' + config.domains[self.objectClass] except: raise RuntimeError(u"%s : domaine non trouvé pour %s" % (champ.capitalize(), self.__class__)) - + # Pas déja pris ? if self.exist('(|(host=%s)(hostAlias=%s))' % (new, new)): raise ValueError(u"%s : nom déjà pris" % champ.capitalize()) - + # Lock host self.lock('host', new) - + return new def nom(self, new=None): """ Défini ou retourne le nom de machine. - Un nom de machine valide ne comporte que des caractères + Un nom de machine valide ne comporte que des caractères alphabétiques minuscules et le - """ if new == None: return decode(self._data.get('host', [''])[0]) - new = self.__host_alias('nom de machine', new) + new = self.__host_alias('nom de machine', new) self._set('host', [new]) return new.split('.')[0] @@ -2707,7 +2707,7 @@ class Machine(BaseClasseCrans): return annuaires.chbre_prises[chbre[0].lower()][chbre[1:]] except: return 'N/A' - + # Attribution de la prise new = preattr(new)[1] if new == 'N/A': @@ -2721,7 +2721,7 @@ class Machine(BaseClasseCrans): self._set('prise', [new.upper()]) return new - + def alias(self, new=None): """ Création ou visualisation des alias d'une machine. @@ -2732,7 +2732,7 @@ class Machine(BaseClasseCrans): liste = list(self._data['hostAlias']) if new == None: return map(decode, liste) - + if type(new) == list: # Modif index = new[0] @@ -2743,7 +2743,7 @@ class Machine(BaseClasseCrans): return liste else: index = -1 - + # Test si valide new = self.__host_alias('alias', new) @@ -2751,10 +2751,10 @@ class Machine(BaseClasseCrans): liste[index] = new else: liste = liste + [new] - + self._set('hostAlias', liste) return liste - + def ip(self, ip=None): """ Défini ou retourne l'IP de la machine. @@ -2769,9 +2769,9 @@ class Machine(BaseClasseCrans): return '' else: return '' - + l, ip = preattr(ip) - + # Dans quel réseau la machine doit-elle être placée ? if isinstance(self, MachineWifi): net = config.NETs['wifi-adh'] @@ -2792,7 +2792,7 @@ class Machine(BaseClasseCrans): else: net = config.NETs["gratuit"] pool_ip = lister_ip_dispo("gratuit") - + if ip == '': # On va prendre choisir une IP au hasard dans le pool des IP dispo random.shuffle(pool_ip) @@ -2805,9 +2805,9 @@ class Machine(BaseClasseCrans): if not len(pool_ip): raise RuntimeError(u"Plus d'IP libres dans %s." % string.join(net, ' et ')) - + else: - # L'ip est elle dans le bon sous-réseau ? + # L'ip est elle dans le bon sous-réseau ? # (accessoirement teste si l'IP est valide et ne correspond pas # à l'adresse de broadcast ou de réseau) if not iptools.AddrInNet(ip, net): @@ -2817,10 +2817,10 @@ class Machine(BaseClasseCrans): # L'ip est-elle déja allouée ? if self.exist('ipHostNumber=%s' % ip): raise ValueError(u'IP déjà prise.') - + # Lock ip self.lock('ipHostNumber', ip) - + self._set('ipHostNumber', [ip]) return ip @@ -2828,7 +2828,7 @@ class Machine(BaseClasseCrans): """ Liste des réseaux vers lesquels on ne compte pas l'upload Cette liste est transférée dans la base postgres de komaz - Pour ajouter un réseau new doit être la chaîne + Pour ajouter un réseau new doit être la chaîne représentant le réseau à ajouter Pour modifier new doit être une liste de la forme : [ index du nouveau réseau , nouveau réseau ] @@ -2838,7 +2838,7 @@ class Machine(BaseClasseCrans): self._data['exempt'] = [] liste = list(self._data['exempt']) if new == None: return map(decode, liste) - + if type(new) == list: # Modif index = new[0] @@ -2875,9 +2875,9 @@ class Machine(BaseClasseCrans): self.__proprietaire = Club(res[0], self._modifiable, self.conn) else: self.__proprietaire = AssociationCrans(self.conn) - + return self.__proprietaire - + def save(self): """ Enregistre la machine courante dans la base LDAP @@ -2886,9 +2886,9 @@ class Machine(BaseClasseCrans): from user_tests import isadm if self.proprietaire().__class__ == AssociationCrans and not (isadm() or user_tests.getuser() == 'www-data'): raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.') - + ret = '' - + # Besoin de redémarrer les firewalls ? if 'ipHostNumber' in self.modifs or 'macAddress' in self.modifs: reconf_ip = self._init_data.get('ipHostNumber', []) @@ -2898,10 +2898,10 @@ class Machine(BaseClasseCrans): # On vire les doublons dans reconf_ip reconf_ip = list(dict(zip(reconf_ip, [None]*len(reconf_ip)))) - + # Enregistrement self._save() - + # Clef IPsec if 'ipsec' in self.modifs: ret += coul(u'Clef IPsec de la machine : %s\n' % self.ipsec(), 'cyan') @@ -2919,11 +2919,11 @@ class Machine(BaseClasseCrans): 'portUDPin' in self.modifs or 'portUDPout' in self.modifs: self.services_to_restart('komaz-ports', [self.ip()]) self.services_to_restart('mail_modif', ['ip=%s' % self.ip()]) - + # Reconfiguration DNS ? if 'host' in self.modifs or 'ipHostNumber' in self.modifs or 'hostAlias' in self.modifs: self.services_to_restart('dns') - + # Reconfiguration bornes wifi ? if 'canal' in self.modifs or 'puissance' in self.modifs or 'nvram' in self.modifs or 'hotspot' in self.modifs: self.services_to_restart('conf_wifi_ng') @@ -2932,13 +2932,13 @@ class Machine(BaseClasseCrans): if isinstance(self, MachineWifi) or isinstance(self, BorneWifi) \ and ('ipHostNumber' in self.modifs or 'host' in self.modifs or 'macAddress' in self.modifs): self.services_to_restart('conf_wifi_ng') - + # Regénération blackliste nécessaire ? bl = self.blacklist_actif() if bl and ('ipHostNumber' in self.modifs or 'host' in self.modifs): for s in bl: self.services_to_restart(s, [self.ip()]) - + # Regénération de l'autostatus et mail de changmement ? if self.proprietaire().__class__ == AssociationCrans: self.services_to_restart('autostatus') @@ -2950,17 +2950,17 @@ class Machine(BaseClasseCrans): if 'exempt' in self.modifs: self.services_to_restart('mail_modif', ['ip=%s' % self.ip()]) - + # Synchronisation avec la base pgsql pour la liste des machines if 'ipHostNumber' in self.modifs: self.services_to_restart('surveillance_machines') - + # Remise à zéro self.modifs = {} - + # Message de sortie ret += coul(u"Machine %s enregistrée avec succès." % self._data['host'][0], 'vert') - + return ret def delete(self, comment=''): @@ -2969,7 +2969,7 @@ class Machine(BaseClasseCrans): if self.proprietaire().__class__ == AssociationCrans and not isadm(): raise EnvironmentError(u'Il faut être administrateur pour effectuer cette opération.') - + self.proprio = self.__proprietaire.Nom() # On met dans un coin le nom du proprio self.__proprietaire = None # On oublie le propriétaire self._delete(self.dn, comment) @@ -2980,35 +2980,35 @@ class Machine(BaseClasseCrans): self.services_to_restart('ragnarok-dhcp') else: self.services_to_restart('rouge-dhcp') - + if self.exempt(): self.services_to_restart('surveillance_exemptions') self.services_to_restart('surveillance_machines') - + self.services_to_restart('dns') self.services_to_restart('macip', [self.ip()]) self.services_to_restart('classify', [self.ip()]) - + def portTCPin(self, ports=None): """ Ports TCP ouverts depuis l'extérieur pour la machine """ return self.__port(ports, 'portTCPin') - + def portTCPout(self, ports=None): """ Ports TCP ouverts vers l'extérieur pour la machine """ return self.__port(ports, 'portTCPout') - + def portUDPin(self, ports=None): """ Ports UDP ouverts vers l'extérieur pour la machine """ return self.__port(ports, 'portUDPin') - + def portUDPout(self, ports=None): """ Ports UDP ouverts vers l'extérieur pour la machine """ return self.__port(ports, 'portUDPout') - + def __port(self, ports, champ): if ports == None: return self._data.get(champ, []) - + # Les ports doivent être de la forme [port][:[port]]. On met # la spécification sous une forme qui donne un tri intéressant def parse(x): @@ -3018,7 +3018,7 @@ class Machine(BaseClasseCrans): return map(lambda x: x and int(x) or '', liste) except: raise ValueError(u'Spécification de ports incorrecte : %s' % x) - + ports = map(parse, ports) ports.sort() self._set(champ, map(lambda x: ':'.join(map(str, x)), ports)) @@ -3036,7 +3036,7 @@ class MachineFixe(Machine): class MachineWifi(Machine): """ Classe de définition d'une machine wifi """ - + def __init__(self, parent_or_tuple, typ='wifi', conn=None): Machine.__init__(self, parent_or_tuple, typ, conn) if not isinstance(parent_or_tuple, tuple): @@ -3050,29 +3050,29 @@ class MachineWifi(Machine): """ if clef == None: return decode(self._data.get('ipsec', [''])[0]) - + if clef == True: # Génération clef = '' for i in range(22): - clef += random.choice(filter(lambda x: x != 'l' and x != 'o', string.lowercase) + + clef += random.choice(filter(lambda x: x != 'l' and x != 'o', string.lowercase) + filter(lambda x: x != '1' and x != '0', string.digits)) self._set('ipsec', [clef]) - + return clef - + class MachineCrans(Machine): """ Classe de définition d'une machine du Crans """ - + def __init__(self, parent_or_tuple, typ='fixe', conn=None): Machine.__init__(self, parent_or_tuple, typ, conn) class BorneWifi(Machine): """ Classe de définition d'une borne wifi """ - + def __init__(self, parent_or_tuple, typ='borne', conn=None): Machine.__init__(self, parent_or_tuple, typ, conn) if not isinstance(parent_or_tuple, tuple): @@ -3086,7 +3086,7 @@ class BorneWifi(Machine): mac = self.mac().split(":") mac[-1] = "%0.2x" % (int(mac[-1], 16) + 2) return ":".join(mac) - + def hotspot(self, new=None): """ Cette borne est-elle partagée avec l'ENS ? """ if new == None: @@ -3130,8 +3130,8 @@ class BorneWifi(Machine): else: lcanal3.append("%d-%d" % (c[0], c[1])) return ",".join(lcanal3) - - + + try: new = int(new) if new < 0 or new > 13: raise @@ -3154,7 +3154,7 @@ class BorneWifi(Machine): new = new + (1 << (c - 1)) except: raise ValueError(u'Canal invalide : doit être entre 0 et 13 ou une liste de canaux') - + self._set('canal', [str(new)]) return new @@ -3162,13 +3162,13 @@ class BorneWifi(Machine): """ Attribution ou visualisation de la puissance d'une borne wifi """ if not new: return self._data.get('puissance', [''])[0] - + try: new = int(new) if new < -99 or new > 99: raise except: raise ValueError(u'Puissance invalide : doit être entre -99 et 99') - + self._set('puissance', [str(new)]) return new @@ -3207,7 +3207,7 @@ class BorneWifi(Machine): que new vaut False, None ou autre chose. """ current = self._data.get('nvram', [])[:] - + if champ == None: if type(new) is list: self._set('nvram', new) @@ -3228,7 +3228,7 @@ class BorneWifi(Machine): if new == False: return index != None and current_champ or None - + elif new == None: if index != None: del current[index] @@ -3250,13 +3250,13 @@ class Facture(BaseClasseCrans): filtre_idn = '(objectClass=facture)' def __init__(self, parent_or_tuple, mode='', conn=None): - """ + """ parent_or_tuple est : * soit une instance d'une classe pouvant posséder une facture (Adherent, Club), la nouvelle facture lui sera alors associée. - * soit directement le tuple définissant une facture (tel que + * soit directement le tuple définissant une facture (tel que retourné par les fonctions de recherche ldap) - + Pour l'édition d'une facture, mode devra être égal à 'w' Attention, si mode='w' mais si l'objet est déja locké il n'y a pas d'erreur, vérifier l'obtention du lock grâce à la valeur de @@ -3269,14 +3269,14 @@ class Facture(BaseClasseCrans): self.conn = conn if not self.conn: self.connect() - + self.modifs = {} t = parent_or_tuple.__class__ if t == tuple: # Initialisation avec données fournies self.dn = parent_or_tuple[0] if mode == 'w': - try: + try: self.lock(self.idn, self.id()) self._modifiable = 'w' except EnvironmentError , c: @@ -3286,10 +3286,10 @@ class Facture(BaseClasseCrans): # Utile pour construire l'instruction LDAP de modif self._init_data = parent_or_tuple[1].copy() self._data = parent_or_tuple[1] - + # Propriéraire inconnu mais ce n'est pas grave self.__proprietaire = None - + elif t in [Adherent, Club] and mode != 'w': # Facture vide self.__proprietaire = parent_or_tuple @@ -3297,10 +3297,10 @@ class Facture(BaseClasseCrans): self._data = {'objectClass': [self.objectClass], 'modePaiement':['paypal']} self._init_data = {} self._modifiable = 'w' - + else: raise TypeError(u'Arguments invalides') - + def numero(self): """ Retourne le numéro de facture """ fid = self._data.get('fid',[None])[0] @@ -3311,9 +3311,9 @@ class Facture(BaseClasseCrans): def nom(self): """ Utilisé pour la fonction delete() """ return "Facture%s" % self.numero() - + Nom = nom - + def proprietaire(self): """ retroune le propriétaire de la facture (classe Adherent ou Club) @@ -3322,7 +3322,7 @@ class Facture(BaseClasseCrans): # le proprio en w if self.__proprietaire and self.__proprietaire._modifiable != self._modifiable: self.__proprietaire = None - + # récupère le proprio si ce n'est pas encore fait if not self.__proprietaire: res = self.conn.search_s(','.join(self.dn.split(',')[1:]), 0) @@ -3332,7 +3332,7 @@ class Facture(BaseClasseCrans): self.__proprietaire = Club(res[0], self._modifiable, self.conn) else: raise ValueError, u'Propriétaire inconnu' - + return self.__proprietaire def modePaiement(self, new=None): @@ -3340,67 +3340,67 @@ class Facture(BaseClasseCrans): Définit ou retourne le mode de paiement. Le mode de paiement doit être une chaine de caractère """ - + # modification du mode de paiement if new != None: if self.recuPaiement(): raise ValueError, u'Facture déja payée' - + if not self._modifiable: raise NotImplementedError, "La facture n'est pas modifiable" - + if new not in ['liquide','cheque','paypal']: raise ValueError, u'Mode de paiement non accepté' - + self._set('modePaiement', [new]) - + return decode(self._data.get('modePaiement', [None])[0]) def recuPaiement(self, new=None): """ Définit ou retourne qui a recu le paiement """ - + # on vérifie que la facture n'est pas déja payéee if new and self._data.get('recuPaiement', []): raise ValueError, u'Facture déja payée' - + # modification de la valeur if new != None: # on vérifie que la facture est modifiable if not self._modifiable and new: raise NotImplementedError, "La facture n'est pas modifiable" - + # on crédite les articles, si c'est pas possible, la metode # levera une exeption self._crediter() # ajout des frais à la liste d'articles self.ajoute(self._frais()) - + # modifie la base ldap self._set("recuPaiement",[new]) - + # renvoie la valeur trouvée dans la base return self._data.get("recuPaiement",[None])[0] def _del_recu_paiement(self): """ Pour test """ self._set("recuPaiement",[]) - + def _crediter(self): """ Credite les articles à son propriétaire """ - + # si la facture n'existe pas encore, on la sauve pour générer un numéro if not self._data.has_key('fid'): self.save() - + # on vérifie que le propriétaire est modifiable if not self.proprietaire()._modifiable: raise SystemError, u"Impossible de créditer les articles, le proprietaire n'est pas modifiable" - + # on crédite les articles for art in self._articles(): # solde impression @@ -3413,37 +3413,37 @@ class Facture(BaseClasseCrans): """ Retourne une liste d'articles correspondants aux divers frais """ - + arts = [] # aucun frais pour une facture payée, ils sont intégrés aux articles if self.recuPaiement(): return [] - + # frais de paiement par paypal if self.modePaiement() == 'paypal': # 25 centimes pour le paiement paypal s = 0.25 - + # et on ajoute 3.5% du montant for art in self._articles(): s += 0.035 * art['nombre'] * art['pu'] # arrondissage-tronquage s = float(int(s*100)/100.0) - + # ajoute à la liste d'articles de frais arts.append( {'code':'FRAIS','designation':'Frais de tansaction PayPal','nombre':1,'pu':round(s,2)} ) - + return arts - + def _articles(self, arts = None): """ Retourne ou modifie la liste des articles de la base """ - + # modifie la liste des articles if arts != None: - self._set('article',['%s~~%s~~%s~~%s' % (art['code'],art['designation'],str(art['nombre']),str(art['pu'])) for art in arts]) - + self._set('article',['%s~~%s~~%s~~%s' % (art['code'],art['designation'],str(art['nombre']),str(art['pu'])) for art in arts]) + # charge la liste des articles arts = [] for art in self._data.get("article",[]): @@ -3453,9 +3453,9 @@ class Facture(BaseClasseCrans): 'nombre' : int(art[2]), 'pu' : float(art[3]) } arts.append(art) - + return arts - + def ajoute(self, ajoute): """ Ajoute un/des article(s) à la facture ajoute est un article ou une liste d'articles @@ -3463,10 +3463,10 @@ class Facture(BaseClasseCrans): # on ne eut pas modifier une facture payée if self.recuPaiement(): raise ValueError, u'On ne peut pas modifier une facture payée' - + # charge la liste des articles arts = self._articles() - + # ajoute les articles if type(ajoute)==dict: ajoute = [ajoute] @@ -3480,10 +3480,10 @@ class Facture(BaseClasseCrans): if '~~' in ' '.join([str(x) for x in art.values()]): raise ValueError, u'Ne pas mettre de ~~ dans les champs' arts.append(art) - + # enregistre la nouvelle liste self._articles(arts) - + def supprime(self, supprime): """ Supprime un/des article(s) à la facture arts est un article ou une liste d'articles @@ -3491,20 +3491,20 @@ class Facture(BaseClasseCrans): # on ne eut pas modifier une facture payée if self.recuPaiement(): raise ValueError, u'On ne peut pas modifier une facture payée' - + # charge la liste des articles arts = self._articles() - + # on supprime les anciens articles if type(supprime)==dict: - supprime = [supprime] + supprime = [supprime] if type(supprime)==list: for art in supprime: arts.remove(art) - + # enregistre la nouvelle liste self._articles(arts) - + def articles(self): """ Retourne la liste des articles. @@ -3513,9 +3513,9 @@ class Facture(BaseClasseCrans): 'designation' : string, 'nombre' : int, 'pu' : int/float } - """ + """ return self._articles() + self._frais() - + def total(self): """ Calcule le total de la facture, frais compris @@ -3524,7 +3524,7 @@ class Facture(BaseClasseCrans): for art in self.articles(): s += art['nombre'] * art['pu'] return s - + def urlPaypal(self, useSandbox = False, businessMail = "paypal@crans.org", return_page=None, cancel_return_page=None): """ Retourne l'url paypal pour le paiement de cette facture @@ -3550,9 +3550,9 @@ class Facture(BaseClasseCrans): url += "&item_name_%d=%s" % (item_id, item['designation']) url += "&amount_%d=%s" % (item_id, item['pu']) url += "&quantity_%d=%s" % (item_id, int(item['nombre'])) - + return url - + def save(self): """ Enregistre la facture dans la base LDAP @@ -3595,7 +3595,7 @@ class _FakeProprio(CransLdap): m = [] for r in res: m.append(self.make(r)) - return m + return m class AssociationCrans(_FakeProprio): """ Classe définissant l'assoce (pour affichage de ses machines) """ @@ -3622,16 +3622,16 @@ def crans_ldap(readonly=False): if __name__ == '__main__': import sys - + usage = """Usage %s [ACTION] --lock : donne la liste des locks actifs --purgelock : supprime tous les locks de la base LDAP --menage : supprime les machines des anciens adhérents"""%sys.argv[0] - + if len(sys.argv) != 2: print usage sys.exit(1) - + elif '--lock' in sys.argv: print "Liste des locks" for lock in crans_ldap().list_locks():