models.py 22 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.utils import timezone
30
from django.db import models
31
from django.db.models.signals import post_save
32
from django.dispatch import receiver
33
from django.core.cache import cache
34
from django.forms import ValidationError
35
from django.utils.translation import ugettext_lazy as _
36

37
import machines.models
38

chirac's avatar
chirac committed
39
from re2o.mixins import AclMixin
40
from re2o.aes_field import AESEncryptedField
41

42
from datetime import timedelta
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

Gabriel Detraz's avatar
Gabriel Detraz committed
83
84
85
86
87
88
    shell_default = models.OneToOneField(
        'users.ListShell',
        on_delete=models.PROTECT,
        blank=True,
        null=True
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
89
90
    self_change_shell = models.BooleanField(
        default=False,
91
        help_text=_("Users can edit their shell")
Gabriel Detraz's avatar
Gabriel Detraz committed
92
    )
93
94
95
96
    self_change_room = models.BooleanField(
        default=False,
        help_text=_("Users can edit their room")
    )
97
    local_email_accounts_enabled = models.BooleanField(
chirac's avatar
chirac committed
98
        default=False,
99
        help_text=_("Enable local email accounts for users")
chirac's avatar
chirac committed
100
    )
101
    local_email_domain = models.CharField(
102
103
104
        max_length=32,
        default="@example.org",
        help_text=_("Domain to use for local email accounts")
105
    )
106
    max_email_address = models.IntegerField(
107
108
109
        default=15,
        help_text=_("Maximum number of local email addresses for a standard"
                    " user")
grisel-davy's avatar
grisel-davy committed
110
    )
111
112
113
114
    delete_notyetactive = models.IntegerField(
        default=15,
        help_text=_("Inactive users will be deleted after this number of days")
    )
115
116
117
118
    self_adhesion = models.BooleanField(
        default=False,
        help_text=_("A new user can create their account on Re2o")
    )
119

120
121
    class Meta:
        permissions = (
122
            ("view_optionaluser", _("Can view the user options")),
123
        )
124
        verbose_name = _("user options")
125

126
    def clean(self):
Gabriel Detraz's avatar
Gabriel Detraz committed
127
128
        """Clean model:
        Check the mail_extension
129
        """
130
        if self.local_email_domain[0] != "@":
131
            raise ValidationError(_("Email domain must begin with @"))
132

133

134
@receiver(post_save, sender=OptionalUser)
135
def optionaluser_post_save(**kwargs):
136
    """Ecriture dans le cache"""
137
138
139
140
    user_pref = kwargs['instance']
    user_pref.set_in_cache()


chirac's avatar
chirac committed
141
class OptionalMachine(AclMixin, PreferencesModel):
142
143
    """Options pour les machines : maximum de machines ou d'alias par user
    sans droit, activation de l'ipv6"""
144

145
146
147
148
    SLAAC = 'SLAAC'
    DHCPV6 = 'DHCPV6'
    DISABLED = 'DISABLED'
    CHOICE_IPV6 = (
149
150
151
        (SLAAC, _("Autoconfiguration by RA")),
        (DHCPV6, _("IP addresses assigning by DHCPv6")),
        (DISABLED, _("Disabled")),
152
153
    )

154
155
156
    password_machine = models.BooleanField(default=False)
    max_lambdauser_interfaces = models.IntegerField(default=10)
    max_lambdauser_aliases = models.IntegerField(default=10)
157
158
159
160
161
    ipv6_mode = models.CharField(
        max_length=32,
        choices=CHOICE_IPV6,
        default='DISABLED'
    )
162
    create_machine = models.BooleanField(
163
        default=True
164
    )
165
166
167

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

171
172
    class Meta:
        permissions = (
173
            ("view_optionalmachine", _("Can view the machine options")),
174
        )
175
        verbose_name = _("machine options")
176

177

178
@receiver(post_save, sender=OptionalMachine)
179
def optionalmachine_post_save(**kwargs):
180
    """Synchronisation ipv6 et ecriture dans le cache"""
181
    machine_pref = kwargs['instance']
182
    machine_pref.set_in_cache()
183
184
185
186
187
    if machine_pref.ipv6_mode != "DISABLED":
        for interface in machines.models.Interface.objects.all():
            interface.sync_ipv6()


chirac's avatar
chirac committed
188
class OptionalTopologie(AclMixin, PreferencesModel):
189
190
    """Reglages pour la topologie : mode d'accès radius, vlan où placer
    les machines en accept ou reject"""
191
192
193
    MACHINE = 'MACHINE'
    DEFINED = 'DEFINED'
    CHOICE_RADIUS = (
194
195
        (MACHINE, _("On the IP range's VLAN of the machine")),
        (DEFINED, _("Preset in 'VLAN for machines accepted by RADIUS'")),
196
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
197
198
199
200
    CHOICE_PROVISION = (
        ('sftp', 'sftp'),
        ('tftp', 'tftp'),
    )
201

Gabriel Detraz's avatar
Gabriel Detraz committed
202
203
204
205
206
207
208
    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 !"
209
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
210
211
212
213
214
215
216
217
218
219
220
    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"
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
    switchs_provision = models.CharField(
        max_length=32,
        choices=CHOICE_PROVISION,
        default='tftp',
        help_text="Mode de récupération des confs par les switchs"
    )
    sftp_login = models.CharField(
        max_length=32,
        null=True,
        blank=True,
        help_text="Login sftp des switchs"
    )
    sftp_pass = AESEncryptedField(
        max_length=63,
        null=True,
        blank=True,
        help_text="Mot de passe sftp"
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
239
240
241

    @cached_property
    def provisioned_switchs(self):
242
        """Liste des switches provisionnés"""
Gabriel Detraz's avatar
Gabriel Detraz committed
243
        from topologie.models import Switch
Gabriel Detraz's avatar
Gabriel Detraz committed
244
        return Switch.objects.filter(automatic_provision=True).order_by('interface__domain__name')
245

246
    @cached_property
247
248
249
250
251
252
253
254
    def switchs_management_interface(self):
        """Return the ip of the interface that the switch have to contact to get it's config"""
        if self.switchs_ip_type:
            from machines.models import Role, Interface
            return Interface.objects.filter(machine__interface__in=Role.interface_for_roletype("switch-conf-server")).filter(type__ip_type=self.switchs_ip_type).first()
        else:
            return None

255
    @cached_property
256
257
258
259
260
261
    def switchs_management_interface_ip(self):
        """Same, but return the ipv4"""
        if not self.switchs_management_interface:
            return None
        return self.switchs_management_interface.ipv4

Gabriel Detraz's avatar
Gabriel Detraz committed
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
    @cached_property
    def switchs_management_sftp_creds(self):
        """Credentials des switchs pour provion sftp"""
        if self.sftp_login and self.sftp_pass:
            return {'login' : self.sftp_login, 'pass' : self.sftp_pass}
        else:
            return None

    @cached_property
    def switchs_management_utils(self):
        """Used for switch_conf, return a list of ip on vlans"""
        from machines.models import Role, Ipv6List, Interface
        def return_ips_dict(interfaces):
            return {'ipv4' : [str(interface.ipv4) for interface in interfaces], 'ipv6' : Ipv6List.objects.filter(interface__in=interfaces).values_list('ipv6', flat=True)}

        ntp_servers = Role.all_interfaces_for_roletype("ntp-server").filter(type__ip_type=self.switchs_ip_type)
        log_servers = Role.all_interfaces_for_roletype("log-server").filter(type__ip_type=self.switchs_ip_type)
        radius_servers = Role.all_interfaces_for_roletype("radius-server").filter(type__ip_type=self.switchs_ip_type)
        dhcp_servers = Role.all_interfaces_for_roletype("dhcp-server")
281
        dns_recursive_servers = Role.all_interfaces_for_roletype("dns-recursif-server").filter(type__ip_type=self.switchs_ip_type)
Gabriel Detraz's avatar
Gabriel Detraz committed
282
283
284
285
286
        subnet = None
        subnet6 = None
        if self.switchs_ip_type:
            subnet = self.switchs_ip_type.ip_set_full_info
            subnet6 = self.switchs_ip_type.ip6_set_full_info
287
        return {'ntp_servers': return_ips_dict(ntp_servers), 'log_servers': return_ips_dict(log_servers), 'radius_servers': return_ips_dict(radius_servers), 'dhcp_servers': return_ips_dict(dhcp_servers), 'dns_recursive_servers': return_ips_dict(dns_recursive_servers), 'subnet': subnet, 'subnet6': subnet6}
Gabriel Detraz's avatar
Gabriel Detraz committed
288

289
290
291
292
    @cached_property
    def provision_switchs_enabled(self):
        """Return true if all settings are ok : switchs on automatic provision,
        ip_type"""
Gabriel Detraz's avatar
Gabriel Detraz committed
293
        return bool(self.provisioned_switchs and self.switchs_ip_type and SwitchManagementCred.objects.filter(default_switch=True).exists() and self.switchs_management_interface_ip and bool(self.switchs_provision != 'sftp'  or self.switchs_management_sftp_creds))
294
295
    class Meta:
        permissions = (
296
            ("view_optionaltopologie", _("Can view the topology options")),
297
        )
298
        verbose_name = _("topology options")
299

300

301
@receiver(post_save, sender=OptionalTopologie)
302
def optionaltopologie_post_save(**kwargs):
303
    """Ecriture dans le cache"""
304
305
306
307
    topologie_pref = kwargs['instance']
    topologie_pref.set_in_cache()


308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
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"),
        )

331
332
333
    def __str__(self):
        return "Clef radius " + str(self.id) + " " + str(self.comment)

334

335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
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)


360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
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)
389
        users = all_has_access(futur_date).exclude(pk__in = all_has_access(futur_date + timedelta(days=1)))
