From 97b8e73716c8d6a76f9e11eb370d6273050a6889 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 22 Apr 2020 11:15:39 -0400 Subject: [PATCH] Introduce model-specific bulk create forms for device components --- netbox/dcim/forms.py | 230 +++++++++++++++++++++++--------------- netbox/dcim/views.py | 12 +- netbox/utilities/forms.py | 10 ++ 3 files changed, 155 insertions(+), 97 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 48b0de903..c51332b76 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -23,8 +23,9 @@ from tenancy.models import Tenant, TenantGroup from utilities.forms import ( APISelect, APISelectMultiple, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, DynamicModelChoiceField, - DynamicModelMultipleChoiceField, ExpandableNameField, FlexibleModelChoiceField, JSONField, SelectWithPK, - SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, + DynamicModelMultipleChoiceField, ExpandableNameField, FlexibleModelChoiceField, form_from_model, JSONField, + SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField, + BOOLEAN_WITH_BLANK_CHOICES, ) from virtualization.models import Cluster, ClusterGroup, VirtualMachine from .choices import * @@ -2299,31 +2300,6 @@ class DeviceBulkAddComponentForm(BootstrapMixin, forms.Form): ) -class DeviceBulkAddInterfaceForm(DeviceBulkAddComponentForm): - type = forms.ChoiceField( - choices=InterfaceTypeChoices, - widget=StaticSelect2() - ) - enabled = forms.BooleanField( - required=False, - initial=True - ) - mtu = forms.IntegerField( - required=False, - min_value=INTERFACE_MTU_MIN, - max_value=INTERFACE_MTU_MAX, - label='MTU' - ) - mgmt_only = forms.BooleanField( - required=False, - label='Management only' - ) - description = forms.CharField( - max_length=100, - required=False - ) - - # # Console ports # @@ -2375,6 +2351,15 @@ class ConsolePortCreateForm(BootstrapMixin, forms.Form): ) +class ConsolePortBulkCreateForm( + form_from_model(ConsolePort, ['type', 'description', 'tags']), + DeviceBulkAddComponentForm +): + tags = TagField( + required=False + ) + + class ConsolePortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=ConsolePort.objects.all(), @@ -2462,6 +2447,15 @@ class ConsoleServerPortCreateForm(BootstrapMixin, forms.Form): ) +class ConsoleServerPortBulkCreateForm( + form_from_model(ConsoleServerPort, ['type', 'description', 'tags']), + DeviceBulkAddComponentForm +): + tags = TagField( + required=False + ) + + class ConsoleServerPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=ConsoleServerPort.objects.all(), @@ -2573,6 +2567,15 @@ class PowerPortCreateForm(BootstrapMixin, forms.Form): ) +class PowerPortBulkCreateForm( + form_from_model(PowerPort, ['type', 'maximum_draw', 'allocated_draw', 'description', 'tags']), + DeviceBulkAddComponentForm +): + tags = TagField( + required=False + ) + + class PowerPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=PowerPort.objects.all(), @@ -2700,6 +2703,15 @@ class PowerOutletCreateForm(BootstrapMixin, forms.Form): self.fields['power_port'].queryset = PowerPort.objects.filter(device=device) +class PowerOutletBulkCreateForm( + form_from_model(PowerOutlet, ['type', 'feed_leg', 'description', 'tags']), + DeviceBulkAddComponentForm +): + tags = TagField( + required=False + ) + + class PowerOutletCSVForm(forms.ModelForm): device = FlexibleModelChoiceField( queryset=Device.objects.all(), @@ -2985,71 +2997,13 @@ class InterfaceCreateForm(BootstrapMixin, InterfaceCommonForm, forms.Form): self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', device.site.pk) -class InterfaceCSVForm(forms.ModelForm): - device = FlexibleModelChoiceField( - queryset=Device.objects.all(), - required=False, - to_field_name='name', - help_text='Name or ID of device', - error_messages={ - 'invalid_choice': 'Device not found.', - } +class InterfaceBulkCreateForm( + form_from_model(Interface, ['type', 'enabled', 'mtu', 'mgmt_only', 'description', 'tags']), + DeviceBulkAddComponentForm +): + tags = TagField( + required=False ) - virtual_machine = FlexibleModelChoiceField( - queryset=VirtualMachine.objects.all(), - required=False, - to_field_name='name', - help_text='Name or ID of virtual machine', - error_messages={ - 'invalid_choice': 'Virtual machine not found.', - } - ) - lag = FlexibleModelChoiceField( - queryset=Interface.objects.all(), - required=False, - to_field_name='name', - help_text='Name or ID of LAG interface', - error_messages={ - 'invalid_choice': 'LAG interface not found.', - } - ) - type = CSVChoiceField( - choices=InterfaceTypeChoices, - ) - mode = CSVChoiceField( - choices=InterfaceModeChoices, - required=False, - ) - - class Meta: - model = Interface - fields = Interface.csv_headers - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Limit LAG choices to interfaces belonging to this device (or VC master) - if self.is_bound and 'device' in self.data: - try: - device = self.fields['device'].to_python(self.data['device']) - except forms.ValidationError: - device = None - else: - device = self.instance.device - - if device: - self.fields['lag'].queryset = Interface.objects.filter( - device__in=[device, device.get_vc_master()], type=InterfaceTypeChoices.TYPE_LAG - ) - else: - self.fields['lag'].queryset = Interface.objects.none() - - def clean_enabled(self): - # Make sure enabled is True when it's not included in the uploaded data - if 'enabled' not in self.data: - return True - else: - return self.cleaned_data['enabled'] class InterfaceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): @@ -3175,6 +3129,73 @@ class InterfaceBulkDisconnectForm(ConfirmationForm): ) +class InterfaceCSVForm(forms.ModelForm): + device = FlexibleModelChoiceField( + queryset=Device.objects.all(), + required=False, + to_field_name='name', + help_text='Name or ID of device', + error_messages={ + 'invalid_choice': 'Device not found.', + } + ) + virtual_machine = FlexibleModelChoiceField( + queryset=VirtualMachine.objects.all(), + required=False, + to_field_name='name', + help_text='Name or ID of virtual machine', + error_messages={ + 'invalid_choice': 'Virtual machine not found.', + } + ) + lag = FlexibleModelChoiceField( + queryset=Interface.objects.all(), + required=False, + to_field_name='name', + help_text='Name or ID of LAG interface', + error_messages={ + 'invalid_choice': 'LAG interface not found.', + } + ) + type = CSVChoiceField( + choices=InterfaceTypeChoices, + ) + mode = CSVChoiceField( + choices=InterfaceModeChoices, + required=False, + ) + + class Meta: + model = Interface + fields = Interface.csv_headers + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Limit LAG choices to interfaces belonging to this device (or VC master) + if self.is_bound and 'device' in self.data: + try: + device = self.fields['device'].to_python(self.data['device']) + except forms.ValidationError: + device = None + else: + device = self.instance.device + + if device: + self.fields['lag'].queryset = Interface.objects.filter( + device__in=[device, device.get_vc_master()], type=InterfaceTypeChoices.TYPE_LAG + ) + else: + self.fields['lag'].queryset = Interface.objects.none() + + def clean_enabled(self): + # Make sure enabled is True when it's not included in the uploaded data + if 'enabled' not in self.data: + return True + else: + return self.cleaned_data['enabled'] + + # # Front pass-through ports # @@ -3331,6 +3352,15 @@ class FrontPortCSVForm(forms.ModelForm): self.fields['rear_port'].queryset = RearPort.objects.none() +# class FrontPortBulkCreateForm( +# form_from_model(FrontPort, ['type', 'description', 'tags']), +# DeviceBulkAddComponentForm +# ): +# tags = TagField( +# required=False +# ) + + class FrontPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=FrontPort.objects.all(), @@ -3436,6 +3466,15 @@ class RearPortCSVForm(forms.ModelForm): fields = RearPort.csv_headers +# class RearPortBulkCreateForm( +# form_from_model(RearPort, ['type', 'positions', 'description', 'tags']), +# DeviceBulkAddComponentForm +# ): +# tags = TagField( +# required=False +# ) + + class RearPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=RearPort.objects.all(), @@ -4011,6 +4050,15 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form): ).exclude(pk=device_bay.device.pk) +class DeviceBayBulkCreateForm( + form_from_model(DeviceBay, ['description', 'tags']), + DeviceBulkAddComponentForm +): + tags = TagField( + required=False + ) + + class DeviceBayBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=DeviceBay.objects.all(), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index c10a821dc..cc8f285c8 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1930,7 +1930,7 @@ class DeviceBulkAddConsolePortView(PermissionRequiredMixin, BulkComponentCreateV permission_required = 'dcim.add_consoleport' parent_model = Device parent_field = 'device' - form = forms.DeviceBulkAddComponentForm + form = forms.ConsolePortBulkCreateForm model = ConsolePort model_form = forms.ConsolePortForm filterset = filters.DeviceFilterSet @@ -1942,7 +1942,7 @@ class DeviceBulkAddConsoleServerPortView(PermissionRequiredMixin, BulkComponentC permission_required = 'dcim.add_consoleserverport' parent_model = Device parent_field = 'device' - form = forms.DeviceBulkAddComponentForm + form = forms.ConsoleServerPortBulkCreateForm model = ConsoleServerPort model_form = forms.ConsoleServerPortForm filterset = filters.DeviceFilterSet @@ -1954,7 +1954,7 @@ class DeviceBulkAddPowerPortView(PermissionRequiredMixin, BulkComponentCreateVie permission_required = 'dcim.add_powerport' parent_model = Device parent_field = 'device' - form = forms.DeviceBulkAddComponentForm + form = forms.PowerPortBulkCreateForm model = PowerPort model_form = forms.PowerPortForm filterset = filters.DeviceFilterSet @@ -1966,7 +1966,7 @@ class DeviceBulkAddPowerOutletView(PermissionRequiredMixin, BulkComponentCreateV permission_required = 'dcim.add_poweroutlet' parent_model = Device parent_field = 'device' - form = forms.DeviceBulkAddComponentForm + form = forms.PowerOutletBulkCreateForm model = PowerOutlet model_form = forms.PowerOutletForm filterset = filters.DeviceFilterSet @@ -1978,7 +1978,7 @@ class DeviceBulkAddInterfaceView(PermissionRequiredMixin, BulkComponentCreateVie permission_required = 'dcim.add_interface' parent_model = Device parent_field = 'device' - form = forms.DeviceBulkAddInterfaceForm + form = forms.InterfaceBulkCreateForm model = Interface model_form = forms.InterfaceForm filterset = filters.DeviceFilterSet @@ -1990,7 +1990,7 @@ class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateVie permission_required = 'dcim.add_devicebay' parent_model = Device parent_field = 'device' - form = forms.DeviceBulkAddComponentForm + form = forms.DeviceBayBulkCreateForm model = DeviceBay model_form = forms.DeviceBayForm filterset = filters.DeviceFilterSet diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index fd528f827..d787b2d67 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -10,6 +10,7 @@ from django.conf import settings from django.contrib.postgres.forms.jsonb import JSONField as _JSONField, InvalidJSONInput from django.db.models import Count from django.forms import BoundField +from django.forms.models import fields_for_model from django.urls import reverse from .choices import unpack_grouped_choices @@ -123,6 +124,15 @@ def add_blank_choice(choices): return ((None, '---------'),) + tuple(choices) +def form_from_model(model, fields): + """ + Return a Form class with the specified fields from a model. + """ + form_fields = fields_for_model(model, fields=fields) + + return type('FormFromModel', (forms.Form,), form_fields) + + # # Widgets #