diff --git a/netbox/ipam/choices.py b/netbox/ipam/choices.py index 298baa64329..72d3f542a08 100644 --- a/netbox/ipam/choices.py +++ b/netbox/ipam/choices.py @@ -155,6 +155,16 @@ class VLANStatusChoices(ChoiceSet): ] +class VLANAssignmentTypeChoices(ChoiceSet): + VLAN_GROUP = 'vlan_group' + SITE = 'site' + + CHOICES = [ + (VLAN_GROUP, 'VLAN Group'), + (SITE, 'Site'), + ] + + # # Services # diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index eb6dbe598eb..ef77238f90a 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -16,7 +16,8 @@ CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, SlugField, ) -from utilities.forms.widgets import DatePicker +from utilities.forms.utils import get_field_value +from utilities.forms.widgets import DatePicker, HTMXSelect from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface __all__ = ( @@ -633,12 +634,42 @@ class VLANForm(TenancyForm, NetBoxModelForm): ) comments = CommentField() + fieldsets = ( + ('VLAN', ('vid', 'name', 'status', 'role', 'description', 'tags')), + ('Tenancy', ('tenant_group', 'tenant')), + ('Assignment', ('assignment_type', 'site', 'group')), + ) + class Meta: model = VLAN fields = [ - 'site', 'group', 'vid', 'name', 'status', 'role', 'tenant_group', 'tenant', 'description', 'comments', - 'tags', + 'assignment_type', 'site', 'group', 'vid', 'name', 'status', 'role', 'tenant_group', 'tenant', + 'description', 'comments', 'tags', ] + widgets = { + 'assignment_type': HTMXSelect(), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + assignment_type = get_field_value(self, 'assignment_type') + + if assignment_type != VLANAssignmentTypeChoices.VLAN_GROUP: + del self.fields['group'] + if assignment_type != VLANAssignmentTypeChoices.SITE: + del self.fields['site'] + + def clean(self): + super().clean() + + assignment_type = self.cleaned_data.get('assignment_type') + + if assignment_type != VLANAssignmentTypeChoices.VLAN_GROUP: + self.cleaned_data['group'] = None + + if assignment_type != VLANAssignmentTypeChoices.SITE: + self.cleaned_data['site'] = None class ServiceTemplateForm(NetBoxModelForm): diff --git a/netbox/ipam/migrations/0067_vlan_assignment_type.py b/netbox/ipam/migrations/0067_vlan_assignment_type.py new file mode 100644 index 00000000000..b31aec758a4 --- /dev/null +++ b/netbox/ipam/migrations/0067_vlan_assignment_type.py @@ -0,0 +1,37 @@ +import sys +from django.db import migrations, models +from ipam.choices import VLANAssignmentTypeChoices + + +def populate_assignment_type_field(apps, schema_editor): + VLAN = apps.get_model('ipam', 'VLAN') + + total_count = VLAN.objects.count() + if 'test' not in sys.argv: + print(f'\nUpdating {total_count} VLANs...') + + for row in VLAN.objects.all(): + if row.group: + row.assignment_type = VLANAssignmentTypeChoices.VLAN_GROUP + elif row.site: + row.assignment_type = VLANAssignmentTypeChoices.SITE + else: + # Assign the default value if nothing else matches + row.assignment_type = VLANAssignmentTypeChoices.VLAN_GROUP + row.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0066_iprange_mark_utilized'), + ] + + operations = [ + migrations.AddField( + model_name='vlan', + name='assignment_type', + field=models.CharField(default='vlan_group', max_length=50), + ), + migrations.RunPython(populate_assignment_type_field), + ] diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index 7d4777da97f..0d07eb12a1a 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -124,6 +124,12 @@ class VLAN(PrimaryModel): Like Prefixes, each VLAN is assigned an operational status and optionally a user-defined Role. A VLAN can have zero or more Prefixes assigned to it. """ + assignment_type = models.CharField( + verbose_name='Assignment Type', + max_length=50, + choices=VLANAssignmentTypeChoices, + default=VLANAssignmentTypeChoices.VLAN_GROUP + ) site = models.ForeignKey( to='dcim.Site', on_delete=models.PROTECT, @@ -183,7 +189,7 @@ class VLAN(PrimaryModel): objects = VLANQuerySet.as_manager() clone_fields = [ - 'site', 'group', 'tenant', 'status', 'role', 'description', + 'assignment_type', 'site', 'group', 'tenant', 'status', 'role', 'description', ] class Meta: diff --git a/netbox/ipam/tables/vlans.py b/netbox/ipam/tables/vlans.py index 6fa2cd2da48..8063210c18d 100644 --- a/netbox/ipam/tables/vlans.py +++ b/netbox/ipam/tables/vlans.py @@ -129,8 +129,8 @@ class VLANTable(TenancyColumnsMixin, NetBoxTable): class Meta(NetBoxTable.Meta): model = VLAN fields = ( - 'pk', 'id', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'tenant_group', 'status', 'role', - 'description', 'comments', 'tags', 'l2vpn', 'created', 'last_updated', + 'pk', 'id', 'vid', 'name', 'assignment_type', 'site', 'group', 'prefixes', 'tenant', 'tenant_group', + 'status', 'role', 'description', 'comments', 'tags', 'l2vpn', 'created', 'last_updated', ) default_columns = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description') row_attrs = { diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index 44af9eae210..c70e89e2854 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -763,6 +763,7 @@ def setUpTestData(cls): tags = create_tags('Alpha', 'Bravo', 'Charlie') cls.form_data = { + 'assignment_type': VLANAssignmentTypeChoices.VLAN_GROUP, 'site': sites[1].pk, 'group': vlangroups[1].pk, 'vid': 999, diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 6b73a061bc2..308d1562499 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -1120,7 +1120,6 @@ def get_children(self, request, parent): class VLANEditView(generic.ObjectEditView): queryset = VLAN.objects.all() form = forms.VLANForm - template_name = 'ipam/vlan_edit.html' @register_model_view(VLAN, 'delete') diff --git a/netbox/templates/ipam/vlan_edit.html b/netbox/templates/ipam/vlan_edit.html deleted file mode 100644 index 7b8c85cb856..00000000000 --- a/netbox/templates/ipam/vlan_edit.html +++ /dev/null @@ -1,67 +0,0 @@ -{% extends 'generic/object_edit.html' %} -{% load static %} -{% load form_helpers %} -{% load helpers %} - -{% block form %} -