models.py 8.44 KB
Newer Older
Daniel STAN's avatar
Daniel STAN committed
1 2
# -*- coding: utf-8 -*-

3 4
from django.contrib import messages

5
from intranet import settings, conn_pool
6 7 8 9 10
class asterisk_reload_conf_debug:
    @classmethod
    def reload_config(self, fname):
        pass

11
# TODO weird stuff here
12
try:
13
    from sip import asterisk_reload_conf
14 15 16 17
except ImportError:
    if not settings.DEBUG:
        raise
    asterisk_reload_conf = asterisk_reload_conf_debug
18

Daniel STAN's avatar
Daniel STAN committed
19 20
from django.db import models
from django.contrib.auth.models import User
21
import hashlib
22
import os
23

24
# Description de la configuration générée pour asterisk
25
# TODO eurk
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
asterisk_conf = {
    'modules':{
        'sip':{
            'reload_fields':["caller_id","password"],
            'gen_conf':lambda _:Profile.asterisk_config, # pour pouvoir appeler Profile paresseusement
            'conf_file':'sip_accounts',
        },
        'voicemail':{
            'reload_fields':["voicemail_password"],
            'gen_conf':lambda _:Profile.asterisk_voicemail_config,
            'conf_file':'sip_accounts_voicemail',
        },
        'dialplan':{
            'reload_fields':["num"],
            'gen_conf':lambda _:Profile.asterisk_alias_config,
            'conf_file':'sip_accounts_alias',
        },
   },
   'conf_path':'/usr/scripts/var/sip/'
}


Daniel STAN's avatar
Daniel STAN committed
48 49 50 51 52 53
class Profile(models.Model):
    """ Profil d'utilisateur Voip """

    class Meta:
        verbose_name = u'Profil SIP'
        verbose_name_plural = u'Profils SIP'
54

Daniel STAN's avatar
Daniel STAN committed
55 56 57 58 59
    CALLER_IDS = [
        ('full_name', u'Nom complet'),
        ('number', u'Numéro de Téléphone'),
        ('both', u'Nom et numéro de téléphone'),
    ]
60

61
    """ Propriétaire du profil. Devrait être un compte Cr@ns """
Daniel STAN's avatar
Daniel STAN committed
62
    user = models.OneToOneField(User, primary_key=True)
63 64 65

    """ Mot de passe. Hashé en md5 avec un sel
        (md5($num_asterisk:asterisk:$pass)) """
Daniel STAN's avatar
Daniel STAN committed
66
    password = models.CharField(u"Mot de passe", max_length=32, null=False, blank=False)
67 68

    """ Autoriser ou non la publication dans l'annuaire """
69
    published = models.BooleanField(u"Publication dans l'annuaire", help_text=u"Affichage du numéro et du nom dans l'annuaire du Crans", default=False)
70 71

    """ Choix du format pour l'identification de l'appelant """
72
    caller_id = models.CharField(u"Affichage de l'appelant", max_length=9, choices=CALLER_IDS, help_text=u"Nom à afficher lors d'un appel")
73

74
    """ Numéro de téléphone, on utilise un varchar afin de pouvoir placer des 0
75
        initiaux et des éventuels #"""
76
    num = models.CharField(u"Numéro de téléphone", max_length=10, null=False, blank=False)
77
    voicemail_password = models.CharField(u"Mot de passe boîte vocale", max_length=6, null=False, blank=False)
78

79 80 81 82 83 84 85 86 87 88 89 90

    def what_to_reload(self):
        """ Defini les modules asterisk dont il faut régénérer la configuration """
        def has_changed(self, field):
            if not self.pk:
                return True
            try:
                old_value = self.__class__._default_manager.\
                         filter(pk=self.pk).values(field).get()[field]
            except self.DoesNotExist:
                return True
            return not getattr(self, field) == old_value
Daniel STAN's avatar
Daniel STAN committed
91

92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
        modules={}
        for module in asterisk_conf['modules'].keys():
            modules[module]=reduce( # List.fold_left
                lambda x,y: x or has_changed(self,y),
                asterisk_conf['modules'][module]['reload_fields'],
                False
            )
        return modules

    def reload_what_it_is_to_reload(self,modules):
        """ Écrit la configuration des modules asterisk à recharger, puis les recharge """
        def writeconf(gen_conf_module,path):
            conf=""
            for profile in Profile.objects.order_by('user__username').all():
                conf+=gen_conf_module(profile).encode('utf-8') + '\n'
107
            tmppath = path + '.new'
108
            if settings.LOCATION == 'o2' or not settings.DEBUG:
