mirror of
https://github.com/netbox-community/netbox.git
synced 2026-02-05 14:56:24 -06:00
* VLANTranslationPolicy and VLANTranslationRule models and all associated UI classes
* Change VLANTranslationPolicy to a PrimaryModel and make name unique
* Add serializer classes to InterfaceSerializer
* Remake migrations
* Add GraphQL typing
* Skip tagged models in test
* Missing migration
* Remove get_absolute_url methods
* Remove package-lock.json
* Rebuild migration and add constraints and field options
* Rebuild migrations
* Use DynamicModelChoiceField for policy field
* Make vlan_translation_policy fields on filtersets more consistent with existing __name convention
* Add vlan_translation_table to VMInterface detail page
* Add vlan_translation_policy to VMInterfaceSerializer
* Move vlan_translation_policy fields to model and filterset mixins
* Protect in-use policies against deletion
* Add vlan_translation_policy to fields in VMInterfaceSerializer
* Cleanup indentation
* Remove unnecessary ordering column
* Rebuild migrations
* Search methods and registration
* Ensure 'id' column is present by default
* Add graphql types/filters/schema for VLANTranslationRule
* Filterset tests
* View tests
* API and viewset tests (incomplete)
* Add tags to VLANTranslationRuleForm
* Complete viewset tests for VLANTranslationRule
* Make VLANTranslationRule.policy nullable (but still required)
* Revert "Make VLANTranslationRule.policy nullable (but still required)"
This reverts commit 4c1bb437ef.
* Revert nullability
* Explicitly prefetch policy in graphql
* Documentation of new and affected models
* Add note about select_related in graphql
* Rework policy/rule documentation
* Move vlan_translation_policy into 802.1Q Switching fieldset
* Remove redundant InterfaceVLANTranslationTable
* Conditionally include vlan_translation_table in interface.html and vminterface.html
* Add description field to VLANTranslationRule
* Define vlan_translation_table conditionally
* Add policy (name) filter to VLANTranslationRuleFilterSet
* Revert changes to adding-models.md (moved to another PR)
* Dynamic table for linked rules in vlantranslationpolicy.html
* Misc cleanup
---------
Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
@@ -8,7 +8,7 @@ from dcim.models import (
|
||||
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort,
|
||||
RearPort, VirtualDeviceContext,
|
||||
)
|
||||
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, ContentTypeField, SerializedPKRelatedField
|
||||
@@ -196,6 +196,7 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
|
||||
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)
|
||||
wireless_link = NestedWirelessLinkSerializer(read_only=True, allow_null=True)
|
||||
@@ -225,7 +226,7 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
|
||||
'tx_power', 'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable', 'cable_end', 'wireless_link',
|
||||
'link_peers', 'link_peers_type', 'wireless_lans', 'vrf', 'l2vpn_termination', 'connected_endpoints',
|
||||
'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
|
||||
'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied', 'vlan_translation_policy'
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from circuits.models import CircuitTermination
|
||||
from extras.filtersets import LocalConfigContextFilterSet
|
||||
from extras.models import ConfigTemplate
|
||||
from ipam.filtersets import PrimaryIPFilterSet
|
||||
from ipam.models import ASN, IPAddress, VRF
|
||||
from ipam.models import ASN, IPAddress, VLANTranslationPolicy, VRF
|
||||
from netbox.choices import ColorChoices
|
||||
from netbox.filtersets import (
|
||||
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet,
|
||||
@@ -1629,6 +1629,17 @@ class CommonInterfaceFilterSet(django_filters.FilterSet):
|
||||
to_field_name='identifier',
|
||||
label=_('L2VPN'),
|
||||
)
|
||||
vlan_translation_policy_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='vlan_translation_policy',
|
||||
queryset=VLANTranslationPolicy.objects.all(),
|
||||
label=_('VLAN Translation Policy (ID)'),
|
||||
)
|
||||
vlan_translation_policy = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='vlan_translation_policy__name',
|
||||
queryset=VLANTranslationPolicy.objects.all(),
|
||||
to_field_name='name',
|
||||
label=_('VLAN Translation Policy'),
|
||||
)
|
||||
|
||||
def filter_vlan_id(self, queryset, name, value):
|
||||
value = value.strip()
|
||||
|
||||
@@ -7,7 +7,7 @@ from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import *
|
||||
from extras.models import ConfigTemplate
|
||||
from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VRF
|
||||
from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from users.models import User
|
||||
@@ -1382,6 +1382,11 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
||||
required=False,
|
||||
label=_('WWN')
|
||||
)
|
||||
vlan_translation_policy = DynamicModelChoiceField(
|
||||
queryset=VLANTranslationPolicy.objects.all(),
|
||||
required=False,
|
||||
label=_('VLAN Translation Policy')
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
@@ -1391,7 +1396,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
||||
FieldSet('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected', name=_('Operation')),
|
||||
FieldSet('parent', 'bridge', 'lag', name=_('Related Interfaces')),
|
||||
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
|
||||
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')),
|
||||
FieldSet(
|
||||
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
|
||||
name=_('Wireless')
|
||||
@@ -1404,7 +1409,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
||||
'device', 'module', 'vdcs', 'name', 'label', 'type', 'speed', 'duplex', 'enabled', 'parent', 'bridge', 'lag',
|
||||
'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'poe_mode', 'poe_type', 'mode',
|
||||
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'wireless_lans',
|
||||
'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
|
||||
'untagged_vlan', 'tagged_vlans', 'vrf', 'tags', 'vlan_translation_policy',
|
||||
]
|
||||
widgets = {
|
||||
'speed': NumberWithOptions(
|
||||
|
||||
@@ -385,6 +385,7 @@ class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, P
|
||||
wireless_link: Annotated["WirelessLinkType", strawberry.lazy('wireless.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
|
||||
|
||||
vdcs: List[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]]
|
||||
tagged_vlans: List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]
|
||||
|
||||
@@ -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 = [
|
||||
('dcim', '0194_charfield_null_choices'),
|
||||
('ipam', '0074_vlantranslationpolicy_vlantranslationrule'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='vlan_translation_policy',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='ipam.vlantranslationpolicy'),
|
||||
),
|
||||
]
|
||||
@@ -547,6 +547,13 @@ class BaseInterface(models.Model):
|
||||
blank=True,
|
||||
verbose_name=_('bridge interface')
|
||||
)
|
||||
vlan_translation_policy = models.ForeignKey(
|
||||
to='ipam.VLANTranslationPolicy',
|
||||
on_delete=models.PROTECT,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_('VLAN Translation Policy'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@@ -4,7 +4,7 @@ from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
from dcim.choices import *
|
||||
from dcim.filtersets import *
|
||||
from dcim.models import *
|
||||
from ipam.models import ASN, IPAddress, RIR, VRF
|
||||
from ipam.models import ASN, IPAddress, RIR, VLANTranslationPolicy, VRF
|
||||
from netbox.choices import ColorChoices, WeightUnitChoices
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from users.models import User
|
||||
@@ -3669,6 +3669,13 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
)
|
||||
VirtualDeviceContext.objects.bulk_create(vdcs)
|
||||
|
||||
vlan_translation_policies = (
|
||||
VLANTranslationPolicy(name='Policy 1'),
|
||||
VLANTranslationPolicy(name='Policy 2'),
|
||||
VLANTranslationPolicy(name='Policy 3'),
|
||||
)
|
||||
VLANTranslationPolicy.objects.bulk_create(vlan_translation_policies)
|
||||
|
||||
interfaces = (
|
||||
Interface(
|
||||
device=devices[0],
|
||||
@@ -3686,7 +3693,8 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
speed=1000000,
|
||||
duplex='half',
|
||||
poe_mode=InterfacePoEModeChoices.MODE_PSE,
|
||||
poe_type=InterfacePoETypeChoices.TYPE_1_8023AF
|
||||
poe_type=InterfacePoETypeChoices.TYPE_1_8023AF,
|
||||
vlan_translation_policy=vlan_translation_policies[0],
|
||||
),
|
||||
Interface(
|
||||
device=devices[1],
|
||||
@@ -3711,7 +3719,8 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
speed=1000000,
|
||||
duplex='full',
|
||||
poe_mode=InterfacePoEModeChoices.MODE_PD,
|
||||
poe_type=InterfacePoETypeChoices.TYPE_1_8023AF
|
||||
poe_type=InterfacePoETypeChoices.TYPE_1_8023AF,
|
||||
vlan_translation_policy=vlan_translation_policies[0],
|
||||
),
|
||||
Interface(
|
||||
device=devices[3],
|
||||
@@ -3729,7 +3738,8 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
speed=100000,
|
||||
duplex='half',
|
||||
poe_mode=InterfacePoEModeChoices.MODE_PSE,
|
||||
poe_type=InterfacePoETypeChoices.TYPE_2_8023AT
|
||||
poe_type=InterfacePoETypeChoices.TYPE_2_8023AT,
|
||||
vlan_translation_policy=vlan_translation_policies[1],
|
||||
),
|
||||
Interface(
|
||||
device=devices[4],
|
||||
@@ -3742,7 +3752,8 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
speed=100000,
|
||||
duplex='full',
|
||||
poe_mode=InterfacePoEModeChoices.MODE_PD,
|
||||
poe_type=InterfacePoETypeChoices.TYPE_2_8023AT
|
||||
poe_type=InterfacePoETypeChoices.TYPE_2_8023AT,
|
||||
vlan_translation_policy=vlan_translation_policies[1],
|
||||
),
|
||||
Interface(
|
||||
device=devices[4],
|
||||
@@ -4016,6 +4027,13 @@ class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFil
|
||||
params = {'vdc_identifier': vdc.values_list('identifier', flat=True)}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
|
||||
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(), 4)
|
||||
params = {'vlan_translation_policy': [vlan_translation_policies[0].name, vlan_translation_policies[1].name]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||
|
||||
|
||||
class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
|
||||
queryset = FrontPort.objects.all()
|
||||
|
||||
+10
-1
@@ -18,7 +18,7 @@ from jinja2.exceptions import TemplateError
|
||||
from circuits.models import Circuit, CircuitTermination
|
||||
from extras.views import ObjectConfigContextView
|
||||
from ipam.models import ASN, IPAddress, VLANGroup
|
||||
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
|
||||
@@ -2580,11 +2580,20 @@ class InterfaceView(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
|
||||
)
|
||||
|
||||
return {
|
||||
'vdc_table': vdc_table,
|
||||
'bridge_interfaces_table': bridge_interfaces_tables,
|
||||
'child_interfaces_table': child_interfaces_tables,
|
||||
'vlan_table': vlan_table,
|
||||
'vlan_translation_table': vlan_translation_table,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user