390
391
392
        return users


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

397
    general_message_fr = models.TextField(
398
399
        default="",
        blank=True,
400
401
        help_text=_("General message displayed on the French version of the"
                    " website (e.g. in case of maintenance)")
402
403
404
405
    )
    general_message_en = models.TextField(
        default="",
        blank=True,
406
407
        help_text=_("General message displayed on the English version of the"
                    " website (e.g. in case of maintenance)")
408
    )
409
410
411
    search_display_page = models.IntegerField(default=15)
    pagination_number = models.IntegerField(default=25)
    pagination_large_number = models.IntegerField(default=8)
412
413
    req_expire_hrs = models.IntegerField(default=48)
    site_name = models.CharField(max_length=32, default="Re2o")
414
    email_from = models.EmailField(default="www-data@example.com")
415
    main_site_url = models.URLField(max_length=255, default="http://re2o.example.org")
416
417
418
419
420
    GTU_sum_up = models.TextField(
        default="",
        blank=True,
    )
    GTU = models.FileField(
Maël Kervella's avatar
Maël Kervella committed
421
        upload_to='',
422
423
424
425
        default="",
        null=True,
        blank=True,
    )
426

427
428
    class Meta:
        permissions = (
429
            ("view_generaloption", _("Can view the general options")),
430
        )