109 110 111
                with open(tmppath,'w') as fout:
                    fout.write(conf)
                os.rename(tmppath, path)
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
        for module in asterisk_conf['modules'].keys():
            if modules[module]:
                writeconf(
                    asterisk_conf['modules'][module]['gen_conf'](''),
                    asterisk_conf['conf_path'] + asterisk_conf['modules'][module][ 'conf_file']
                )
        
        if reduce(lambda x,y: x or y, modules.values(), False):
            asterisk_reload_conf.reload_config('all')
        else:
            for module in asterisk_conf['modules'].keys(): 
                if modules[module]:
                    asterisk_reload_conf.reload_config(module)

    def delete(self):
        ret=super(Profile,self).delete()
        self.reload_what_it_is_to_reload(self.what_to_reload())
        return ret

    def save(self):
        to_reload=self.what_to_reload()
        ret=super(Profile,self).save()
        self.reload_what_it_is_to_reload(to_reload)
        return ret

137
    def set_user(self, new_user):
138 139 140 141
        """ Défini l'utilisateur propriétaire du profil. Cette fonction calcule
        également le numéro de téléphone correspondant, à partir de la base LDAP.
        IMPORTANT: changer d'utilisateur (donc de numéro de téléphone) casse
        le mot de passe, qui devra être redéfini."""
142 143
        self.user = new_user
        try:
144
            aid = conn_pool.get_user(new_user)['aid'][0].value
145
            sip_ext = '1%04d' % int(str(aid)[:4])
146
        except KeyError:
147 148 149
            if not settings.DEBUG:
                raise Exception("Not allowed") # FIXME ?
            sip_ext = '9%04d' % int(str(new_user.pk)[:4])
150 151 152
        self.num = sip_ext

    def set_password(self, new_pass):
153 154 155 156 157 158 159
        """ Définition du mot de passe.
        IMPORTANT: le mot de passe est stocké hashé dans la base de donnée.
        Il faut donc avoir préalablement défini le numéro de téléphone (et
        donc l'utilisateur) avant d'appeler cette fonction.
        """
        if self.num == '':
            raise Exception('Set user/num first')
160
        self.password = hashlib.md5("%s:asterisk:%s" % (self.num, new_pass)).hexdigest()
Daniel STAN's avatar
Daniel STAN committed
161 162

    def __unicode__(self):
163
        return u'%s (%s)' % (self.user.username, self.num)
Daniel STAN's avatar
Daniel STAN committed
164

165 166 167 168
    def asterisk_alias_config(self):
        """Génération de la ligne de conf asterisk mappant login à son numéro correspondant"""
        return "exten => %(login)s,1,Goto(%(num)s,1)" % {'num':self.num,'login':self.user.username.split('@')[0]}

169
    def asterisk_voicemail_config(self):
170
        """ Génération de la ligne de conf asterisk activant la boîte vocale"""
171
        return u"%(num)s => %(mail_pass)s,%(full_name)s,%(mail)s,,attach=%(attach)s|delete=%(delete)s" % {
172
       'num':self.num,'mail_pass':self.voicemail_password,'full_name':self.user.get_full_name(),
173 174 175
       'mail':self.user.username,'attach':'yes','delete':'no'}

    def asterisk_config(self):
176
        """ Génération du paragraphe de conf asterisk definissant l'utilisateur"""
177
        return u""";%(uid)s
178
[%(num)s](default-crans)
179 180 181 182 183 184 185 186 187 188 189
username=%(num)s
callerid="%(callerid)s"
md5secret=%(passwd)s
mailbox=%(num)s@666
""" % {
 'callerid': self.get_caller_id(),
 'num': self.num,
 'passwd': self.password,
 'uid': self.user.username,
}

190

191 192 193
    def get_category(self):
        """ Renvoie la catégorie du profil, dans l'annuaire """
        return "Cr@ns"
194 195

    def get_caller_id(self):
196
        """ Calcule le texte descriptif de l'appelant """
197 198 199 200 201 202
        if self.caller_id == 'full_name':
            return self.user.get_full_name()
        elif self.caller_id == 'number':
            return self.num
        else: # Both
            return self.user.get_full_name() + u' <' + self.num + u'>'
Daniel STAN's avatar
Daniel STAN committed
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223

class Call(models.Model):
    """ Un appel archivé.
    Automatiquement rempli par asterisk (et les scripts maisons de Nit)
    """

    class Meta:
        """La table s'appelle history, on définit ici plutôt un nom pour un objet"""
        db_table = 'voip_history'
        verbose_name = u'Appel archivé'
        verbose_name_plural = u'Appels archivés'

    uniq_id = models.CharField(u"Identifiant de l'appel", max_length=80, primary_key=True)
    src = models.CharField(u"Numéro appelant", max_length=80)
    dst = models.CharField(u"Numéro appelant", max_length=80)

    """ Une durée égale à NULL correspond à un appel en cours (sauf si plantage) """
    duration = models.IntegerField(u"Durée de l'appel", null=True, blank=True)
    date = models.DateField(u"Date d'initiation de l'appel")

    def __unicode__(self):
224 225 226 227 228
        o = u"%s -> %s @ %s" % (self.src, self.dst, self.date)
        if self.duration:
            o += " (%ds)" % self.duration
        return o