from django import forms from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext_lazy as _ from timezone_field import TimeZoneFormField 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 netbox.forms import NetBoxModelForm from tenancy.forms import TenancyForm from utilities.forms import BootstrapMixin, add_blank_choice from utilities.forms.fields import ( CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SlugField, ) from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK from virtualization.models import Cluster from wireless.models import WirelessLAN, WirelessLANGroup from .common import InterfaceCommonForm, ModuleCommonForm __all__ = ( 'CableForm', 'ConsolePortForm', 'ConsolePortTemplateForm', 'ConsoleServerPortForm', 'ConsoleServerPortTemplateForm', 'DeviceBayForm', 'DeviceBayTemplateForm', 'DeviceForm', 'DeviceRoleForm', 'DeviceTypeForm', 'DeviceVCMembershipForm', 'FrontPortForm', 'FrontPortTemplateForm', 'InterfaceForm', 'InterfaceTemplateForm', 'InventoryItemForm', 'InventoryItemRoleForm', 'InventoryItemTemplateForm', 'LocationForm', 'ManufacturerForm', 'ModuleForm', 'ModuleBayForm', 'ModuleBayTemplateForm', 'ModuleTypeForm', 'PlatformForm', 'PopulateDeviceBayForm', 'PowerFeedForm', 'PowerOutletForm', 'PowerOutletTemplateForm', 'PowerPanelForm', 'PowerPortForm', 'PowerPortTemplateForm', 'RackForm', 'RackReservationForm', 'RackRoleForm', 'RearPortForm', 'RearPortTemplateForm', 'RegionForm', 'SiteForm', 'SiteGroupForm', 'VCMemberSelectForm', 'VirtualChassisForm', 'VirtualDeviceContextForm' ) class RegionForm(NetBoxModelForm): parent = DynamicModelChoiceField( label=_('Parent'), queryset=Region.objects.all(), required=False ) slug = SlugField() fieldsets = ( (_('Region'), ( 'parent', 'name', 'slug', 'description', 'tags', )), ) class Meta: model = Region fields = ( 'parent', 'name', 'slug', 'description', 'tags', ) class SiteGroupForm(NetBoxModelForm): parent = DynamicModelChoiceField( label=_('Parent'), queryset=SiteGroup.objects.all(), required=False ) slug = SlugField() fieldsets = ( (_('Site Group'), ( 'parent', 'name', 'slug', 'description', 'tags', )), ) class Meta: model = SiteGroup fields = ( 'parent', 'name', 'slug', 'description', 'tags', ) class SiteForm(TenancyForm, NetBoxModelForm): region = DynamicModelChoiceField( label=_('Region'), queryset=Region.objects.all(), required=False ) group = DynamicModelChoiceField( label=_('Group'), queryset=SiteGroup.objects.all(), required=False ) asns = DynamicModelMultipleChoiceField( queryset=ASN.objects.all(), label=_('ASNs'), required=False ) slug = SlugField() time_zone = TimeZoneFormField( label=_('Time zone'), choices=add_blank_choice(TimeZoneFormField().choices), required=False ) comments = CommentField() fieldsets = ( (_('Site'), ( 'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags', )), (_('Tenancy'), ('tenant_group', 'tenant')), (_('Contact Info'), ('physical_address', 'shipping_address', 'latitude', 'longitude')), ) class Meta: model = Site fields = ( 'name', 'slug', 'status', 'region', 'group', 'tenant_group', 'tenant', 'facility', 'asns', 'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags', ) widgets = { 'physical_address': forms.Textarea( attrs={ 'rows': 3, } ), 'shipping_address': forms.Textarea( attrs={ 'rows': 3, } ), } class LocationForm(TenancyForm, NetBoxModelForm): site = DynamicModelChoiceField( label=_('Site'), queryset=Site.objects.all(), selector=True ) parent = DynamicModelChoiceField( label=_('Parent'), queryset=Location.objects.all(), required=False, query_params={ 'site_id': '$site' } ) slug = SlugField() fieldsets = ( (_('Location'), ('site', 'parent', 'name', 'slug', 'status', 'description', 'tags')), (_('Tenancy'), ('tenant_group', 'tenant')), ) class Meta: model = Location fields = ( 'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant', 'tags', ) class RackRoleForm(NetBoxModelForm): slug = SlugField() fieldsets = ( (_('Rack Role'), ( 'name', 'slug', 'color', 'description', 'tags', )), ) class Meta: model = RackRole fields = [ 'name', 'slug', 'color', 'description', 'tags', ] class RackForm(TenancyForm, NetBoxModelForm): site = DynamicModelChoiceField( label=_('Site'), queryset=Site.objects.all(), selector=True ) location = DynamicModelChoiceField( label=_('Location'), queryset=Location.objects.all(), required=False, query_params={ 'site_id': '$site' } ) role = DynamicModelChoiceField( label=_('Role'), queryset=RackRole.objects.all(), required=False ) comments = CommentField() class Meta: model = Rack fields = [ 'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags', ] class RackReservationForm(TenancyForm, NetBoxModelForm): rack = DynamicModelChoiceField( label=_('Rack'), queryset=Rack.objects.all(), selector=True ) units = NumericArrayField( label=_('Units'), base_field=forms.IntegerField(), help_text=_("Comma-separated list of numeric unit IDs. A range may be specified using a hyphen.") ) user = forms.ModelChoiceField( label=_('User'), queryset=get_user_model().objects.order_by( 'username' ) ) comments = CommentField() fieldsets = ( (_('Reservation'), ('rack', 'units', 'user', 'description', 'tags')), (_('Tenancy'), ('tenant_group', 'tenant')), ) class Meta: model = RackReservation fields = [ 'rack', 'units', 'user', 'tenant_group', 'tenant', 'description', 'comments', 'tags', ] class ManufacturerForm(NetBoxModelForm): slug = SlugField() fieldsets = ( (_('Manufacturer'), ( 'name', 'slug', 'description', 'tags', )), ) class Meta: model = Manufacturer fields = [ 'name', 'slug', 'description', 'tags', ] class DeviceTypeForm(NetBoxModelForm): manufacturer = DynamicModelChoiceField( label=_('Manufacturer'), queryset=Manufacturer.objects.all() ) default_platform = DynamicModelChoiceField( label=_('Default platform'), queryset=Platform.objects.all(), required=False ) slug = SlugField( label=_('Slug'), slug_source='model' ) comments = CommentField() fieldsets = ( (_('Device Type'), ('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags')), (_('Chassis'), ( 'u_height', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow', 'weight', 'weight_unit', )), (_('Images'), ('front_image', 'rear_image')), ) class Meta: model = DeviceType fields = [ 'manufacturer', 'model', 'slug', 'default_platform', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'description', 'comments', 'tags', ] widgets = { 'front_image': ClearableFileInput(attrs={ 'accept': DEVICETYPE_IMAGE_FORMATS }), 'rear_image': ClearableFileInput(attrs={ 'accept': DEVICETYPE_IMAGE_FORMATS }), } class ModuleTypeForm(NetBoxModelForm): manufacturer = DynamicModelChoiceField( label=_('Manufacturer'), queryset=Manufacturer.objects.all() ) comments = CommentField() fieldsets = ( (_('Module Type'), ('manufacturer', 'model', 'part_number', 'description', 'tags')), (_('Weight'), ('weight', 'weight_unit')) ) class Meta: model = ModuleType fields = [ 'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'description', 'comments', 'tags', ] class DeviceRoleForm(NetBoxModelForm): config_template = DynamicModelChoiceField( label=_('Config template'), queryset=ConfigTemplate.objects.all(), required=False ) slug = SlugField() fieldsets = ( (_('Device Role'), ( 'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags', )), ) class Meta: model = DeviceRole fields = [ 'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags', ] class PlatformForm(NetBoxModelForm): manufacturer = DynamicModelChoiceField( label=_('Manufacturer'), queryset=Manufacturer.objects.all(), required=False ) config_template = DynamicModelChoiceField( label=_('Config template'), queryset=ConfigTemplate.objects.all(), required=False ) slug = SlugField( label=_('Slug'), max_length=64 ) fieldsets = ( (_('Platform'), ('name', 'slug', 'manufacturer', 'config_template', 'description', 'tags')), ) class Meta: model = Platform fields = [ 'name', 'slug', 'manufacturer', 'config_template', 'description', 'tags', ] class DeviceForm(TenancyForm, NetBoxModelForm): site = DynamicModelChoiceField( label=_('Site'), queryset=Site.objects.all(), selector=True ) location = DynamicModelChoiceField( label=_('Location'), queryset=Location.objects.all(), required=False, query_params={ 'site_id': '$site' }, initial_params={ 'racks': '$rack' } ) rack = DynamicModelChoiceField( label=_('Rack'), queryset=Rack.objects.all(), required=False, query_params={ 'site_id': '$site', 'location_id': '$location', } ) position = forms.DecimalField( label=_('Position'), required=False, help_text=_("The lowest-numbered unit occupied by the device"), widget=APISelect( api_url='/api/dcim/racks/{{rack}}/elevation/', attrs={ 'disabled-indicator': 'device', 'data-dynamic-params': '[{"fieldName":"face","queryParam":"face"}]' } ) ) device_type = DynamicModelChoiceField( label=_('Device type'), queryset=DeviceType.objects.all(), selector=True ) role = DynamicModelChoiceField( label=_('Device role'), queryset=DeviceRole.objects.all() ) platform = DynamicModelChoiceField( label=_('Platform'), queryset=Platform.objects.all(), required=False ) cluster = DynamicModelChoiceField( label=_('Cluster'), queryset=Cluster.objects.all(), required=False, selector=True ) comments = CommentField() local_context_data = JSONField( required=False, label='' ) virtual_chassis = DynamicModelChoiceField( label=_('Virtual chassis'), queryset=VirtualChassis.objects.all(), required=False, selector=True ) vc_position = forms.IntegerField( required=False, label=_('Position'), help_text=_("The position in the virtual chassis this device is identified by") ) vc_priority = forms.IntegerField( required=False, label=_('Priority'), help_text=_("The priority of the device in the virtual chassis") ) config_template = DynamicModelChoiceField( label=_('Config template'), queryset=ConfigTemplate.objects.all(), required=False ) class Meta: model = Device fields = [ 'name', 'role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face', 'latitude', 'longitude', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template', 'comments', 'tags', 'local_context_data', ] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.instance.pk: # Compile list of choices for primary IPv4 and IPv6 addresses oob_ip_choices = [(None, '---------')] for family in [4, 6]: ip_choices = [(None, '---------')] # Gather PKs of all interfaces belonging to this Device or a peer VirtualChassis member interface_ids = self.instance.vc_interfaces(if_master=False).values_list('pk', flat=True) # Collect interface IPs interface_ips = IPAddress.objects.filter( address__family=family, assigned_object_type=ContentType.objects.get_for_model(Interface), assigned_object_id__in=interface_ids ).prefetch_related('assigned_object') if interface_ips: ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips] ip_choices.append(('Interface IPs', ip_list)) oob_ip_choices.extend(ip_list) # Collect NAT IPs nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter( address__family=family, nat_inside__assigned_object_type=ContentType.objects.get_for_model(Interface), nat_inside__assigned_object_id__in=interface_ids ).prefetch_related('assigned_object') if nat_ips: ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips] ip_choices.append(('NAT IPs', ip_list)) self.fields['primary_ip{}'.format(family)].choices = ip_choices self.fields['oob_ip'].choices = oob_ip_choices # If editing an existing device, exclude it from the list of occupied rack units. This ensures that a device # can be flipped from one face to another. self.fields['position'].widget.add_query_param('exclude', self.instance.pk) # Disable rack assignment if this is a child device installed in a parent device if self.instance.device_type.is_child_device and hasattr(self.instance, 'parent_bay'): self.fields['site'].disabled = True self.fields['rack'].disabled = True self.initial['site'] = self.instance.parent_bay.device.site_id self.initial['rack'] = self.instance.parent_bay.device.rack_id else: # An object that doesn't exist yet can't have any IPs assigned to it self.fields['primary_ip4'].choices = [] self.fields['primary_ip4'].widget.attrs['readonly'] = True self.fields['primary_ip6'].choices = [] self.fields['primary_ip6'].widget.attrs['readonly'] = True self.fields['oob_ip'].choices = [] self.fields['oob_ip'].widget.attrs['readonly'] = True # Rack position position = self.data.get('position') or self.initial.get('position') if position: self.fields['position'].widget.choices = [(position, f'U{position}')] class ModuleForm(ModuleCommonForm, NetBoxModelForm): device = DynamicModelChoiceField( label=_('Device'), queryset=Device.objects.all(), initial_params={ 'modulebays': '$module_bay' } ) module_bay = DynamicModelChoiceField( label=_('Module bay'), queryset=ModuleBay.objects.all(), query_params={ 'device_id': '$device' } ) module_type = DynamicModelChoiceField( label=_('Module type'), queryset=ModuleType.objects.all(), selector=True ) comments = CommentField() replicate_components = forms.BooleanField( label=_('Replicate components'), required=False, initial=True, help_text=_("Automatically populate components associated with this module type") ) adopt_components = forms.BooleanField( label=_('Adopt components'), required=False, initial=False, help_text=_("Adopt already existing components") ) fieldsets = ( (_('Module'), ('device', 'module_bay', 'module_type', 'status', 'description', 'tags')), (_('Hardware'), ( 'serial', 'asset_tag', 'replicate_components', 'adopt_components', )), ) class Meta: model = Module fields = [ 'device', 'module_bay', 'module_type', 'status', 'serial', 'asset_tag', 'tags', 'replicate_components', 'adopt_components', 'description', 'comments', ] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.instance.pk: self.fields['device'].disabled = True self.fields['replicate_components'].initial = False self.fields['replicate_components'].disabled = True self.fields['adopt_components'].initial = False self.fields['adopt_components'].disabled = True class CableForm(TenancyForm, NetBoxModelForm): comments = CommentField() class Meta: model = Cable fields = [ 'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags', ] error_messages = { 'length': { 'max_value': _('Maximum length is 32767 (any unit)') } } class PowerPanelForm(NetBoxModelForm): site = DynamicModelChoiceField( label=_('Site'), queryset=Site.objects.all(), selector=True ) location = DynamicModelChoiceField( label=_('Location'), queryset=Location.objects.all(), required=False, query_params={ 'site_id': '$site' } ) comments = CommentField() fieldsets = ( ('Power Panel', ('site', 'location', 'name', 'description', 'tags')), ) class Meta: model = PowerPanel fields = [ 'site', 'location', 'name', 'description', 'comments', 'tags', ] class PowerFeedForm(TenancyForm, NetBoxModelForm): power_panel = DynamicModelChoiceField( label=_('Power panel'), queryset=PowerPanel.objects.all(), selector=True ) rack = DynamicModelChoiceField( label=_('Rack'), queryset=Rack.objects.all(), required=False, selector=True ) comments = CommentField() fieldsets = ( (_('Power Feed'), ('power_panel', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags')), (_('Characteristics'), ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')), (_('Tenancy'), ('tenant_group', 'tenant')), ) class Meta: model = PowerFeed fields = [ 'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', 'tenant_group', 'tenant', 'description', 'comments', 'tags' ] # # Virtual chassis # class VirtualChassisForm(NetBoxModelForm): master = forms.ModelChoiceField( label=_('Master'), queryset=Device.objects.all(), required=False, ) comments = CommentField() class Meta: model = VirtualChassis fields = [ 'name', 'domain', 'master', 'description', 'comments', 'tags', ] widgets = { 'master': SelectWithPK(), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['master'].queryset = Device.objects.filter(virtual_chassis=self.instance) class DeviceVCMembershipForm(forms.ModelForm): class Meta: model = Device fields = [ 'vc_position', 'vc_priority', ] labels = { 'vc_position': 'Position', 'vc_priority': 'Priority', } def __init__(self, validate_vc_position=False, *args, **kwargs): super().__init__(*args, **kwargs) # Require VC position (only required when the Device is a VirtualChassis member) self.fields['vc_position'].required = True # Add bootstrap classes to form elements. self.fields['vc_position'].widget.attrs = {'class': 'form-control'} self.fields['vc_priority'].widget.attrs = {'class': 'form-control'} # Validation of vc_position is optional. This is only required when adding a new member to an existing # VirtualChassis. Otherwise, vc_position validation is handled by BaseVCMemberFormSet. self.validate_vc_position = validate_vc_position def clean_vc_position(self): vc_position = self.cleaned_data['vc_position'] if self.validate_vc_position: conflicting_members = Device.objects.filter( virtual_chassis=self.instance.virtual_chassis, vc_position=vc_position ) if conflicting_members.exists(): raise forms.ValidationError( 'A virtual chassis member already exists in position {}.'.format(vc_position) ) return vc_position class VCMemberSelectForm(BootstrapMixin, forms.Form): device = DynamicModelChoiceField( label=_('Device'), queryset=Device.objects.all(), query_params={ 'virtual_chassis_id': 'null', }, selector=True ) def clean_device(self): device = self.cleaned_data['device'] if device.virtual_chassis is not None: raise forms.ValidationError( f"Device {device} is already assigned to a virtual chassis." ) return device # # Device component templates # class ComponentTemplateForm(BootstrapMixin, forms.ModelForm): device_type = DynamicModelChoiceField( label=_('Device type'), queryset=DeviceType.objects.all() ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Disable reassignment of DeviceType when editing an existing instance if self.instance.pk: self.fields['device_type'].disabled = True class ModularComponentTemplateForm(ComponentTemplateForm): device_type = DynamicModelChoiceField( label=_('Device type'), queryset=DeviceType.objects.all().all(), required=False ) module_type = DynamicModelChoiceField( label=_('Module type'), queryset=ModuleType.objects.all(), required=False ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Disable reassignment of ModuleType when editing an existing instance if self.instance.pk: self.fields['module_type'].disabled = True class ConsolePortTemplateForm(ModularComponentTemplateForm): fieldsets = ( (None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')), ) class Meta: model = ConsolePortTemplate fields = [ 'device_type', 'module_type', 'name', 'label', 'type', 'description', ] class ConsoleServerPortTemplateForm(ModularComponentTemplateForm): fieldsets = ( (None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')), ) class Meta: model = ConsoleServerPortTemplate fields = [ 'device_type', 'module_type', 'name', 'label', 'type', 'description', ] class PowerPortTemplateForm(ModularComponentTemplateForm): fieldsets = ( (None, ( 'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', )), ) class Meta: model = PowerPortTemplate fields = [ 'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', ] class PowerOutletTemplateForm(ModularComponentTemplateForm): power_port = DynamicModelChoiceField( label=_('Power port'), queryset=PowerPortTemplate.objects.all(), required=False, query_params={ 'devicetype_id': '$device_type', } ) fieldsets = ( (None, ('device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description')), ) class Meta: model = PowerOutletTemplate fields = [ 'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', ] class InterfaceTemplateForm(ModularComponentTemplateForm): bridge = DynamicModelChoiceField( label=_('Bridge'), queryset=InterfaceTemplate.objects.all(), required=False, query_params={ 'devicetype_id': '$device_type', 'moduletype_id': '$module_type', } ) fieldsets = ( (None, ('device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge')), (_('PoE'), ('poe_mode', 'poe_type')), (_('Wireless'), ('rf_role',)), ) class Meta: model = InterfaceTemplate fields = [ 'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'enabled', 'description', 'poe_mode', 'poe_type', 'bridge', 'rf_role', ] class FrontPortTemplateForm(ModularComponentTemplateForm): rear_port = DynamicModelChoiceField( label=_('Rear port'), queryset=RearPortTemplate.objects.all(), required=False, query_params={ 'devicetype_id': '$device_type', 'moduletype_id': '$module_type', } ) fieldsets = ( (None, ( 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', )), ) class Meta: model = FrontPortTemplate fields = [ 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', ] class RearPortTemplateForm(ModularComponentTemplateForm): fieldsets = ( (None, ('device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description')), ) class Meta: model = RearPortTemplate fields = [ 'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description', ] class ModuleBayTemplateForm(ComponentTemplateForm): fieldsets = ( (None, ('device_type', 'name', 'label', 'position', 'description')), ) class Meta: model = ModuleBayTemplate fields = [ 'device_type', 'name', 'label', 'position', 'description', ] class DeviceBayTemplateForm(ComponentTemplateForm): fieldsets = ( (None, ('device_type', 'name', 'label', 'description')), ) class Meta: model = DeviceBayTemplate fields = [ 'device_type', 'name', 'label', 'description', ] class InventoryItemTemplateForm(ComponentTemplateForm): parent = DynamicModelChoiceField( label=_('Parent'), queryset=InventoryItemTemplate.objects.all(), required=False, query_params={ 'devicetype_id': '$device_type' } ) role = DynamicModelChoiceField( label=_('Role'), queryset=InventoryItemRole.objects.all(), required=False ) manufacturer = DynamicModelChoiceField( label=_('Manufacturer'), queryset=Manufacturer.objects.all(), required=False ) component_type = ContentTypeChoiceField( queryset=ContentType.objects.all(), limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS, required=False, widget=forms.HiddenInput ) component_id = forms.IntegerField( required=False, widget=forms.HiddenInput ) fieldsets = ( (None, ( 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description', 'component_type', 'component_id', )), ) class Meta: model = InventoryItemTemplate fields = [ 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description', 'component_type', 'component_id', ] # # Device components # class DeviceComponentForm(NetBoxModelForm): device = DynamicModelChoiceField( label=_('Device'), queryset=Device.objects.all(), selector=True ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Disable reassignment of Device when editing an existing instance if self.instance.pk: self.fields['device'].disabled = True class ModularDeviceComponentForm(DeviceComponentForm): module = DynamicModelChoiceField( label=_('Module'), queryset=Module.objects.all(), required=False, query_params={ 'device_id': '$device', } ) class ConsolePortForm(ModularDeviceComponentForm): fieldsets = ( (None, ( 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags', )), ) class Meta: model = ConsolePort fields = [ 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags', ] class ConsoleServerPortForm(ModularDeviceComponentForm): fieldsets = ( (None, ( 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags', )), ) class Meta: model = ConsoleServerPort fields = [ 'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags', ] class PowerPortForm(ModularDeviceComponentForm): fieldsets = ( (None, ( 'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description', 'tags', )), ) class Meta: model = PowerPort fields = [ 'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description', 'tags', ] class PowerOutletForm(ModularDeviceComponentForm): power_port = DynamicModelChoiceField( label=_('Power port'), queryset=PowerPort.objects.all(), required=False, query_params={ 'device_id': '$device', } ) fieldsets = ( (None, ( 'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description', 'tags', )), ) class Meta: model = PowerOutlet fields = [ 'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description', 'tags', ] class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm): vdcs = DynamicModelMultipleChoiceField( queryset=VirtualDeviceContext.objects.all(), required=False, label=_('Virtual device contexts'), initial_params={ 'interfaces': '$parent', }, query_params={ 'device_id': '$device', } ) parent = DynamicModelChoiceField( queryset=Interface.objects.all(), required=False, label=_('Parent interface'), query_params={ 'device_id': '$device', } ) bridge = DynamicModelChoiceField( queryset=Interface.objects.all(), required=False, label=_('Bridged interface'), query_params={ 'device_id': '$device', } ) lag = DynamicModelChoiceField( queryset=Interface.objects.all(), required=False, label=_('LAG interface'), query_params={ 'device_id': '$device', 'type': 'lag', } ) wireless_lan_group = DynamicModelChoiceField( queryset=WirelessLANGroup.objects.all(), required=False, label=_('Wireless LAN group') ) wireless_lans = DynamicModelMultipleChoiceField( queryset=WirelessLAN.objects.all(), required=False, label=_('Wireless LANs'), query_params={ 'group_id': '$wireless_lan_group', } ) vlan_group = DynamicModelChoiceField( queryset=VLANGroup.objects.all(), required=False, label=_('VLAN group') ) untagged_vlan = DynamicModelChoiceField( queryset=VLAN.objects.all(), required=False, label=_('Untagged VLAN'), query_params={ 'group_id': '$vlan_group', 'available_on_device': '$device', } ) tagged_vlans = DynamicModelMultipleChoiceField( queryset=VLAN.objects.all(), required=False, label=_('Tagged VLANs'), query_params={ 'group_id': '$vlan_group', 'available_on_device': '$device', } ) vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, label=_('VRF') ) wwn = forms.CharField( empty_value=None, required=False, label=_('WWN') ) fieldsets = ( (_('Interface'), ('device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')), (_('Addressing'), ('vrf', 'mac_address', 'wwn')), (_('Operation'), ('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')), (_('Related Interfaces'), ('parent', 'bridge', 'lag')), (_('PoE'), ('poe_mode', 'poe_type')), (_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')), (_('Wireless'), ( 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans', )), ) class Meta: model = Interface fields = [ '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', ] widgets = { 'speed': NumberWithOptions( options=InterfaceSpeedChoices ), 'mode': HTMXSelect(), } labels = { 'mode': '802.1Q Mode', } class FrontPortForm(ModularDeviceComponentForm): rear_port = DynamicModelChoiceField( queryset=RearPort.objects.all(), query_params={ 'device_id': '$device', } ) fieldsets = ( (None, ( 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected', 'description', 'tags', )), ) class Meta: model = FrontPort fields = [ 'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected', 'description', 'tags', ] class RearPortForm(ModularDeviceComponentForm): fieldsets = ( (None, ( 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags', )), ) class Meta: model = RearPort fields = [ 'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags', ] class ModuleBayForm(DeviceComponentForm): fieldsets = ( (None, ('device', 'name', 'label', 'position', 'description', 'tags',)), ) class Meta: model = ModuleBay fields = [ 'device', 'name', 'label', 'position', 'description', 'tags', ] class DeviceBayForm(DeviceComponentForm): fieldsets = ( (None, ('device', 'name', 'label', 'description', 'tags',)), ) class Meta: model = DeviceBay fields = [ 'device', 'name', 'label', 'description', 'tags', ] class PopulateDeviceBayForm(BootstrapMixin, forms.Form): installed_device = forms.ModelChoiceField( queryset=Device.objects.all(), label=_('Child Device'), help_text=_("Child devices must first be created and assigned to the site and rack of the parent device.") ) def __init__(self, device_bay, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['installed_device'].queryset = Device.objects.filter( site=device_bay.device.site, rack=device_bay.device.rack, parent_bay__isnull=True, device_type__u_height=0, device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD ).exclude(pk=device_bay.device.pk) class InventoryItemForm(DeviceComponentForm): parent = DynamicModelChoiceField( label=_('Parent'), queryset=InventoryItem.objects.all(), required=False, query_params={ 'device_id': '$device' } ) role = DynamicModelChoiceField( label=_('Role'), queryset=InventoryItemRole.objects.all(), required=False ) manufacturer = DynamicModelChoiceField( label=_('Manufacturer'), queryset=Manufacturer.objects.all(), required=False ) # Assigned component selectors consoleport = DynamicModelChoiceField( queryset=ConsolePort.objects.all(), required=False, query_params={ 'device_id': '$device' }, label=_('Console port') ) consoleserverport = DynamicModelChoiceField( queryset=ConsoleServerPort.objects.all(), required=False, query_params={ 'device_id': '$device' }, label=_('Console server port') ) frontport = DynamicModelChoiceField( queryset=FrontPort.objects.all(), required=False, query_params={ 'device_id': '$device' }, label=_('Front port') ) interface = DynamicModelChoiceField( queryset=Interface.objects.all(), required=False, query_params={ 'device_id': '$device' }, label=_('Interface') ) poweroutlet = DynamicModelChoiceField( queryset=PowerOutlet.objects.all(), required=False, query_params={ 'device_id': '$device' }, label=_('Power outlet') ) powerport = DynamicModelChoiceField( queryset=PowerPort.objects.all(), required=False, query_params={ 'device_id': '$device' }, label=_('Power port') ) rearport = DynamicModelChoiceField( queryset=RearPort.objects.all(), required=False, query_params={ 'device_id': '$device' }, label=_('Rear port') ) fieldsets = ( (_('Inventory Item'), ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')), (_('Hardware'), ('manufacturer', 'part_id', 'serial', 'asset_tag')), ) class Meta: model = InventoryItem fields = [ 'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'tags', ] def __init__(self, *args, **kwargs): instance = kwargs.get('instance') initial = kwargs.get('initial', {}).copy() component_type = initial.get('component_type') component_id = initial.get('component_id') # Used for picking the default active tab for component selection self.no_component = True if instance: # When editing set the initial value for component selectin for component_model in ContentType.objects.filter(MODULAR_COMPONENT_MODELS): if type(instance.component) is component_model.model_class(): initial[component_model.model] = instance.component self.no_component = False break elif component_type and component_id: # When adding the InventoryItem from a component page if content_type := ContentType.objects.filter(MODULAR_COMPONENT_MODELS).filter(pk=component_type).first(): if component := content_type.model_class().objects.filter(pk=component_id).first(): initial[content_type.model] = component self.no_component = False kwargs['initial'] = initial super().__init__(*args, **kwargs) # Specifically allow editing the device of IntentoryItems if self.instance.pk: self.fields['device'].disabled = False def clean(self): super().clean() # Handle object assignment selected_objects = [ field for field in ( 'consoleport', 'consoleserverport', 'frontport', 'interface', 'poweroutlet', 'powerport', 'rearport' ) if self.cleaned_data[field] ] if len(selected_objects) > 1: raise forms.ValidationError(_("An InventoryItem can only be assigned to a single component.")) elif selected_objects: self.instance.component = self.cleaned_data[selected_objects[0]] else: self.instance.component = None # Device component roles # class InventoryItemRoleForm(NetBoxModelForm): slug = SlugField() fieldsets = ( (_('Inventory Item Role'), ( 'name', 'slug', 'color', 'description', 'tags', )), ) class Meta: model = InventoryItemRole fields = [ 'name', 'slug', 'color', 'description', 'tags', ] class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm): device = DynamicModelChoiceField( label=_('Device'), queryset=Device.objects.all(), selector=True ) primary_ip4 = DynamicModelChoiceField( queryset=IPAddress.objects.all(), label=_('Primary IPv4'), required=False, query_params={ 'device_id': '$device', 'family': '4', } ) primary_ip6 = DynamicModelChoiceField( queryset=IPAddress.objects.all(), label=_('Primary IPv6'), required=False, query_params={ 'device_id': '$device', 'family': '6', } ) fieldsets = ( (_('Virtual Device Context'), ('device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags')), (_('Tenancy'), ('tenant_group', 'tenant')) ) class Meta: model = VirtualDeviceContext fields = [ 'device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant', 'comments', 'tags' ]