431
        verbose_name = _("general options")
432

433

434
@receiver(post_save, sender=GeneralOption)
435
def generaloption_post_save(**kwargs):
436
    """Ecriture dans le cache"""
437
438
439
440
    general_pref = kwargs['instance']
    general_pref.set_in_cache()


chirac's avatar
chirac committed
441
class Service(AclMixin, models.Model):
442
443
    """Liste des services affichés sur la page d'accueil : url, description,
    image et nom"""
444
445
446
    name = models.CharField(max_length=32)
    url = models.URLField()
    description = models.TextField()
447
    image = models.ImageField(upload_to='logo', blank=True)
448

449
450
    class Meta:
        permissions = (
451
            ("view_service", _("Can view the service options")),
452
        )
453
454
        verbose_name = _("service")
        verbose_name_plural =_("services")
455

456
457
458
    def __str__(self):
        return str(self.name)

459
class MailContact(AclMixin, models.Model):
460
    """Contact email adress with a commentary."""
461
462
463

    address = models.EmailField(
        default = "contact@example.org",
464
        help_text = _("Contact email address")
465
466
467
468
469
    )

    commentary = models.CharField(
        blank = True,
        null = True,
470
        help_text = _(
471
            "Description of the associated email address."),
472
473
474
        max_length = 256
    )

