CustomerDirectory.clean()   F
last analyzed

Complexity

Conditions 15

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 26
rs 2.7451
cc 15

How to fix   Complexity   

Complexity

Complex classes like CustomerDirectory.clean() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# Copyright 2013 Mathias WOLFF
2
# This file is part of pyfreebilling.
3
#
4
# pyfreebilling is free software: you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# pyfreebilling is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with pyfreebilling.  If not, see <http://www.gnu.org/licenses/>
16
17
from django.core.exceptions import ValidationError
18
from django.db import models
19
from django.utils.translation import ugettext_lazy as _
20
21
from netaddr import IPNetwork
22
import re
23
import random
24
import string
25
26
from pyfreebilling.pyfreebill.models import Company
27
from pyfreebilling.pyfreebill.validators import validate_cidr
28
29
from pyfreebilling.normalizationrule.models import NormalizationGroup
30
31
from pyfreebilling.switch.models import Domain
32
33
34
class Subscriber(models.Model):
35
    """ Subscriber table for Kam """
36
    id = models.AutoField(auto_created=True, primary_key=True)
37
    username = models.CharField(max_length=64)
38
    # domain = models.CharField(max_length=64, blank=True)
39
    password = models.CharField(max_length=25, blank=True)
40
    # email_address = models.CharField(max_length=64, blank=True)
41
    # ha1 = models.CharField(max_length=64, blank=True)
42
    # ha1b = models.CharField(max_length=64, blank=True)
43
    #rpid = models.CharField(max_length=64, blank=True, null=True)
44
45
    class Meta:
46
        managed = False
47
        db_table = "subscriber"
48
        app_label = "customerdirectory"
49
        verbose_name = "Subscriber"
50
        verbose_name_plural = "Subscribers"
51
52
    def __str__(self):
53
        pass
54
55
# CREATE OR REPLACE VIEW subscriber AS
56
#     SELECT row_number() OVER () AS id,
57
#         c.name AS username,
58
##         c.domain,
59
#         c.password,
60
##         md5(username:realm:password) AS ha1,
61
##         md5(username@domain:realm:password) AS ha1b
62
#     FROM customer_directory c WHERE c.enabled=True and c.registration=True
63
64
65
def random_string():
66
    # Alphanumeric + special characters
67
    chars = string.letters + string.digits + string.punctuation
68
69
    pwdSize = 25
70
71
    return str(''.join((random.choice(chars)) for _ in range(pwdSize)))
72
73
74
class CustomerDirectory(models.Model):
75
    """ Customer Directory Model """
76
    company = models.ForeignKey(Company,
77
                                verbose_name=_(u"company"))
78
    registration = models.BooleanField(_(u"Registration"),
79
                                       default=True,
80
                                       help_text=_(u"""Is registration needed
81
                                       for calling ? True, the phone needs to
82
                                       register with correct username/password.
83
                                       If false, you must specify a CIDR in SIP
84
                                       IP CIDR !"""))
85
    password = models.CharField(_(u"password"),
86
                                max_length=100,
87
                                blank=True,
88
                                default=random_string,
89
                                help_text=_(u"""It's recommended to use strong
90
                                passwords for the endpoint."""))
91
    description = models.TextField(_(u'description'),
92
                                   blank=True)
93
    name = models.CharField(_(u"SIP username"),
94
                            max_length=50,
95
                            unique=True,
96
                            help_text=_(u"Ex.: customer SIP username, etc..."))
97
    domain = models.ForeignKey(
98
        Domain,
99
        verbose_name=_(u"SIP domain"),
100
        help_text=_(u"""A sip account must belong to a domain.
101
            This domain must be used in SIP message"""))
102
    rtp_ip = models.CharField(_(u"RTP IP CIDR"),
103
                              max_length=100,
104
                              default="auto",
105
                              help_text=_(u"""Internal IP address/mask to bind
106
                              to for RTP. Format : CIDR Ex. 192.168.1.0/32"""))
107
    sip_ip = models.CharField(_(u"SIP IP CIDR"),
108
                              max_length=100,
109
                              null=True,
110
                              blank=True,
111
                              validators=[validate_cidr],
112
                              help_text=_(u"""Internal IP address/mask to bind
113
                              to for SIP. Format : CIDR. Ex. 192.168.1.0/32
114
                              """))
