{% include 'inc/panel_table.html' with table=child_interfaces_table heading="Child Interfaces" %}
diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py
index 12bf4baab..96b2cb071 100644
--- a/netbox/tenancy/views.py
+++ b/netbox/tenancy/views.py
@@ -1,6 +1,6 @@
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
from netbox.views import generic
from utilities.query import count_related
diff --git a/netbox/translations/en/LC_MESSAGES/django.po b/netbox/translations/en/LC_MESSAGES/django.po
index 35a1fae7a..7692853f4 100644
--- a/netbox/translations/en/LC_MESSAGES/django.po
+++ b/netbox/translations/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-10-28 19:20+0000\n"
+"POT-Creation-Date: 2024-10-29 21:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
diff --git a/netbox/virtualization/api/serializers_/clusters.py b/netbox/virtualization/api/serializers_/clusters.py
index adc31a73c..101a5b5a3 100644
--- a/netbox/virtualization/api/serializers_/clusters.py
+++ b/netbox/virtualization/api/serializers_/clusters.py
@@ -1,3 +1,4 @@
+from dcim.constants import LOCATION_SCOPE_TYPES
from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
@@ -5,7 +6,6 @@ from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountF
from netbox.api.serializers import NetBoxModelSerializer
from tenancy.api.serializers_.tenants import TenantSerializer
from virtualization.choices import *
-from virtualization.constants import CLUSTER_SCOPE_TYPES
from virtualization.models import Cluster, ClusterGroup, ClusterType
from utilities.api import get_serializer_for_model
@@ -51,7 +51,7 @@ class ClusterSerializer(NetBoxModelSerializer):
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
scope_type = ContentTypeField(
queryset=ContentType.objects.filter(
- model__in=CLUSTER_SCOPE_TYPES
+ model__in=LOCATION_SCOPE_TYPES
),
allow_null=True,
required=False,
diff --git a/netbox/virtualization/api/serializers_/virtualmachines.py b/netbox/virtualization/api/serializers_/virtualmachines.py
index 1b224c16a..2c00cac96 100644
--- a/netbox/virtualization/api/serializers_/virtualmachines.py
+++ b/netbox/virtualization/api/serializers_/virtualmachines.py
@@ -8,7 +8,7 @@ from dcim.api.serializers_.sites import SiteSerializer
from dcim.choices import InterfaceModeChoices
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
from ipam.api.serializers_.ip import IPAddressSerializer
-from ipam.api.serializers_.vlans import VLANSerializer
+from ipam.api.serializers_.vlans import VLANSerializer, VLANTranslationPolicySerializer
from ipam.api.serializers_.vrfs import VRFSerializer
from ipam.models import VLAN
from netbox.api.fields import ChoiceField, SerializedPKRelatedField
@@ -89,6 +89,7 @@ class VMInterfaceSerializer(NetBoxModelSerializer):
required=False,
many=True
)
+ vlan_translation_policy = VLANTranslationPolicySerializer(nested=True, required=False, allow_null=True)
vrf = VRFSerializer(nested=True, required=False, allow_null=True)
l2vpn_termination = L2VPNTerminationSerializer(nested=True, read_only=True, allow_null=True)
count_ipaddresses = serializers.IntegerField(read_only=True)
@@ -105,6 +106,7 @@ class VMInterfaceSerializer(NetBoxModelSerializer):
'id', 'url', 'display_url', 'display', 'virtual_machine', 'name', 'enabled', 'parent', 'bridge', 'mtu',
'mac_address', 'description', 'mode', 'untagged_vlan', 'tagged_vlans', 'vrf', 'l2vpn_termination',
'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups',
+ 'vlan_translation_policy',
]
brief_fields = ('id', 'url', 'display', 'virtual_machine', 'name', 'description')
diff --git a/netbox/virtualization/constants.py b/netbox/virtualization/constants.py
deleted file mode 100644
index 6154b825e..000000000
--- a/netbox/virtualization/constants.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# models values for ContentTypes which may be Cluster scope types
-CLUSTER_SCOPE_TYPES = (
- 'region', 'sitegroup', 'site', 'location',
-)
diff --git a/netbox/virtualization/forms/bulk_edit.py b/netbox/virtualization/forms/bulk_edit.py
index 19b614ac6..aaeb259b9 100644
--- a/netbox/virtualization/forms/bulk_edit.py
+++ b/netbox/virtualization/forms/bulk_edit.py
@@ -1,20 +1,19 @@
from django import forms
-from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _
from dcim.choices import InterfaceModeChoices
from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
+from dcim.forms.mixins import ScopedBulkEditForm
from dcim.models import Device, DeviceRole, Platform, Site
from extras.models import ConfigTemplate
from ipam.models import VLAN, VLANGroup, VRF
from netbox.forms import NetBoxModelBulkEditForm
from tenancy.models import Tenant
-from utilities.forms import BulkRenameForm, add_blank_choice, get_field_value
-from utilities.forms.fields import CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
+from utilities.forms import BulkRenameForm, add_blank_choice
+from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
from utilities.forms.rendering import FieldSet
-from utilities.forms.widgets import BulkEditNullBooleanSelect, HTMXSelect
+from utilities.forms.widgets import BulkEditNullBooleanSelect
from virtualization.choices import *
-from virtualization.constants import CLUSTER_SCOPE_TYPES
from virtualization.models import *
__all__ = (
@@ -57,7 +56,7 @@ class ClusterGroupBulkEditForm(NetBoxModelBulkEditForm):
nullable_fields = ('description',)
-class ClusterBulkEditForm(NetBoxModelBulkEditForm):
+class ClusterBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
type = DynamicModelChoiceField(
label=_('Type'),
queryset=ClusterType.objects.all(),
@@ -79,19 +78,6 @@ class ClusterBulkEditForm(NetBoxModelBulkEditForm):
queryset=Tenant.objects.all(),
required=False
)
- scope_type = ContentTypeChoiceField(
- queryset=ContentType.objects.filter(model__in=CLUSTER_SCOPE_TYPES),
- widget=HTMXSelect(method='post', attrs={'hx-select': '#form_fields'}),
- required=False,
- label=_('Scope type')
- )
- scope = DynamicModelChoiceField(
- label=_('Scope'),
- queryset=Site.objects.none(), # Initial queryset
- required=False,
- disabled=True,
- selector=True
- )
description = forms.CharField(
label=_('Description'),
max_length=200,
@@ -109,21 +95,6 @@ class ClusterBulkEditForm(NetBoxModelBulkEditForm):
)
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- if scope_type_id := get_field_value(self, 'scope_type'):
- try:
- scope_type = ContentType.objects.get(pk=scope_type_id)
- model = scope_type.model_class()
- self.fields['scope'].queryset = model.objects.all()
- self.fields['scope'].widget.attrs['selector'] = model._meta.label_lower
- self.fields['scope'].disabled = False
- self.fields['scope'].label = _(bettertitle(model._meta.verbose_name))
- except ObjectDoesNotExist:
- pass
-
-
class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
status = forms.ChoiceField(
label=_('Status'),
diff --git a/netbox/virtualization/forms/bulk_import.py b/netbox/virtualization/forms/bulk_import.py
index b9126a8c0..9ccdd68f7 100644
--- a/netbox/virtualization/forms/bulk_import.py
+++ b/netbox/virtualization/forms/bulk_import.py
@@ -1,15 +1,14 @@
-from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _
from dcim.choices import InterfaceModeChoices
+from dcim.forms.mixins import ScopedImportForm
from dcim.models import Device, DeviceRole, Platform, Site
from extras.models import ConfigTemplate
from ipam.models import VRF
from netbox.forms import NetBoxModelImportForm
from tenancy.models import Tenant
-from utilities.forms.fields import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField
+from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
from virtualization.choices import *
-from virtualization.constants import CLUSTER_SCOPE_TYPES
from virtualization.models import *
__all__ = (
@@ -38,7 +37,7 @@ class ClusterGroupImportForm(NetBoxModelImportForm):
fields = ('name', 'slug', 'description', 'tags')
-class ClusterImportForm(NetBoxModelImportForm):
+class ClusterImportForm(ScopedImportForm, NetBoxModelImportForm):
type = CSVModelChoiceField(
label=_('Type'),
queryset=ClusterType.objects.all(),
@@ -57,11 +56,6 @@ class ClusterImportForm(NetBoxModelImportForm):
choices=ClusterStatusChoices,
help_text=_('Operational status')
)
- scope_type = CSVContentTypeField(
- queryset=ContentType.objects.filter(model__in=CLUSTER_SCOPE_TYPES),
- required=False,
- label=_('Scope type (app & model)')
- )
site = CSVModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
@@ -81,7 +75,7 @@ class ClusterImportForm(NetBoxModelImportForm):
model = Cluster
fields = ('name', 'type', 'group', 'status', 'scope_type', 'scope_id', 'tenant', 'description', 'comments', 'tags')
labels = {
- 'scope_id': 'Scope ID',
+ 'scope_id': _('Scope ID'),
}
diff --git a/netbox/virtualization/forms/model_forms.py b/netbox/virtualization/forms/model_forms.py
index 4550f0d61..d6835075b 100644
--- a/netbox/virtualization/forms/model_forms.py
+++ b/netbox/virtualization/forms/model_forms.py
@@ -4,19 +4,18 @@ from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from dcim.forms.common import InterfaceCommonForm
+from dcim.forms.mixins import ScopedForm
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
from extras.models import ConfigTemplate
-from ipam.models import IPAddress, VLAN, VLANGroup, VRF
-from netbox.forms import NetBoxModelForm, ScopedForm
+from ipam.models import IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF
+from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm
from utilities.forms import ConfirmationForm
from utilities.forms.fields import (
CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField,
)
-from utilities.forms.fields import ContentTypeChoiceField
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import HTMXSelect
-from virtualization.constants import CLUSTER_SCOPE_TYPES
from virtualization.models import *
__all__ = (
@@ -69,19 +68,6 @@ class ClusterForm(TenancyForm, ScopedForm, NetBoxModelForm):
queryset=ClusterGroup.objects.all(),
required=False
)
- scope_type = ContentTypeChoiceField(
- queryset=ContentType.objects.filter(model__in=CLUSTER_SCOPE_TYPES),
- widget=HTMXSelect(),
- required=False,
- label=_('Scope type')
- )
- scope = DynamicModelChoiceField(
- label=_('Scope'),
- queryset=Site.objects.none(), # Initial queryset
- required=False,
- disabled=True,
- selector=True
- )
comments = CommentField()
fieldsets = (
@@ -353,20 +339,25 @@ class VMInterfaceForm(InterfaceCommonForm, VMComponentForm):
required=False,
label=_('VRF')
)
+ vlan_translation_policy = DynamicModelChoiceField(
+ queryset=VLANTranslationPolicy.objects.all(),
+ required=False,
+ label=_('VLAN Translation Policy')
+ )
fieldsets = (
FieldSet('virtual_machine', 'name', 'description', 'tags', name=_('Interface')),
FieldSet('vrf', 'mac_address', name=_('Addressing')),
FieldSet('mtu', 'enabled', name=_('Operation')),
FieldSet('parent', 'bridge', name=_('Related Interfaces')),
- FieldSet('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', name=_('802.1Q Switching')),
+ FieldSet('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vlan_translation_policy', name=_('802.1Q Switching')),
)
class Meta:
model = VMInterface
fields = [
'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mac_address', 'mtu', 'description', 'mode',
- 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
+ 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags', 'vlan_translation_policy',
]
labels = {
'mode': '802.1Q Mode',
diff --git a/netbox/virtualization/graphql/types.py b/netbox/virtualization/graphql/types.py
index 4af31fc2e..c14c8e34d 100644
--- a/netbox/virtualization/graphql/types.py
+++ b/netbox/virtualization/graphql/types.py
@@ -31,7 +31,7 @@ class ComponentType(NetBoxObjectType):
@strawberry_django.type(
models.Cluster,
- exclude=('scope_type', 'scope_id', '_location', '_region', '_site', '_sitegroup'),
+ exclude=('scope_type', 'scope_id', '_location', '_region', '_site', '_site_group'),
filters=ClusterFilter
)
class ClusterType(VLANGroupsMixin, NetBoxObjectType):
@@ -107,6 +107,7 @@ class VMInterfaceType(IPAddressesMixin, ComponentType):
bridge: Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')] | None
untagged_vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
+ vlan_translation_policy: Annotated["VLANTranslationPolicyType", strawberry.lazy('ipam.graphql.types')] | None
tagged_vlans: List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]
bridge_interfaces: List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]
diff --git a/netbox/virtualization/migrations/0042_vminterface_vlan_translation_policy.py b/netbox/virtualization/migrations/0042_vminterface_vlan_translation_policy.py
new file mode 100644
index 000000000..e0992c9c8
--- /dev/null
+++ b/netbox/virtualization/migrations/0042_vminterface_vlan_translation_policy.py
@@ -0,0 +1,20 @@
+# Generated by Django 5.0.9 on 2024-10-11 19:45
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ipam', '0074_vlantranslationpolicy_vlantranslationrule'),
+ ('virtualization', '0041_charfield_null_choices'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='vminterface',
+ name='vlan_translation_policy',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='ipam.vlantranslationpolicy'),
+ ),
+ ]
diff --git a/netbox/virtualization/migrations/0042_cluster_scope.py b/netbox/virtualization/migrations/0043_cluster_scope.py
similarity index 95%
rename from netbox/virtualization/migrations/0042_cluster_scope.py
rename to netbox/virtualization/migrations/0043_cluster_scope.py
index ed8d8bd88..4df325093 100644
--- a/netbox/virtualization/migrations/0042_cluster_scope.py
+++ b/netbox/virtualization/migrations/0043_cluster_scope.py
@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
- ('virtualization', '0041_charfield_null_choices'),
+ ('virtualization', '0042_vminterface_vlan_translation_policy'),
]
operations = [
diff --git a/netbox/virtualization/migrations/0043_clusters_cached_relations.py b/netbox/virtualization/migrations/0044_clusters_cached_relations.py
similarity index 89%
rename from netbox/virtualization/migrations/0043_clusters_cached_relations.py
rename to netbox/virtualization/migrations/0044_clusters_cached_relations.py
index 5b0407b3d..32c3bd66c 100644
--- a/netbox/virtualization/migrations/0043_clusters_cached_relations.py
+++ b/netbox/virtualization/migrations/0044_clusters_cached_relations.py
@@ -4,24 +4,24 @@ from django.db import migrations, models
def populate_denormalized_fields(apps, schema_editor):
"""
- Copy site ForeignKey values to the scope GFK.
+ Copy the denormalized fields for _region, _site_group and _site from existing site field.
"""
Cluster = apps.get_model('virtualization', 'Cluster')
clusters = Cluster.objects.filter(site__isnull=False).prefetch_related('site')
for cluster in clusters:
cluster._region_id = cluster.site.region_id
- cluster._sitegroup_id = cluster.site.group_id
+ cluster._site_group_id = cluster.site.group_id
cluster._site_id = cluster.site_id
# Note: Location cannot be set prior to migration
- Cluster.objects.bulk_update(clusters, ['_region', '_sitegroup', '_site'])
+ Cluster.objects.bulk_update(clusters, ['_region', '_site_group', '_site'])
class Migration(migrations.Migration):
dependencies = [
- ('virtualization', '0042_cluster_scope'),
+ ('virtualization', '0043_cluster_scope'),
]
operations = [
@@ -60,7 +60,7 @@ class Migration(migrations.Migration):
),
migrations.AddField(
model_name='cluster',
- name='_sitegroup',
+ name='_site_group',
field=models.ForeignKey(
blank=True,
null=True,
diff --git a/netbox/virtualization/models/clusters.py b/netbox/virtualization/models/clusters.py
index ad2aaedc7..601ee7f23 100644
--- a/netbox/virtualization/models/clusters.py
+++ b/netbox/virtualization/models/clusters.py
@@ -1,5 +1,5 @@
from django.apps import apps
-from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
+from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
@@ -9,7 +9,6 @@ from dcim.models.mixins import CachedScopeMixin
from netbox.models import OrganizationalModel, PrimaryModel
from netbox.models.features import ContactsMixin
from virtualization.choices import *
-from virtualization.constants import CLUSTER_SCOPE_TYPES
__all__ = (
'Cluster',
@@ -79,22 +78,6 @@ class Cluster(ContactsMixin, CachedScopeMixin, PrimaryModel):
blank=True,
null=True
)
- scope_type = models.ForeignKey(
- to='contenttypes.ContentType',
- on_delete=models.PROTECT,
- limit_choices_to=models.Q(model__in=CLUSTER_SCOPE_TYPES),
- related_name='+',
- blank=True,
- null=True
- )
- scope_id = models.PositiveBigIntegerField(
- blank=True,
- null=True
- )
- scope = GenericForeignKey(
- ct_field='scope_type',
- fk_field='scope_id'
- )
# Generic relations
vlan_groups = GenericRelation(
diff --git a/netbox/virtualization/tests/test_filtersets.py b/netbox/virtualization/tests/test_filtersets.py
index 41cd7ca50..1c6ef5906 100644
--- a/netbox/virtualization/tests/test_filtersets.py
+++ b/netbox/virtualization/tests/test_filtersets.py
@@ -1,7 +1,7 @@
from django.test import TestCase
from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
-from ipam.models import IPAddress, VRF
+from ipam.models import IPAddress, VLANTranslationPolicy, VRF
from tenancy.models import Tenant, TenantGroup
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device
from virtualization.choices import *
@@ -563,6 +563,13 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
)
VirtualMachine.objects.bulk_create(vms)
+ vlan_translation_policies = (
+ VLANTranslationPolicy(name='Policy 1'),
+ VLANTranslationPolicy(name='Policy 2'),
+ VLANTranslationPolicy(name='Policy 3'),
+ )
+ VLANTranslationPolicy.objects.bulk_create(vlan_translation_policies)
+
interfaces = (
VMInterface(
virtual_machine=vms[0],
@@ -571,7 +578,8 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
mtu=100,
mac_address='00-00-00-00-00-01',
vrf=vrfs[0],
- description='foobar1'
+ description='foobar1',
+ vlan_translation_policy=vlan_translation_policies[0],
),
VMInterface(
virtual_machine=vms[1],
@@ -580,7 +588,8 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
mtu=200,
mac_address='00-00-00-00-00-02',
vrf=vrfs[1],
- description='foobar2'
+ description='foobar2',
+ vlan_translation_policy=vlan_translation_policies[0],
),
VMInterface(
virtual_machine=vms[2],
@@ -660,6 +669,13 @@ class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'description': ['foobar1', 'foobar2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ def test_vlan_translation_policy(self):
+ vlan_translation_policies = VLANTranslationPolicy.objects.all()[:2]
+ params = {'vlan_translation_policy_id': [vlan_translation_policies[0].pk, vlan_translation_policies[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'vlan_translation_policy': [vlan_translation_policies[0].name, vlan_translation_policies[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
class VirtualDiskTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = VirtualDisk.objects.all()
diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py
index 0828d3a2a..35f2f8f75 100644
--- a/netbox/virtualization/views.py
+++ b/netbox/virtualization/views.py
@@ -6,7 +6,7 @@ from django.db.models import Prefetch, Sum
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy as _
from django.views.generic.base import RedirectView
from jinja2.exceptions import TemplateError
@@ -16,7 +16,7 @@ from dcim.models import Device
from dcim.tables import DeviceTable
from extras.views import ObjectConfigContextView
from ipam.models import IPAddress
-from ipam.tables import InterfaceVLANTable
+from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
from netbox.views import generic
from tenancy.views import ObjectContactsView
@@ -516,6 +516,14 @@ class VMInterfaceView(generic.ObjectView):
orderable=False
)
+ # Get VLAN translation rules
+ vlan_translation_table = None
+ if instance.vlan_translation_policy:
+ vlan_translation_table = VLANTranslationRuleTable(
+ data=instance.vlan_translation_policy.rules.all(),
+ orderable=False
+ )
+
# Get assigned VLANs and annotate whether each is tagged or untagged
vlans = []
if instance.untagged_vlan is not None:
@@ -533,6 +541,7 @@ class VMInterfaceView(generic.ObjectView):
return {
'child_interfaces_table': child_interfaces_tables,
'vlan_table': vlan_table,
+ 'vlan_translation_table': vlan_translation_table,
}