diff --git a/machines/forms.py b/machines/forms.py
index 05fd6999..5dc9dd8b 100644
--- a/machines/forms.py
+++ b/machines/forms.py
@@ -57,6 +57,7 @@ from .models import (
Nas,
IpType,
OuverturePortList,
+ Ipv6List,
)
@@ -274,6 +275,17 @@ class DelExtensionForm(Form):
self.fields['extensions'].queryset = Extension.objects.all()
+class Ipv6ListForm(FieldPermissionFormMixin, ModelForm):
+ """Gestion des ipv6 d'une machine"""
+ class Meta:
+ model = Ipv6List
+ fields = ['ipv6', 'slaac_ip']
+
+ def __init__(self, *args, **kwargs):
+ prefix = kwargs.pop('prefix', self.Meta.model.__name__)
+ super(Ipv6ListForm, self).__init__(*args, prefix=prefix, **kwargs)
+
+
class SOAForm(ModelForm):
"""Ajout et edition d'un SOA"""
class Meta:
diff --git a/machines/migrations/0073_auto_20180128_2203.py b/machines/migrations/0073_auto_20180128_2203.py
new file mode 100644
index 00000000..b09b9c47
--- /dev/null
+++ b/machines/migrations/0073_auto_20180128_2203.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-28 21:03
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('machines', '0072_auto_20180108_1822'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Ipv6List',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('ipv6', models.GenericIPAddressField(protocol='IPv6', unique=True)),
+ ('slaac_ip', models.BooleanField(default=False)),
+ ('interface', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='machines.Interface')),
+ ],
+ ),
+ migrations.AlterUniqueTogether(
+ name='ipv6list',
+ unique_together=set([('interface', 'slaac_ip')]),
+ ),
+ ]
diff --git a/machines/migrations/0074_auto_20180129_0352.py b/machines/migrations/0074_auto_20180129_0352.py
new file mode 100644
index 00000000..298f2a8e
--- /dev/null
+++ b/machines/migrations/0074_auto_20180129_0352.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-29 02:52
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('machines', '0073_auto_20180128_2203'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='ipv6list',
+ options={'permissions': (('view_ipv6list', 'Peut voir un objet ipv6'), ('change_ipv6list_slaac_ip', 'Peut changer la valeur slaac sur une ipv6'))},
+ ),
+ ]
diff --git a/machines/models.py b/machines/models.py
index 59965983..53e73ae6 100644
--- a/machines/models.py
+++ b/machines/models.py
@@ -1188,7 +1188,7 @@ class Interface(FieldPermissionModelMixin,models.Model):
return machine.active and user.has_access()
@cached_property
- def ipv6_object(self):
+ def ipv6_slaac(self):
""" Renvoie un objet type ipv6 à partir du prefix associé à
l'iptype parent"""
if self.type.ip_type.prefix_v6:
@@ -1198,10 +1198,29 @@ class Interface(FieldPermissionModelMixin,models.Model):
else:
return None
- @cached_property
+ def sync_ipv6_slaac(self):
+ """Cree, mets à jour et supprime si il y a lieu l'ipv6 slaac associée
+ à la machine
+ Sans prefixe ipv6, on return
+ Si l'ip slaac n'est pas celle qu'elle devrait être, on maj"""
+ ipv6_slaac = self.ipv6_slaac
+ if not ipv6_slaac:
+ return
+ ipv6_object = Ipv6List.objects.filter(interface=self, slaac_ip=True).first()
+ if not ipv6_object:
+ ipv6_object = Ipv6List(interface=self, slaac_ip=True)
+ if ipv6_object.ipv6 != str(ipv6_slaac):
+ ipv6_object.ipv6 = str(ipv6_slaac)
+ ipv6_object.save()
+
def ipv6(self):
- """ Renvoie l'ipv6 en str. Mise en cache et propriété de l'objet"""
- return str(self.ipv6_object)
+ """ Renvoie le queryset de la liste des ipv6
+ On renvoie l'ipv6 slaac que si le mode slaac est activé (et non dhcpv6)"""
+ machine_options, _created = preferences.models.OptionalMachine.objects.get_or_create()
+ if machine_options.ipv6_mode == 'SLAAC':
+ return Ipv6List.objects.filter(interface=self)
+ else:
+ return Ipv6List.objects.filter(interface=self, slaac_ip=False)
def mac_bare(self):
""" Formatage de la mac type mac_bare"""
@@ -1365,6 +1384,104 @@ class Interface(FieldPermissionModelMixin,models.Model):
return self.ipv4 and not self.has_private_ip()
+class Ipv6List(FieldPermissionModelMixin, models.Model):
+ PRETTY_NAME = 'Enregistrements Ipv6 des machines'
+
+ ipv6 = models.GenericIPAddressField(
+ protocol='IPv6',
+ unique=True
+ )
+ interface = models.ForeignKey('Interface', on_delete=models.CASCADE)
+ slaac_ip = models.BooleanField(default=False)
+
+ class Meta:
+ unique_together = (("interface", "slaac_ip"),)
+ permissions = (
+ ("view_ipv6list", "Peut voir un objet ipv6"),
+ ("change_ipv6list_slaac_ip", "Peut changer la valeur slaac sur une ipv6"),
+ )
+
+ def get_instance(ipv6listid, *args, **kwargs):
+ """Récupère une instance
+ :param interfaceid: Instance id à trouver
+ :return: Une instance interface évidemment"""
+ return Ipv6List.objects.get(pk=ipv6listid)
+
+ def can_create(user_request, interfaceid, *args, **kwargs):
+ """Verifie que l'user a les bons droits infra pour créer
+ une ipv6, ou possède l'interface associée
+ :param interfaceid: Id de l'interface associée à cet objet domain
+ :param user_request: instance utilisateur qui fait la requête
+ :return: soit True, soit False avec la raison de l'échec"""
+ try:
+ interface = Interface.objects.get(pk=interfaceid)
+ except Interface.DoesNotExist:
+ return False, u"Interface inexistante"
+ if not user_request.has_perm('machines.add_ipv6list'):
+ if interface.machine.user != user_request:
+ return False, u"Vous ne pouvez pas ajouter un alias à une\
+ machine d'un autre user que vous sans droit"
+ return True, None
+
+ @staticmethod
+ def can_change_slaac_ip(user_request, *args, **kwargs):
+ return user_request.has_perm('machines.change_ipv6list_slaac_ip'), "Droit requis pour changer la valeur slaac ip"
+
+ def can_edit(self, user_request, *args, **kwargs):
+ """Verifie que l'user a les bons droits infra pour editer
+ cette instance interface, ou qu'elle lui appartient
+ :param self: Instance interface à editer
+ :param user_request: Utilisateur qui fait la requête
+ :return: soit True, soit False avec la raison de l'échec"""
+ if self.interface.machine.user != user_request:
+ if not user_request.has_perm('machines.change_ipv6list') or not self.interface.machine.user.can_edit(user_request, *args, **kwargs)[0]:
+ return False, u"Vous ne pouvez pas éditer une machine\
+ d'un autre user que vous sans droit"
+ return True, None
+
+ def can_delete(self, user_request, *args, **kwargs):
+ """Verifie que l'user a les bons droits delete object pour del
+ cette instance interface, ou qu'elle lui appartient
+ :param self: Instance interface à del
+ :param user_request: Utilisateur qui fait la requête
+ :return: soit True, soit False avec la raison de l'échec"""
+ if self.interface.machine.user != user_request:
+ if not user_request.has_perm('machines.change_ipv6list') or not self.interface.machine.user.can_edit(user_request, *args, **kwargs)[0]:
+ return False, u"Vous ne pouvez pas éditer une machine\
+ d'un autre user que vous sans droit"
+ return True, None
+
+ def can_view_all(user_request, *args, **kwargs):
+ """Vérifie qu'on peut bien afficher l'ensemble des interfaces,
+ droit particulier view objet correspondant
+ :param user_request: instance user qui fait l'edition
+ :return: True ou False avec la raison de l'échec le cas échéant"""
+ if not user_request.has_perm('machines.view_ipv6list'):
+ return False, u"Vous n'avez pas le droit de voir des machines autre\
+ que les vôtres"
+ return True, None
+
+ def can_view(self, user_request, *args, **kwargs):
+ """Vérifie qu'on peut bien voir cette instance particulière avec
+ droit view objet ou qu'elle appartient à l'user
+ :param self: instance interface à voir
+ :param user_request: instance user qui fait l'edition
+ :return: True ou False avec la raison de l'échec le cas échéant"""
+ if not user_request.has_perm('machines.view_ipv6list') and self.interface.machine.user != user_request:
+ return False, u"Vous n'avez pas le droit de voir des machines autre\
+ que les vôtres"
+ return True, None
+
+ def __init__(self, *args, **kwargs):
+ super(Ipv6List, self).__init__(*args, **kwargs)
+ self.field_permissions = {
+ 'slaac_ip' : self.can_change_slaac_ip,
+ }
+
+ def __str__(self):
+ return str(self.ipv6)
+
+
class Domain(models.Model):
""" Objet domain. Enregistrement A et CNAME en même temps : permet de
stocker les alias et les nom de machines, suivant si interface_parent
@@ -2039,6 +2156,7 @@ def interface_post_save(sender, **kwargs):
"""Synchronisation ldap et régen parefeu/dhcp lors de la modification
d'une interface"""
interface = kwargs['instance']
+ interface.sync_ipv6_slaac()
user = interface.machine.user
user.ldap_sync(base=False, access_refresh=False, mac_refresh=True)
# Regen services
diff --git a/machines/templates/machines/aff_ipv6.html b/machines/templates/machines/aff_ipv6.html
new file mode 100644
index 00000000..b6efa3f0
--- /dev/null
+++ b/machines/templates/machines/aff_ipv6.html
@@ -0,0 +1,51 @@
+{% comment %}
+Re2o est 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.
+{% endcomment %}
+
+{% load acl %}
+
+
+
+
+ Ipv6 |
+ Slaac |
+ |
+
+
+ {% for ipv6 in ipv6_list %}
+
+ {{ ipv6.ipv6 }} |
+ {{ ipv6.slaac_ip }} |
+
+ {% can_edit ipv6 %}
+ {% include 'buttons/edit.html' with href='machines:edit-ipv6list' id=ipv6.id %}
+ {% acl_end %}
+ {% can_delete ipv6 %}
+ {% include 'buttons/suppr.html' with href='machines:del-ipv6list' id=ipv6.id %}
+ {% acl_end %}
+ {% include 'buttons/history.html' with href='machines:history' name='ipv6list' id=ipv6.id %}
+ |
+
+ {% endfor %}
+
+
diff --git a/machines/templates/machines/aff_machines.html b/machines/templates/machines/aff_machines.html
index 124aaf4d..ac71dc30 100644
--- a/machines/templates/machines/aff_machines.html
+++ b/machines/templates/machines/aff_machines.html
@@ -83,7 +83,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
IPv4 {{ interface.ipv4 }}
{% if ipv6_enabled and interface.ipv6 != 'None'%}
- IPv6 {{ interface.ipv6 }}
+ IPv6 {{ interface.ipv6|join:"," }}
{% endif %}
@@ -110,6 +110,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% acl_end %}
+ {% can_create Ipv6List interface.id %}
+
+
+ Gerer les ipv6
+
+
+ {% acl_end %}
{% can_create OuverturePortList %}
diff --git a/machines/templates/machines/index_ipv6.html b/machines/templates/machines/index_ipv6.html
new file mode 100644
index 00000000..3cc3933b
--- /dev/null
+++ b/machines/templates/machines/index_ipv6.html
@@ -0,0 +1,41 @@
+{% extends "machines/sidebar.html" %}
+{% comment %}
+Re2o est 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.
+{% endcomment %}
+
+{% load bootstrap3 %}
+{% load acl %}
+
+{% block title %}Machines{% endblock %}
+
+{% block content %}
+ Liste des ipv6 de l'interface
+ {% can_create Ipv6List interface_id %}
+ Ajouter une ipv6
+ {% acl_end %}
+ {% include "machines/aff_ipv6.html" with ipv6_list=ipv6_list %}
+
+
+
+{% endblock %}
+
diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html
index 9159a31d..756b68d2 100644
--- a/machines/templates/machines/machine.html
+++ b/machines/templates/machines/machine.html
@@ -72,6 +72,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if nasform %}
{% bootstrap_form_errors nasform %}
{% endif %}
+{% if ipv6form %}
+ {% bootstrap_form_errors ipv6form %}
+{% endif %}
diff --git a/machines/urls.py b/machines/urls.py
index 41f443e9..e3454097 100644
--- a/machines/urls.py
+++ b/machines/urls.py
@@ -64,6 +64,10 @@ urlpatterns = [
url(r'^edit_alias/(?P[0-9]+)$', views.edit_alias, name='edit-alias'),
url(r'^del_alias/(?P[0-9]+)$', views.del_alias, name='del-alias'),
url(r'^index_alias/(?P[0-9]+)$', views.index_alias, name='index-alias'),
+ url(r'^new_ipv6list/(?P[0-9]+)$', views.new_ipv6list, name='new-ipv6list'),
+ url(r'^edit_ipv6list/(?P[0-9]+)$', views.edit_ipv6list, name='edit-ipv6list'),
+ url(r'^del_ipv6list/(?P[0-9]+)$', views.del_ipv6list, name='del-ipv6list'),
+ url(r'^index_ipv6/(?P[0-9]+)$', views.index_ipv6, name='index-ipv6'),
url(r'^add_service/$', views.add_service, name='add-service'),
url(r'^edit_service/(?P[0-9]+)$', views.edit_service, name='edit-service'),
url(r'^del_service/$', views.del_service, name='del-service'),
diff --git a/machines/views.py b/machines/views.py
index c850609e..dff298ef 100644
--- a/machines/views.py
+++ b/machines/views.py
@@ -93,6 +93,7 @@ from .forms import (
DelNasForm,
SrvForm,
DelSrvForm,
+ Ipv6ListForm,
)
from .forms import EditOuverturePortListForm, EditOuverturePortConfigForm
from .models import (
@@ -114,6 +115,7 @@ from .models import (
Srv,
OuverturePortList,
OuverturePort,
+ Ipv6List,
)
from users.models import User
from preferences.models import GeneralOption, OptionalMachine
@@ -368,6 +370,59 @@ def del_interface(request, interface, interfaceid):
))
return form({'objet': interface, 'objet_name': 'interface'}, 'machines/delete.html', request)
+@login_required
+@can_create(Ipv6List)
+@can_edit(Interface)
+def new_ipv6list(request, interface, interfaceid):
+ """Nouvelle ipv6"""
+ ipv6 = Ipv6ListForm(request.POST or None, user=request.user)
+ if ipv6.is_valid():
+ ipv6 = ipv6.save(commit=False)
+ ipv6.interface = interface
+ with transaction.atomic(), reversion.create_revision():
+ ipv6.save()
+ reversion.set_user(request.user)
+ reversion.set_comment("Création")
+ messages.success(request, "Ipv6 ajoutée")
+ return redirect(reverse(
+ 'machines:index-ipv6',
+ kwargs={'interfaceid':str(interface.id)}
+ ))
+ return form({'ipv6form': ipv6}, 'machines/machine.html', request)
+
+@login_required
+@can_edit(Ipv6List)
+def edit_ipv6list(request, ipv6list_instance, ipv6listid):
+ """Edition d'une ipv6"""
+ ipv6 = Ipv6ListForm(request.POST or None, instance=ipv6list_instance, user=request.user)
+ if ipv6.is_valid():
+ with transaction.atomic(), reversion.create_revision():
+ ipv6.save()
+ reversion.set_user(request.user)
+ reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in ipv6.changed_data))
+ messages.success(request, "Ipv6 modifiée")
+ return redirect(reverse(
+ 'machines:index-ipv6',
+ kwargs={'interfaceid':str(ipv6list_instance.interface.id)}
+ ))
+ return form({'ipv6form': ipv6}, 'machines/machine.html', request)
+
+@login_required
+@can_delete(Ipv6List)
+def del_ipv6list(request, ipv6list, ipv6listid):
+ """ Supprime une ipv6"""
+ if request.method == "POST":
+ interfaceid = ipv6list.interface.id
+ with transaction.atomic(), reversion.create_revision():
+ ipv6list.delete()
+ reversion.set_user(request.user)
+ messages.success(request, "L'ipv6 a été détruite")
+ return redirect(reverse(
+ 'machines:index-ipv6',
+ kwargs={'interfaceid':str(interfaceid)}
+ ))
+ return form({'objet': ipv6list, 'objet_name': 'ipv6'}, 'machines/delete.html', request)
+
@login_required
@can_create(IpType)
def add_iptype(request):
@@ -994,6 +1049,12 @@ def index_alias(request, interface, interfaceid):
alias_list = Domain.objects.filter(cname=Domain.objects.filter(interface_parent=interface)).order_by('name')
return render(request, 'machines/index_alias.html', {'alias_list':alias_list, 'interface_id': interfaceid})
+@login_required
+@can_edit(Interface)
+def index_ipv6(request, interface, interfaceid):
+ ipv6_list = Ipv6List.objects.filter(interface=interface)
+ return render(request, 'machines/index_ipv6.html', {'ipv6_list':ipv6_list, 'interface_id': interfaceid})
+
@login_required
@can_view_all(Service)
def index_service(request):
@@ -1209,7 +1270,7 @@ def ouverture_ports(request):
d["udp_out"] = d.get("udp_out",set()).union(pl["udp_out"])
r['ipv4'][i.ipv4.ipv4] = d
if i.ipv6_object:
- d = r['ipv6'].get(i.ipv6, {})
+ d = r['ipv6'].get(i.ipv6.first(), {})
d["tcp_in"] = d.get("tcp_in",set()).union(pl["tcp_in"])
d["tcp_out"] = d.get("tcp_out",set()).union(pl["tcp_out"])
d["udp_in"] = d.get("udp_in",set()).union(pl["udp_in"])
diff --git a/preferences/migrations/0028_auto_20180128_2203.py b/preferences/migrations/0028_auto_20180128_2203.py
new file mode 100644
index 00000000..ac8894fd
--- /dev/null
+++ b/preferences/migrations/0028_auto_20180128_2203.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-01-28 21:03
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0027_merge_20180106_2019'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='optionalmachine',
+ name='ipv6',
+ ),
+ migrations.AddField(
+ model_name='optionalmachine',
+ name='ipv6_mode',
+ field=models.CharField(choices=[('SLAAC', 'Autoconfiguration par RA'), ('DHCPV6', 'Attribution des ip par dhcpv6'), ('DISABLED', 'Désactivé')], default='DISABLED', max_length=32),
+ ),
+ ]
diff --git a/preferences/models.py b/preferences/models.py
index 8dfc4260..af437cf7 100644
--- a/preferences/models.py
+++ b/preferences/models.py
@@ -25,6 +25,7 @@ Reglages généraux, machines, utilisateurs, mail, general pour l'application.
"""
from __future__ import unicode_literals
+from django.utils.functional import cached_property
from django.db import models
import cotisations.models
@@ -115,10 +116,27 @@ class OptionalMachine(models.Model):
sans droit, activation de l'ipv6"""
PRETTY_NAME = "Options machines"
+ SLAAC = 'SLAAC'
+ DHCPV6 = 'DHCPV6'
+ DISABLED = 'DISABLED'
+ CHOICE_IPV6 = (
+ (SLAAC, 'Autoconfiguration par RA'),
+ (DHCPV6, 'Attribution des ip par dhcpv6'),
+ (DISABLED, 'Désactivé'),
+ )
+
password_machine = models.BooleanField(default=False)
max_lambdauser_interfaces = models.IntegerField(default=10)
max_lambdauser_aliases = models.IntegerField(default=10)
- ipv6 = models.BooleanField(default=False)
+ ipv6_mode = models.CharField(
+ max_length=32,
+ choices=CHOICE_IPV6,
+ default='DISABLED'
+ )
+
+ @cached_property
+ def ipv6(self):
+ return not self.ipv6_mode == 'DISABLED'
class Meta:
permissions = (
diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html
index 7802929d..7ea53c40 100644
--- a/preferences/templates/preferences/display_preferences.html
+++ b/preferences/templates/preferences/display_preferences.html
@@ -74,7 +74,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Alias dns autorisé par utilisateur |
{{ machineoptions.max_lambdauser_aliases }} |
Support de l'ipv6 |
- {{ machineoptions.ipv6 }} |
+ {{ machineoptions.ipv6_mode }} |
Préférences topologie
diff --git a/re2o/templatetags/acl.py b/re2o/templatetags/acl.py
index a73d9278..28a7b4d3 100644
--- a/re2o/templatetags/acl.py
+++ b/re2o/templatetags/acl.py
@@ -106,6 +106,7 @@ MODEL_NAME = {
'Interface' : machines.models.Interface,
'Domain' : machines.models.Domain,
'IpList' : machines.models.IpList,
+ 'Ipv6List' : machines.models.Ipv6List,
'machines.Service' : machines.models.Service,
'Service_link' : machines.models.Service_link,
'OuverturePortList' : machines.models.OuverturePortList,
diff --git a/re2o/views.py b/re2o/views.py
index dce28b5d..cf08b08e 100644
--- a/re2o/views.py
+++ b/re2o/views.py
@@ -94,6 +94,7 @@ HISTORY_BIND = {
'service' : machines.models.Service,
'vlan' : machines.models.Vlan,
'nas' : machines.models.Vlan,
+ 'ipv6list' : machines.models.Ipv6List,
},
}
|