115
    sip_port = models.PositiveIntegerField(_(u"SIP port"),
116
                                           default=5060)
117
    max_calls = models.PositiveIntegerField(_(u'max calls'),
118
                                            default=1,
119
                                            help_text=_(u"""max simultaneous
120
                                            calls allowed for this customer
121
                                            account."""))
122
    calls_per_second = models.PositiveIntegerField(_(u'max calls per second'),
123
                                                   default=10,
124
                                                   help_text=_(u"""maximum
125
                                                   calls per second allowed for
126
                                                   this customer account."""))
127
    log_auth_failures = models.BooleanField(_(u"log auth failures"),
128
                                            default=False,
129
                                            help_text=_(u"""It true, the server
130
                                            will log authentication failures.
131
                                            Required for Fail2ban."""))
132
    MULTIPLE_CODECS_CHOICES = (
133
        ("PCMA,PCMU,G729", _(u"PCMA,PCMU,G729")),
134
        ("PCMU,PCMA,G729", _(u"PCMU,PCMA,G729")),
135
        ("G729,PCMA,PCMU", _(u"G729,PCMA,PCMU")),
136
        ("G729,PCMU,PCMA", _(u"G729,PCMU,PCMA")),
137
        ("PCMA,G729", _(u"PCMA,G729")),
138
        ("PCMU,G729", _(u"PCMU,G729")),
139
        ("G729,PCMA", _(u"G729,PCMA")),
140
        ("G729,PCMU", _(u"G729,PCMU")),
141
        ("PCMA,PCMU", _(u"PCMA,PCMU")),
142
        ("PCMU,PCMA", _(u"PCMU,PCMA")),
143
        ("G722,PCMA,PCMU", _(u"G722,PCMA,PCMU")),
144
        ("G722,PCMU,PCMA", _(u"G722,PCMU,PCMA")),
145
        ("G722", _(u"G722")),
146
        ("G729", _(u"G729")),
147
        ("PCMU", _(u"PCMU")),
148
        ("PCMA", _(u"PCMA")),
149
        ("ALL", _(u"ALL")),
150
    )
151
    codecs = models.CharField(_(u"Codecs"),
152
                              max_length=100,
153
                              default="ALL",
154
                              choices=MULTIPLE_CODECS_CHOICES,
155
                              help_text=_(u"""Codecs allowed - beware about
156
                              order, 1st has high priority """))
157
    MULTIPLE_REG_CHOICES = (
158
        ("call-id", _(u"Call-id")),
159
        ("contact", _(u"Contact")),
160
        ("false", _(u"False")),
161
        ("true", _(u"True")))
162
    multiple_registrations = models.CharField(_(u"multiple registrations"),
163
                                              max_length=100,
164
                                              default="false",
165
                                              choices=MULTIPLE_REG_CHOICES,
166
                                              help_text=_(u"""Used to allow to
167
                                              call one extension and ring
168
                                              several phones."""))
169
    outbound_caller_id_name = models.CharField(_(u"CallerID name"),
170
                                               max_length=50,
171
                                               blank=True,
172
                                               help_text=_(u"""Caller ID name
173
                                               sent to provider on outbound
174
                                               calls."""))
175
    outbound_caller_id_number = models.CharField(_(u"""CallerID
176
                                                   num"""),
177
                                                 max_length=80,
178
                                                 blank=True,
179
                                                 help_text=_(u"""Caller ID
180
                                                 number sent to provider on
181
                                                 outbound calls."""))
182
    callerid_norm = models.ForeignKey(
183
        NormalizationGroup,
184
        related_name='calleridnormrules',
185
        null=True,
186
        blank=True,
187
        verbose_name=_(u"CallerID normalization rules for outbound call"))
188
    callee_norm = models.ForeignKey(
189
        NormalizationGroup,
190
        related_name='caleenormrules',
191
        null=True,
192
        blank=True,
193
        verbose_name=_(u"Destination number normalization rules for outbound call"))
194
    callerid_norm_in = models.ForeignKey(
195
        NormalizationGroup,
196
        related_name='calleridnormrulesin',
197
        null=True,
198
        blank=True,
199
        verbose_name=_(u"CallerID normalization rules for inbound call"))
200
    callee_norm_in = models.ForeignKey(
201
        NormalizationGroup,
202
        related_name='caleenormrulesin',
203
        null=True,
204
        blank=True,
205
        verbose_name=_(u"Destination number normalization rules for inbound call"))
