models.py 15.8 KB
Newer Older
1
# -*- mode: python; coding: utf-8 -*-
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Re2o un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017  Gabriel Détraz
# Copyright © 2017  Goulven Kermarec
# Copyright © 2017  Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
24
25
"""
Reglages généraux, machines, utilisateurs, mail, general pour l'application.
"""
26
from __future__ import unicode_literals
27

28
from django.utils.functional import cached_property
29
from django.db import models
30
from django.db.models.signals import post_save
31
from django.dispatch import receiver
32
from django.core.cache import cache
33
from django.forms import ValidationError
34
from django.utils.translation import ugettext_lazy as _
35

36
import machines.models
chirac's avatar
chirac committed
37
from re2o.mixins import AclMixin
38
39
40
41
42
<<<<<<< HEAD
=======
from re2o.aes_field import AESEncryptedField
from datetime import timedelta
>>>>>>> 3d881c4f... Gestion de la clef radius, et serialisation
43

Maël Kervella's avatar
Maël Kervella committed
44

Gabriel Detraz's avatar
Gabriel Detraz committed
45
class PreferencesModel(models.Model):
46
47
48
    """ Base object for the Preferences objects
    Defines methods to handle the cache of the settings (they should
    not change a lot) """
Gabriel Detraz's avatar
Gabriel Detraz committed
49
50
    @classmethod
    def set_in_cache(cls):
51
        """ Save the preferences in a server-side cache """
Gabriel Detraz's avatar
Gabriel Detraz committed
52
53
54
55
56
57
        instance, _created = cls.objects.get_or_create()
        cache.set(cls().__class__.__name__.lower(), instance, None)
        return instance

    @classmethod
    def get_cached_value(cls, key):
58
        """ Get the preferences from the server-side cache """
Gabriel Detraz's avatar
Gabriel Detraz committed
59
        instance = cache.get(cls().__class__.__name__.lower())
Maël Kervella's avatar
Maël Kervella committed
60
        if instance is None:
61
            instance = cls.set_in_cache()
Gabriel Detraz's avatar
Gabriel Detraz committed
62
63
64
65
66
67
        return getattr(instance, key)

    class Meta:
        abstract = True


chirac's avatar
chirac committed
68
class OptionalUser(AclMixin, PreferencesModel):
69
70
    """Options pour l'user : obligation ou nom du telephone,
    activation ou non du solde, autorisation du negatif, fingerprint etc"""
71

72
73
    is_tel_mandatory = models.BooleanField(default=True)
    gpg_fingerprint = models.BooleanField(default=True)
74
    all_can_create_club = models.BooleanField(
75
        default=False,
76
        help_text=_("Users can create a club")
77
78
79
    )
    all_can_create_adherent = models.BooleanField(
        default=False,
80
        help_text=_("Users can create a member"),
81
    )