475
476
477
478
    @cached_property
    def get_name(self):
        return self.address.split("@")[0]

479
480
    class Meta:
        permissions = (
481
            ("view_mailcontact", _("Can view a contact email address object")),
482
        )
483
484
        verbose_name = _("contact email address")
        verbose_name_plural = _("contact email addresses")
485
486
487
488

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

489

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

493
    name = models.CharField(
494
        default=_("Networking organisation school Something"),
495
496
        max_length=256
    )
497
    siret = models.CharField(default="00000000000000", max_length=32)
498
499
    adresse1 = models.CharField(default=_("Threadneedle Street"), max_length=128)
    adresse2 = models.CharField(default=_("London EC2R 8AH"), max_length=128)
500
501
    contact = models.EmailField(default="contact@example.org")
    telephone = models.CharField(max_length=15, default="0000000000")
502
    pseudo = models.CharField(default=_("Organisation"), max_length=32)
503
504
505
506
507
508
    utilisateur_asso = models.OneToOneField(
        'users.User',
        on_delete=models.PROTECT,
        blank=True,
        null=True
    )
Gabriel Detraz's avatar
Gabriel Detraz committed
509
510
511
512
    description = models.TextField(
        null=True,
        blank=True,
    )
513

514
515
    class Meta:
        permissions = (
516
            ("view_assooption", _("Can view the organisation options")),
517
        )
518
        verbose_name = _("organisation options")
519

520

521
@receiver(post_save, sender=AssoOption)
522
def assooption_post_save(**kwargs):
523
    """Ecriture dans le cache"""
524
525
526
527
    asso_pref = kwargs['instance']
    asso_pref.set_in_cache()


Gabriel Detraz's avatar
Gabriel Detraz committed
528
529
class HomeOption(AclMixin, PreferencesModel):
    """Settings of the home page (facebook/twitter etc)"""
Gabriel Detraz's avatar
Gabriel Detraz committed
530
531
532

    facebook_url = models.URLField(
        null=True,
533
        blank=True
Gabriel Detraz's avatar
Gabriel Detraz committed
534
535
536
    )
    twitter_url = models.URLField(
        null=True,
537
        blank=True
Gabriel Detraz's avatar
Gabriel Detraz committed
538
539
540
541
    )
    twitter_account_name = models.CharField(
        max_length=32,
        null=True,
542
        blank=True
Gabriel Detraz's avatar
Gabriel Detraz committed
543
544
545
546
    )

    class Meta:
        permissions = (
547
            ("view_homeoption", _("Can view the homepage options")),
Gabriel Detraz's avatar
Gabriel Detraz committed
548
        )
549
        verbose_name = _("homepage options")
Gabriel Detraz's avatar
Gabriel Detraz committed
550
551


Gabriel Detraz's avatar
Gabriel Detraz committed
552
553
@receiver(post_save, sender=HomeOption)
def homeoption_post_save(**kwargs):
Gabriel Detraz's avatar
Gabriel Detraz committed
554
    """Ecriture dans le cache"""
Gabriel Detraz's avatar
Gabriel Detraz committed
555
556
    home_pref = kwargs['instance']
    home_pref.set_in_cache()
Gabriel Detraz's avatar
Gabriel Detraz committed
557
558


chirac's avatar
chirac committed
559
class MailMessageOption(AclMixin, models.Model):
560
    """Reglages, mail de bienvenue et autre"""
561

Gabriel Detraz's avatar
Gabriel Detraz committed
562
563
    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")
564

565
566
    class Meta:
        permissions = (
567
568
            ("view_mailmessageoption", _("Can view the email message"
                                         " options")),
569
        )
570
571
        verbose_name = _("email message options")

Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
572