206
    force_caller_id = models.BooleanField(
207
        _(u"Force callerID"),
208
        default=False)
209
    masq_caller_id = models.BooleanField(
210
        _(u"Masq callerID"),
211
        default=False)
212
    urgency_numbr = models.BooleanField(
213
        _(u"Allow urgency numbers"),
214
        default=True,
215
        help_text=_(u"""You have also to allow global routing option
216
          and define an urgency ratecard"""))
217
    insee_code = models.CharField(
218
        _(u'Special code for routing urgency numbers'),
219
        null=True,
220
        blank=True,
221
        max_length=10,
222
        help_text=_(u"""Postal code, INSEE code ... for routing
223
          urgency number to the right urgency call center."""))
224
    IEM_CHOICES = (
225
        ("false", _(u"false")),
226
        ("true", _(u"true")),
227
        ("ring_ready", _(u"ring_ready")))
228
    ignore_early_media = models.CharField(_(u"Ignore early media"),
229
                                          max_length=20,
230
                                          default="false",
231
                                          choices=IEM_CHOICES,
232
                                          help_text=_(u"""Controls if the call
233
                                                      returns on early media
234
                                                      or not. Default is false.
235
                                                      Setting the value to
236
                                                      "ring_ready" will work
237
                                                      the same as
238
                                                      ignore_early_media=true
239
                                                      but also send a SIP 180
240
                                                      to the inbound leg when
241
                                                      the first SIP 183 is
242
                                                      caught.
243
                                                      """))
244
    enabled = models.BooleanField(_(u"Enabled / Disabled"),
245
                                  default=True)
246
    fake_ring = models.BooleanField(_(u"Fake ring"),
247
                                    default=False,
248
                                    help_text=_(u"""Fake ring : Enabled /
249
                                    Disabled - Send a fake ring to the
250
                                    caller."""))
251
    cli_debug = models.BooleanField(_(u"CLI debug"),
252
                                    default=False,
253
                                    help_text=_(u"""CLI debug : Enabled /
254
                                    Disabled - Permit to see all debug
255
                                    messages on cli."""))
256
    vmd = models.BooleanField(_(u"Voicemail detection : Enabled / Disabled"),
257
                              default=False,
258
                              help_text=_(u"""Be carefull with this option, as
259
                              it takes a lot of ressources !."""))
260
    date_added = models.DateTimeField(_(u'date added'),
261
                                      auto_now_add=True)
262
    date_modified = models.DateTimeField(_(u'date modified'),
263
                                         auto_now=True)
264
265
    class Meta:
266
        db_table = 'customer_directory'
267
        app_label = 'customerdirectory'
268
        ordering = ('company', 'name')
269
        verbose_name = _(u'Customer sip account')
270
        verbose_name_plural = _(u'Customer sip accounts')
271
272
    def __unicode__(self):
273
        return "%s (%s:%s)" % (self.name, self.sip_ip, self.sip_port)
274
275
    def clean(self):
276
        if (self.registration and
277
                (self.password is None or self.password == '')):
278
            raise ValidationError(_(u"""You have to specify a password if you
279
                                  want to allow registration"""))
280
        if (self.registration is False and
281
                (self.sip_ip is None or self.sip_ip == '')):
282
            raise ValidationError(_(u"""You must specify a SIP IP CIDR if you do
283
                                  not want to use registration"""))
284
        if self.registration and self.password:
285
            # in future use https://github.com/dstufft/django-passwords ?
286
            MIN_LENGTH = 8
287
            if len(self.password) < MIN_LENGTH:
288
                raise ValidationError(_(u"""The password must be at least %d
289
                                      characters long.""") % MIN_LENGTH)
290
            first_isalpha = self.password[0].isalpha()
291
            if all(c.isalpha() == first_isalpha for c in self.password):
292
                raise ValidationError(_(u"""The new password must contain
293
                                            at least one letter and at least
294
                                            one digit"""))
295
        if self.sip_ip:
296
            m = re.search('/32$', self.sip_ip)
297
            if m:
298
                pass
299
            elif len(IPNetwork(self.sip_ip)) == 1:
300
                self.sip_ip = str(self.sip_ip) + str('/32')
301
                # add name check no space ...
302