82
83
    self_adhesion = models.BooleanField(
        default=False,
84
        help_text=_("A new user can create their account on Re2o")
85
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
86
87
88
89
90
91
    shell_default = models.OneToOneField(
        'users.ListShell',
        on_delete=models.PROTECT,
        blank=True,
        null=True
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
92
93
    self_change_shell = models.BooleanField(
        default=False,
94
        help_text=_("Users can edit their shell")
Gabriel Detraz's avatar
Gabriel Detraz committed
95
    )
96
    local_email_accounts_enabled = models.BooleanField(
chirac's avatar
chirac committed
97
        default=False,
98
        help_text=_("Enable local email accounts for users")
chirac's avatar
chirac committed
99
    )
100
    local_email_domain = models.CharField(
101
102
103
        max_length=32,
        default="@example.org",
        help_text=_("Domain to use for local email accounts")
104
    )
105
    max_email_address = models.IntegerField(
106
107
108
        default=15,
        help_text=_("Maximum number of local email addresses for a standard"
                    " user")
grisel-davy's avatar
grisel-davy committed
109
    )
110

111
112
    class Meta:
        permissions = (
113
            ("view_optionaluser", _("Can view the user options")),
114
        )
115
        verbose_name = _("user options")
116

117
    def clean(self):
Gabriel Detraz's avatar
Gabriel Detraz committed
118
119
        """Clean model:
        Check the mail_extension
120
        """
121
        if self.local_email_domain[0] != "@":
122
            raise ValidationError(_("Email domain must begin with @"))
123

124

125
@receiver(post_save, sender=OptionalUser)
126
def optionaluser_post_save(**kwargs):
127
    """Ecriture dans le cache"""
128
129
130
131
    user_pref = kwargs['instance']
    user_pref.set_in_cache()


chirac's avatar
chirac committed
132
class OptionalMachine(AclMixin, PreferencesModel):
133
134
    """Options pour les machines : maximum de machines ou d'alias par user
    sans droit, activation de l'ipv6"""
135

136
137
138
139
    SLAAC = 'SLAAC'
    DHCPV6 = 'DHCPV6'
    DISABLED = 'DISABLED'
    CHOICE_IPV6 = (
140
141
142
        (SLAAC, _("Autoconfiguration by RA")),
        (DHCPV6, _("IP addresses assigning by DHCPv6")),
        (DISABLED, _("Disabled")),
143
144
    )

145
146
147
    password_machine = models.BooleanField(default=False)
    max_lambdauser_interfaces = models.IntegerField(default=10)
    max_lambdauser_aliases = models.IntegerField(default=10)
148
149
150
151
152
    ipv6_mode = models.CharField(
        max_length=32,
        choices=CHOICE_IPV6,
        default='DISABLED'
    )
153
    create_machine = models.BooleanField(
154
        default=True
155
    )
156
157
158

    @cached_property
    def ipv6(self):
159
        """ Check if the IPv6 option is activated """
Maël Kervella's avatar
Maël Kervella committed
160
        return not self.get_cached_value('ipv6_mode') == 'DISABLED'
161

162
163
    class Meta:
        permissions = (
164
            ("view_optionalmachine", _("Can view the machine options")),
165
        )
166
        verbose_name = _("machine options")
167

168

169
@receiver(post_save, sender=OptionalMachine)
170
def optionalmachine_post_save(**kwargs):
171
    """Synchronisation ipv6 et ecriture dans le cache"""
172
    machine_pref = kwargs['instance']
173
    machine_pref.set_in_cache()
174
175
176
177
178
    if machine_pref.ipv6_mode != "DISABLED":
        for interface in machines.models.Interface.objects.all():
            interface.sync_ipv6()


chirac's avatar
chirac committed
179
class OptionalTopologie(AclMixin, PreferencesModel):
180
181
    """Reglages pour la topologie : mode d'accès radius, vlan où placer
    les machines en accept ou reject"""
182
183
184
    MACHINE = 'MACHINE'
    DEFINED = 'DEFINED'
    CHOICE_RADIUS = (
185
186
        (MACHINE, _("On the IP range's VLAN of the machine")),
        (DEFINED, _("Preset in 'VLAN for machines accepted by RADIUS'")),
187
    )
188

189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
    radius_general_policy = models.CharField(
        max_length=32,
        choices=CHOICE_RADIUS,
        default='DEFINED'
    )
    vlan_decision_ok = models.OneToOneField(
        'machines.Vlan',
        on_delete=models.PROTECT,
        related_name='decision_ok',
        blank=True,
        null=True
    )
    vlan_decision_nok = models.OneToOneField(
        'machines.Vlan',
        on_delete=models.PROTECT,
        related_name='decision_nok',
        blank=True,
        null=True
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
    switchs_web_management = models.BooleanField(
        default=False,
        help_text="Web management, activé si provision automatique"
    )
    switchs_web_management_ssl = models.BooleanField(
        default=False,
        help_text="Web management ssl. Assurez-vous que un certif est installé sur le switch !"
    )   
    switchs_rest_management = models.BooleanField(
        default=False,
        help_text="Rest management, activé si provision auto"
    )
    switchs_ip_type = models.OneToOneField(
        'machines.IpType',
        on_delete=models.PROTECT,
        blank=True,
        null=True,
        help_text="Plage d'ip de management des switchs"
    )

    @cached_property
    def provisioned_switchs(self):
230
        """Liste des switches provisionnés"""
Gabriel Detraz's avatar
Gabriel Detraz committed
231
232
        from topologie.models import Switch
        return Switch.objects.filter(automatic_provision=True)
233

234
235
236
237
    @cached_property
    def provision_switchs_enabled(self):
        """Return true if all settings are ok : switchs on automatic provision,
        ip_type"""
238
        return bool(self.provisioned_switchs and self.switchs_ip_type and SwitchManagementCred.objects.filter(default_switch=True).exists())
239

240
241
    class Meta:
        permissions = (
242
            ("view_optionaltopologie", _("Can view the topology options")),
243
        )
244
        verbose_name = _("topology options")
245

246

247
@receiver(post_save, sender=OptionalTopologie)
248
def optionaltopologie_post_save(**kwargs):
249
    """Ecriture dans le cache"""
250
251
252
253
    topologie_pref = kwargs['instance']
    topologie_pref.set_in_cache()


254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
class RadiusKey(AclMixin, models.Model):
    """Class of a radius key"""
    radius_key = AESEncryptedField(
        max_length=255,
        help_text="Clef radius"
    )
    comment = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        help_text="Commentaire de cette clef"
    )
    default_switch = models.BooleanField(
        default=True,
        unique=True,
        help_text= "Clef par défaut des switchs"
    )

    class Meta:
        permissions = (
            ("view_radiuskey", "Peut voir un objet radiuskey"),
        )

277
278
279
    def __str__(self):
        return "Clef radius " + str(self.id) + " " + str(self.comment)

280

281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
class SwitchManagementCred(AclMixin, models.Model):
    """Class of a management creds of a switch, for rest management"""
    management_id = models.CharField(
        max_length=63,
        help_text="Login du switch"
    )
    management_pass = AESEncryptedField(
        max_length=63,
        help_text="Mot de passe"
    )
    default_switch = models.BooleanField(
        default=True,
        unique=True,
        help_text= "Creds par défaut des switchs"
    )

    class Meta:
        permissions = (
            ("view_switchmanagementcred", "Peut voir un objet switchmanagementcred"),
        )

    def __str__(self):
        return "Identifiant " + str(self.management_id)


306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
class Reminder(AclMixin, models.Model):
    """Options pour les mails de notification de fin d'adhésion.
    Days: liste des nombres de jours pour lesquells un mail est envoyé
    optionalMessage: message additionel pour le mail
    """
    PRETTY_NAME="Options pour le mail de fin d'adhésion"

    days = models.IntegerField(
        default=7,
        unique=True,
        help_text="Délais entre le mail et la fin d'adhésion"
    )
    message = models.CharField(
        max_length=255,
        default="",
        null=True,
        blank=True,
        help_text="Message affiché spécifiquement pour ce rappel"
    )

    class Meta:
        permissions = (
            ("view_reminder", "Peut voir un objet reminder"),
        )

    def users_to_remind(self):
        from re2o.utils import all_has_access
        date = timezone.now().replace(minute=0,hour=0)
        futur_date = date + timedelta(days=self.days)
        users = all_has_access(futur_date).exclude(pk__in = all_has_access(futur_date + timedelta(days=1))) 
        return users


chirac's avatar
chirac committed
339
class GeneralOption(AclMixin, PreferencesModel):
340
341
    """Options générales : nombre de resultats par page, nom du site,
    temps où les liens sont valides"""
342

343
    general_message_fr = models.TextField(
344
345
        default="",
        blank=True,
346
347
        help_text=_("General message displayed on the French version of the"
                    " website (e.g. in case of maintenance)")
348
349
350
351
    )
    general_message_en = models.TextField(
        default="",
        blank=True,
352
353
        help_text=_("General message displayed on the English version of the"
                    " website (e.g. in case of maintenance)")
354
    )
355
356
357
    search_display_page = models.IntegerField(default=15)
    pagination_number = models.IntegerField(default=25)
    pagination_large_number = models.IntegerField(default=8)
358
359
    req_expire_hrs = models.IntegerField(default=48)
    site_name = models.CharField(max_length=32, default="Re2o")
360
    email_from = models.EmailField(default="www-data@example.com")
361
362
363
364
365
    GTU_sum_up = models.TextField(
        default="",
        blank=True,
    )
    GTU = models.FileField(
Maël Kervella's avatar
Maël Kervella committed
366
        upload_to='',
367
368
369
370
        default="",
        null=True,
        blank=True,
    )
371

372
373
    class Meta:
        permissions = (
374
            ("view_generaloption", _("Can view the general options")),
375
        )
376
        verbose_name = _("general options")
377

378

379
@receiver(post_save, sender=GeneralOption)
380
def generaloption_post_save(**kwargs):
381
    """Ecriture dans le cache"""
382
383
384
385
    general_pref = kwargs['instance']
    general_pref.set_in_cache()


chirac's avatar
chirac committed
386
class Service(AclMixin, models.Model):
387
388
    """Liste des services affichés sur la page d'accueil : url, description,
    image et nom"""
389
390
391
    name = models.CharField(max_length=32)
    url = models.URLField()
    description = models.TextField()
392
    image = models.ImageField(upload_to='logo', blank=True)
393

394
395
    class Meta:
        permissions = (
396
            ("view_service", _("Can view the service options")),
397
        )
398
399
        verbose_name = _("service")
        verbose_name_plural =_("services")
400

401
402
403
    def __str__(self):
        return str(self.name)

404
class MailContact(AclMixin, models.Model):
405
    """Contact email adress with a commentary."""
406
407
408

    address = models.EmailField(
        default = "contact@example.org",
409
        help_text = _("Contact email address")
410
411
412
413
414
    )

    commentary = models.CharField(
        blank = True,
        null = True,
415
        help_text = _(
416
            "Description of the associated email address."),
417
418
419
        max_length = 256
    )

420
421
422
423
    @cached_property
    def get_name(self):
        return self.address.split("@")[0]

424
425
    class Meta:
        permissions = (
426
            ("view_mailcontact", _("Can view a contact email address object")),
427
        )
428
429
        verbose_name = _("contact email address")
        verbose_name_plural = _("contact email addresses")
430
431
432
433

    def __str__(self):
        return(self.address)

434

chirac's avatar
chirac committed
435
class AssoOption(AclMixin, PreferencesModel):
436
    """Options générales de l'asso : siret, addresse, nom, etc"""
437

438
    name = models.CharField(
439
        default=_("Networking organisation school Something"),
440
441
        max_length=256
    )
442
    siret = models.CharField(default="00000000000000", max_length=32)
443
444
    adresse1 = models.CharField(default=_("Threadneedle Street"), max_length=128)
    adresse2 = models.CharField(default=_("London EC2R 8AH"), max_length=128)
445
446
    contact = models.EmailField(default="contact@example.org")
    telephone = models.CharField(max_length=15, default="0000000000")
447
    pseudo = models.CharField(default=_("Organisation"), max_length=32)
448
449
450
451
452
453
    utilisateur_asso = models.OneToOneField(
        'users.User',
        on_delete=models.PROTECT,
        blank=True,
        null=True
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
454
455
456
457
    description = models.TextField(
        null=True,
        blank=True,
    )
458

459
460
    class Meta:
        permissions = (
461
            ("view_assooption", _("Can view the organisation options")),
462
        )
463
        verbose_name = _("organisation options")
464

465

466
@receiver(post_save, sender=AssoOption)
467
def assooption_post_save(**kwargs):
468
    """Ecriture dans le cache"""
469
470
471
472
    asso_pref = kwargs['instance']
    asso_pref.set_in_cache()


Gabriel Detraz's avatar
Gabriel Detraz committed
473
474
class HomeOption(AclMixin, PreferencesModel):
    """Settings of the home page (facebook/twitter etc)"""
Gabriel Detraz's avatar
Gabriel Detraz committed
475
476
477

    facebook_url = models.URLField(
        null=True,
478
        blank=True
Gabriel Detraz's avatar
Gabriel Detraz committed
479
480
481
    )
    twitter_url = models.URLField(
        null=True,
482
        blank=True
Gabriel Detraz's avatar
Gabriel Detraz committed
483
484
485
486
    )
    twitter_account_name = models.CharField(
        max_length=32,
        null=True,
487
        blank=True
Gabriel Detraz's avatar
Gabriel Detraz committed
488
489
490
491
    )

    class Meta:
        permissions = (
492
            ("view_homeoption", _("Can view the homepage options")),
Gabriel Detraz's avatar
Gabriel Detraz committed
493
        )
494
        verbose_name = _("homepage options")
Gabriel Detraz's avatar
Gabriel Detraz committed
495
496


Gabriel Detraz's avatar
Gabriel Detraz committed
497
498
@receiver(post_save, sender=HomeOption)
def homeoption_post_save(**kwargs):
Gabriel Detraz's avatar
Gabriel Detraz committed
499
    """Ecriture dans le cache"""
Gabriel Detraz's avatar
Gabriel Detraz committed
500
501
    home_pref = kwargs['instance']
    home_pref.set_in_cache()
Gabriel Detraz's avatar
Gabriel Detraz committed
502
503


chirac's avatar
chirac committed
504
class MailMessageOption(AclMixin, models.Model):
505
    """Reglages, mail de bienvenue et autre"""
506

Gabriel Detraz's avatar
Gabriel Detraz committed
507
508
    welcome_mail_fr = models.TextField(default="", help_text="Mail de bienvenue en français")
    welcome_mail_en = models.TextField(default="", help_text="Mail de bienvenue en anglais")
509

510
511
    class Meta:
        permissions = (
512
513
            ("view_mailmessageoption", _("Can view the email message"
                                         " options")),
514
        )
515
516
        verbose_name = _("email message options")