573
class RadiusOption(AclMixin, PreferencesModel):
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
574
575
576
577
578
579
580
581
582
    class Meta:
        verbose_name = _("radius policies")

    MACHINE = 'MACHINE'
    DEFINED = 'DEFINED'
    CHOICE_RADIUS = (
        (MACHINE, _("On the IP range's VLAN of the machine")),
        (DEFINED, _("Preset in 'VLAN for machines accepted by RADIUS'")),
    )
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
583
584
585
586
587
588
    REJECT = 'REJECT'
    SET_VLAN = 'SET_VLAN'
    CHOICE_POLICY = (
        (REJECT, _('Reject the machine')),
        (SET_VLAN, _('Place the machine on the VLAN'))
    )
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
589
590
591
592
593
    radius_general_policy = models.CharField(
        max_length=32,
        choices=CHOICE_RADIUS,
        default='DEFINED'
    )
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
594
595
596
597
    unknown_machine = models.CharField(
        max_length=32,
        choices=CHOICE_POLICY,
        default=REJECT,
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
598
599
        verbose_name=_("Policy for unknown machines"),
    )
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
600
601
    unknown_machine_vlan = models.ForeignKey(
        'machines.Vlan',
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
602
        on_delete=models.PROTECT,
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
603
604
605
606
607
608
609
610
611
612
613
614
        related_name='unknown_machine_vlan',
        blank=True,
        null=True,
        verbose_name=_('Unknown machine Vlan'),
        help_text=_(
            'Vlan for unknown machines if not rejected.'
        )
    )
    unknown_port = models.CharField(
        max_length=32,
        choices=CHOICE_POLICY,
        default=REJECT,
615
        verbose_name=_("Policy for unknown port"),
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
616
    )
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
617
618
    unknown_port_vlan = models.ForeignKey(
        'machines.Vlan',
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
619
        on_delete=models.PROTECT,
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
620
621
622
623
624
625
626
627
628
629
630
631
        related_name='unknown_port_vlan',
        blank=True,
        null=True,
        verbose_name=_('Unknown port Vlan'),
        help_text=_(
            'Vlan for unknown ports if not rejected.'
        )
    )
    unknown_room = models.CharField(
        max_length=32,
        choices=CHOICE_POLICY,
        default=REJECT,
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
632
633
634
635
636
637
        verbose_name=_(
            "Policy for machine connecting from "
            "unregistered room (relevant on ports with STRICT "
            "radius mode)"
        ),
    )
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
638
639
640
    unknown_room_vlan = models.ForeignKey(
        'machines.Vlan',
        related_name='unknown_room_vlan',
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
641
        on_delete=models.PROTECT,
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
642
643
644
645
646
647
648
649
650
651
652
        blank=True,
        null=True,
        verbose_name=_('Unknown room Vlan'),
        help_text=_(
            'Vlan for unknown room if not rejected.'
        )
    )
    non_member = models.CharField(
        max_length=32,
        choices=CHOICE_POLICY,
        default=REJECT,
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
653
654
        verbose_name=_("Policy non member users."),
    )
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
655
656
657
    non_member_vlan = models.ForeignKey(
        'machines.Vlan',
        related_name='non_member_vlan',
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
658
        on_delete=models.PROTECT,
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
659
660
661
662
663
664
665
666
667
668
669
        blank=True,
        null=True,
        verbose_name=_('Non member Vlan'),
        help_text=_(
            'Vlan for non members if not rejected.'
        )
    )
    banned = models.CharField(
        max_length=32,
        choices=CHOICE_POLICY,
        default=REJECT,
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
670
        verbose_name=_("Policy for banned users."),
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
671
672
673
674
675
676
677
678
679
680
681
    )
    banned_vlan = models.ForeignKey(
        'machines.Vlan',
        related_name='banned_vlan',
        on_delete=models.PROTECT,
        blank=True,
        null=True,
        verbose_name=_('Banned Vlan'),
        help_text=_(
            'Vlan for banned if not rejected.'
        )
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
682
683
684
685
686
687
688
689
690
    )
    vlan_decision_ok = models.OneToOneField(
        'machines.Vlan',
        on_delete=models.PROTECT,
        related_name='vlan_ok_option',
        blank=True,
        null=True
    )