Merge cable creation/edit views & forms

This commit is contained in:
jeremystretch 2022-05-20 16:53:23 -04:00
parent d155c39f59
commit 7b5ff4c1a5
8 changed files with 310 additions and 552 deletions

View File

@ -1,6 +1,6 @@
from django.urls import path from django.urls import path
from dcim.views import CableCreateView, PathTraceView from dcim.views import CableEditView, PathTraceView
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
from . import views from . import views
from .models import * from .models import *
@ -61,6 +61,6 @@ urlpatterns = [
path('circuit-terminations/<int:pk>/edit/', views.CircuitTerminationEditView.as_view(), name='circuittermination_edit'), path('circuit-terminations/<int:pk>/edit/', views.CircuitTerminationEditView.as_view(), name='circuittermination_edit'),
path('circuit-terminations/<int:pk>/delete/', views.CircuitTerminationDeleteView.as_view(), name='circuittermination_delete'), path('circuit-terminations/<int:pk>/delete/', views.CircuitTerminationDeleteView.as_view(), name='circuittermination_delete'),
path('circuit-terminations/<int:pk>/trace/', PathTraceView.as_view(), name='circuittermination_trace', kwargs={'model': CircuitTermination}), path('circuit-terminations/<int:pk>/trace/', PathTraceView.as_view(), name='circuittermination_trace', kwargs={'model': CircuitTermination}),
path('circuit-terminations/connect/', CableCreateView.as_view(), name='circuittermination_connect', kwargs={'termination_a_type': CircuitTermination}), path('circuit-terminations/connect/', CableEditView.as_view(), name='circuittermination_connect', kwargs={'termination_a_type': CircuitTermination}),
] ]

View File

@ -1,305 +1,142 @@
from django import forms
from circuits.models import Circuit, CircuitTermination, Provider from circuits.models import Circuit, CircuitTermination, Provider
from dcim.models import * from dcim.models import *
from netbox.forms import NetBoxModelForm from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField
from tenancy.forms import TenancyForm from .models import CableForm
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect
__all__ = (
'ConnectCableToCircuitTerminationForm',
'ConnectCableToConsolePortForm',
'ConnectCableToConsoleServerPortForm',
'ConnectCableToFrontPortForm',
'ConnectCableToInterfaceForm',
'ConnectCableToPowerFeedForm',
'ConnectCableToPowerPortForm',
'ConnectCableToPowerOutletForm',
'ConnectCableToRearPortForm',
)
class BaseCableConnectionForm(TenancyForm, NetBoxModelForm): def get_cable_form(a_type, b_type):
a_terminations = DynamicModelMultipleChoiceField(
queryset=Interface.objects.all(),
label='Name',
disabled_indicator='_occupied'
)
b_terminations = DynamicModelMultipleChoiceField(
queryset=Interface.objects.all(),
label='Name',
disabled_indicator='_occupied'
)
def save(self, *args, **kwargs): class FormMetaclass(forms.models.ModelFormMetaclass):
# Set the A/B terminations on the Cable instance def __new__(mcs, name, bases, attrs):
self.instance.a_terminations = self.cleaned_data['a_terminations']
self.instance.b_terminations = self.cleaned_data['b_terminations']
return super().save(*args, **kwargs) for cable_end, term_cls in (('a', a_type), ('b', b_type)):
attrs[f'termination_{cable_end}_region'] = DynamicModelChoiceField(
queryset=Region.objects.all(),
label='Region',
required=False,
initial_params={
'sites': '$termination_{cable_end}_site'
}
)
attrs[f'termination_{cable_end}_sitegroup'] = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
label='Site group',
required=False,
initial_params={
'sites': '$termination_{cable_end}_site'
}
)
attrs[f'termination_{cable_end}_site'] = DynamicModelChoiceField(
queryset=Site.objects.all(),
label='Site',
required=False,
query_params={
'region_id': '$termination_{cable_end}_region',
'group_id': '$termination_{cable_end}_sitegroup',
}
)
attrs[f'termination_{cable_end}_location'] = DynamicModelChoiceField(
queryset=Location.objects.all(),
label='Location',
required=False,
null_option='None',
query_params={
'site_id': '$termination_{cable_end}_site'
}
)
class ConnectCableToDeviceForm(BaseCableConnectionForm): # Device component
""" if hasattr(term_cls, 'device'):
Base form for connecting a Cable to a Device component
"""
termination_b_region = DynamicModelChoiceField(
queryset=Region.objects.all(),
label='Region',
required=False,
initial_params={
'sites': '$termination_b_site'
}
)
termination_b_sitegroup = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
label='Site group',
required=False,
initial_params={
'sites': '$termination_b_site'
}
)
termination_b_site = DynamicModelChoiceField(
queryset=Site.objects.all(),
label='Site',
required=False,
query_params={
'region_id': '$termination_b_region',
'group_id': '$termination_b_sitegroup',
}
)
termination_b_location = DynamicModelChoiceField(
queryset=Location.objects.all(),
label='Location',
required=False,
null_option='None',
query_params={
'site_id': '$termination_b_site'
}
)
termination_b_rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
label='Rack',
required=False,
null_option='None',
query_params={
'site_id': '$termination_b_site',
'location_id': '$termination_b_location',
}
)
termination_b_device = DynamicModelChoiceField(
queryset=Device.objects.all(),
label='Device',
required=False,
query_params={
'site_id': '$termination_b_site',
'location_id': '$termination_b_location',
'rack_id': '$termination_b_rack',
}
)
class Meta: attrs[f'termination_{cable_end}_rack'] = DynamicModelChoiceField(
model = Cable queryset=Rack.objects.all(),
fields = [ label='Rack',
'a_terminations', 'termination_b_region', 'termination_b_sitegroup', 'termination_b_site', required=False,
'termination_b_rack', 'termination_b_device', 'b_terminations', 'type', 'status', 'tenant_group', null_option='None',
'tenant', 'label', 'color', 'length', 'length_unit', 'tags', query_params={
] 'site_id': '$termination_{cable_end}_site',
widgets = { 'location_id': '$termination_{cable_end}_location',
'status': StaticSelect, }
'type': StaticSelect, )
'length_unit': StaticSelect, attrs[f'termination_{cable_end}_device'] = DynamicModelChoiceField(
} queryset=Device.objects.all(),
label='Device',
required=False,
query_params={
'site_id': f'$termination_{cable_end}_site',
'location_id': f'$termination_{cable_end}_location',
'rack_id': f'$termination_{cable_end}_rack',
}
)
attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
queryset=term_cls.objects.all(),
label=term_cls._meta.verbose_name.title(),
disabled_indicator='_occupied',
query_params={
'device_id': f'termination_{cable_end}_device',
}
)
# PowerFeed
elif term_cls == PowerFeed:
class ConnectCableToConsolePortForm(ConnectCableToDeviceForm): attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelChoiceField(
b_terminations = DynamicModelMultipleChoiceField( queryset=PowerPanel.objects.all(),
queryset=ConsolePort.objects.all(), label='Power Panel',
label='Name', required=False,
disabled_indicator='_occupied', query_params={
query_params={ 'site_id': f'$termination_{cable_end}_site',
'device_id': '$termination_b_device' 'location_id': f'$termination_{cable_end}_location',
} }
) )
attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
queryset=term_cls.objects.all(),
label='Power Feed',
disabled_indicator='_occupied',
query_params={
'powerpanel_id': f'termination_{cable_end}_powerpanel',
}
)
# CircuitTermination
elif term_cls == CircuitTermination:
class ConnectCableToConsoleServerPortForm(ConnectCableToDeviceForm): attrs[f'termination_{cable_end}_provider'] = DynamicModelChoiceField(
b_terminations = DynamicModelMultipleChoiceField( queryset=Provider.objects.all(),
queryset=ConsoleServerPort.objects.all(), label='Provider',
label='Name', required=False
disabled_indicator='_occupied', )
query_params={ attrs[f'termination_{cable_end}_circuit'] = DynamicModelChoiceField(
'device_id': '$termination_b_device' queryset=Circuit.objects.all(),
} label='Circuit',
) query_params={
'provider_id': f'$termination_{cable_end}_provider',
'site_id': f'$termination_{cable_end}_site',
}
)
attrs[f'{cable_end}_terminations'] = DynamicModelMultipleChoiceField(
queryset=term_cls.objects.all(),
label='Side',
disabled_indicator='_occupied',
query_params={
'circuit_id': f'termination_{cable_end}_circuit',
}
)
return super().__new__(mcs, name, bases, attrs)
class ConnectCableToPowerPortForm(ConnectCableToDeviceForm): class _CableForm(CableForm, metaclass=FormMetaclass):
b_terminations = DynamicModelMultipleChoiceField(
queryset=PowerPort.objects.all(),
label='Name',
disabled_indicator='_occupied',
query_params={
'device_id': '$termination_b_device'
}
)
def save(self, *args, **kwargs):
class ConnectCableToPowerOutletForm(ConnectCableToDeviceForm): # Set the A/B terminations on the Cable instance
b_terminations = DynamicModelMultipleChoiceField( self.instance.a_terminations = self.cleaned_data['a_terminations']
queryset=PowerOutlet.objects.all(), self.instance.b_terminations = self.cleaned_data['b_terminations']
label='Name',
disabled_indicator='_occupied',
query_params={
'device_id': '$termination_b_device'
}
)
return super().save(*args, **kwargs)
class ConnectCableToInterfaceForm(ConnectCableToDeviceForm): return _CableForm
b_terminations = DynamicModelMultipleChoiceField(
queryset=Interface.objects.all(),
label='Name',
disabled_indicator='_occupied',
query_params={
'device_id': '$termination_b_device',
'kind': 'physical',
}
)
class ConnectCableToFrontPortForm(ConnectCableToDeviceForm):
b_terminations = DynamicModelMultipleChoiceField(
queryset=FrontPort.objects.all(),
label='Name',
disabled_indicator='_occupied',
query_params={
'device_id': '$termination_b_device'
}
)
class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
b_terminations = DynamicModelMultipleChoiceField(
queryset=RearPort.objects.all(),
label='Name',
disabled_indicator='_occupied',
query_params={
'device_id': '$termination_b_device'
}
)
class ConnectCableToCircuitTerminationForm(BaseCableConnectionForm):
termination_b_provider = DynamicModelChoiceField(
queryset=Provider.objects.all(),
label='Provider',
required=False
)
termination_b_region = DynamicModelChoiceField(
queryset=Region.objects.all(),
label='Region',
required=False,
initial_params={
'sites': '$termination_b_site'
}
)
termination_b_sitegroup = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
label='Site group',
required=False,
initial_params={
'sites': '$termination_b_site'
}
)
termination_b_site = DynamicModelChoiceField(
queryset=Site.objects.all(),
label='Site',
required=False,
query_params={
'region_id': '$termination_b_region',
'group_id': '$termination_b_sitegroup',
}
)
termination_b_circuit = DynamicModelChoiceField(
queryset=Circuit.objects.all(),
label='Circuit',
query_params={
'provider_id': '$termination_b_provider',
'site_id': '$termination_b_site',
}
)
b_terminations = DynamicModelMultipleChoiceField(
queryset=CircuitTermination.objects.all(),
label='Side',
disabled_indicator='_occupied',
query_params={
'circuit_id': '$termination_b_circuit'
}
)
class Meta(ConnectCableToDeviceForm.Meta):
fields = [
'a_terminations', 'termination_b_provider', 'termination_b_region', 'termination_b_sitegroup',
'termination_b_site', 'termination_b_circuit', 'b_terminations', 'type', 'status', 'tenant_group',
'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
]
class ConnectCableToPowerFeedForm(BaseCableConnectionForm):
termination_b_region = DynamicModelChoiceField(
queryset=Region.objects.all(),
label='Region',
required=False,
initial_params={
'sites': '$termination_b_site'
}
)
termination_b_sitegroup = DynamicModelChoiceField(
queryset=SiteGroup.objects.all(),
label='Site group',
required=False,
initial_params={
'sites': '$termination_b_site'
}
)
termination_b_site = DynamicModelChoiceField(
queryset=Site.objects.all(),
label='Site',
required=False,
query_params={
'region_id': '$termination_b_region',
'group_id': '$termination_b_sitegroup',
}
)
termination_b_location = DynamicModelChoiceField(
queryset=Location.objects.all(),
label='Location',
required=False,
query_params={
'site_id': '$termination_b_site'
}
)
termination_b_powerpanel = DynamicModelChoiceField(
queryset=PowerPanel.objects.all(),
label='Power Panel',
required=False,
query_params={
'site_id': '$termination_b_site',
'location_id': '$termination_b_location',
}
)
b_terminations = DynamicModelMultipleChoiceField(
queryset=PowerFeed.objects.all(),
label='Name',
disabled_indicator='_occupied',
query_params={
'power_panel_id': '$termination_b_powerpanel'
}
)
class Meta(ConnectCableToDeviceForm.Meta):
fields = [
'a_terminations', 'termination_b_region', 'termination_b_sitegroup', 'termination_b_site',
'termination_b_location', 'termination_b_powerpanel', 'b_terminations', 'type', 'status', 'tenant_group',
'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
]

View File

@ -294,7 +294,7 @@ urlpatterns = [
path('console-ports/<int:pk>/delete/', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'), path('console-ports/<int:pk>/delete/', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'),
path('console-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='consoleport_changelog', kwargs={'model': ConsolePort}), path('console-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='consoleport_changelog', kwargs={'model': ConsolePort}),
path('console-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='consoleport_trace', kwargs={'model': ConsolePort}), path('console-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='consoleport_trace', kwargs={'model': ConsolePort}),
path('console-ports/connect/', views.CableCreateView.as_view(), name='consoleport_connect', kwargs={'termination_a_type': ConsolePort}), path('console-ports/connect/', views.CableEditView.as_view(), name='consoleport_connect', kwargs={'termination_a_type': ConsolePort}),
path('devices/console-ports/add/', views.DeviceBulkAddConsolePortView.as_view(), name='device_bulk_add_consoleport'), path('devices/console-ports/add/', views.DeviceBulkAddConsolePortView.as_view(), name='device_bulk_add_consoleport'),
# Console server ports # Console server ports
@ -310,7 +310,7 @@ urlpatterns = [
path('console-server-ports/<int:pk>/delete/', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'), path('console-server-ports/<int:pk>/delete/', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'),
path('console-server-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='consoleserverport_changelog', kwargs={'model': ConsoleServerPort}), path('console-server-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='consoleserverport_changelog', kwargs={'model': ConsoleServerPort}),
path('console-server-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='consoleserverport_trace', kwargs={'model': ConsoleServerPort}), path('console-server-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='consoleserverport_trace', kwargs={'model': ConsoleServerPort}),
path('console-server-ports/connect/', views.CableCreateView.as_view(), name='consoleserverport_connect', kwargs={'termination_a_type': ConsoleServerPort}), path('console-server-ports/connect/', views.CableEditView.as_view(), name='consoleserverport_connect', kwargs={'termination_a_type': ConsoleServerPort}),
path('devices/console-server-ports/add/', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'), path('devices/console-server-ports/add/', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'),
# Power ports # Power ports
@ -326,7 +326,7 @@ urlpatterns = [
path('power-ports/<int:pk>/delete/', views.PowerPortDeleteView.as_view(), name='powerport_delete'), path('power-ports/<int:pk>/delete/', views.PowerPortDeleteView.as_view(), name='powerport_delete'),
path('power-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='powerport_changelog', kwargs={'model': PowerPort}), path('power-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='powerport_changelog', kwargs={'model': PowerPort}),
path('power-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='powerport_trace', kwargs={'model': PowerPort}), path('power-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='powerport_trace', kwargs={'model': PowerPort}),
path('power-ports/connect/', views.CableCreateView.as_view(), name='powerport_connect', kwargs={'termination_a_type': PowerPort}), path('power-ports/connect/', views.CableEditView.as_view(), name='powerport_connect', kwargs={'termination_a_type': PowerPort}),
path('devices/power-ports/add/', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'), path('devices/power-ports/add/', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'),
# Power outlets # Power outlets
@ -342,7 +342,7 @@ urlpatterns = [
path('power-outlets/<int:pk>/delete/', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'), path('power-outlets/<int:pk>/delete/', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'),
path('power-outlets/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='poweroutlet_changelog', kwargs={'model': PowerOutlet}), path('power-outlets/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='poweroutlet_changelog', kwargs={'model': PowerOutlet}),
path('power-outlets/<int:pk>/trace/', views.PathTraceView.as_view(), name='poweroutlet_trace', kwargs={'model': PowerOutlet}), path('power-outlets/<int:pk>/trace/', views.PathTraceView.as_view(), name='poweroutlet_trace', kwargs={'model': PowerOutlet}),
path('power-outlets/connect/', views.CableCreateView.as_view(), name='poweroutlet_connect', kwargs={'termination_a_type': PowerOutlet}), path('power-outlets/connect/', views.CableEditView.as_view(), name='poweroutlet_connect', kwargs={'termination_a_type': PowerOutlet}),
path('devices/power-outlets/add/', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'), path('devices/power-outlets/add/', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'),
# Interfaces # Interfaces
@ -358,7 +358,7 @@ urlpatterns = [
path('interfaces/<int:pk>/delete/', views.InterfaceDeleteView.as_view(), name='interface_delete'), path('interfaces/<int:pk>/delete/', views.InterfaceDeleteView.as_view(), name='interface_delete'),
path('interfaces/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='interface_changelog', kwargs={'model': Interface}), path('interfaces/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='interface_changelog', kwargs={'model': Interface}),
path('interfaces/<int:pk>/trace/', views.PathTraceView.as_view(), name='interface_trace', kwargs={'model': Interface}), path('interfaces/<int:pk>/trace/', views.PathTraceView.as_view(), name='interface_trace', kwargs={'model': Interface}),
path('interfaces/connect/', views.CableCreateView.as_view(), name='interface_connect', kwargs={'termination_a_type': Interface}), path('interfaces/connect/', views.CableEditView.as_view(), name='interface_connect', kwargs={'termination_a_type': Interface}),
path('devices/interfaces/add/', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'), path('devices/interfaces/add/', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'),
# Front ports # Front ports
@ -374,7 +374,7 @@ urlpatterns = [
path('front-ports/<int:pk>/delete/', views.FrontPortDeleteView.as_view(), name='frontport_delete'), path('front-ports/<int:pk>/delete/', views.FrontPortDeleteView.as_view(), name='frontport_delete'),
path('front-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='frontport_changelog', kwargs={'model': FrontPort}), path('front-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='frontport_changelog', kwargs={'model': FrontPort}),
path('front-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='frontport_trace', kwargs={'model': FrontPort}), path('front-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='frontport_trace', kwargs={'model': FrontPort}),
path('front-ports/connect/', views.CableCreateView.as_view(), name='frontport_connect', kwargs={'termination_a_type': FrontPort}), path('front-ports/connect/', views.CableEditView.as_view(), name='frontport_connect', kwargs={'termination_a_type': FrontPort}),
# path('devices/front-ports/add/', views.DeviceBulkAddFrontPortView.as_view(), name='device_bulk_add_frontport'), # path('devices/front-ports/add/', views.DeviceBulkAddFrontPortView.as_view(), name='device_bulk_add_frontport'),
# Rear ports # Rear ports
@ -390,7 +390,7 @@ urlpatterns = [
path('rear-ports/<int:pk>/delete/', views.RearPortDeleteView.as_view(), name='rearport_delete'), path('rear-ports/<int:pk>/delete/', views.RearPortDeleteView.as_view(), name='rearport_delete'),
path('rear-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rearport_changelog', kwargs={'model': RearPort}), path('rear-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rearport_changelog', kwargs={'model': RearPort}),
path('rear-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='rearport_trace', kwargs={'model': RearPort}), path('rear-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='rearport_trace', kwargs={'model': RearPort}),
path('rear-ports/connect/', views.CableCreateView.as_view(), name='rearport_connect', kwargs={'termination_a_type': RearPort}), path('rear-ports/connect/', views.CableEditView.as_view(), name='rearport_connect', kwargs={'termination_a_type': RearPort}),
path('devices/rear-ports/add/', views.DeviceBulkAddRearPortView.as_view(), name='device_bulk_add_rearport'), path('devices/rear-ports/add/', views.DeviceBulkAddRearPortView.as_view(), name='device_bulk_add_rearport'),
# Module bays # Module bays
@ -500,6 +500,6 @@ urlpatterns = [
path('power-feeds/<int:pk>/trace/', views.PathTraceView.as_view(), name='powerfeed_trace', kwargs={'model': PowerFeed}), path('power-feeds/<int:pk>/trace/', views.PathTraceView.as_view(), name='powerfeed_trace', kwargs={'model': PowerFeed}),
path('power-feeds/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='powerfeed_changelog', kwargs={'model': PowerFeed}), path('power-feeds/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='powerfeed_changelog', kwargs={'model': PowerFeed}),
path('power-feeds/<int:pk>/journal/', ObjectJournalView.as_view(), name='powerfeed_journal', kwargs={'model': PowerFeed}), path('power-feeds/<int:pk>/journal/', ObjectJournalView.as_view(), name='powerfeed_journal', kwargs={'model': PowerFeed}),
path('power-feeds/connect/', views.CableCreateView.as_view(), name='powerfeed_connect', kwargs={'termination_a_type': PowerFeed}), path('power-feeds/connect/', views.CableEditView.as_view(), name='powerfeed_connect', kwargs={'termination_a_type': PowerFeed}),
] ]

View File

@ -12,12 +12,12 @@ from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.views.generic import View from django.views.generic import View
from circuits.models import Circuit from circuits.models import Circuit, CircuitTermination
from extras.views import ObjectConfigContextView from extras.views import ObjectConfigContextView
from ipam.models import ASN, IPAddress, Prefix, Service, VLAN, VLANGroup from ipam.models import ASN, IPAddress, Prefix, Service, VLAN, VLANGroup
from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable
from netbox.views import generic from netbox.views import generic
from utilities.forms import ConfirmationForm from utilities.forms import ConfirmationForm, restrict_form_fields
from utilities.paginator import EnhancedPaginator, get_paginate_count from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.permissions import get_permission_for_model from utilities.permissions import get_permission_for_model
from utilities.utils import count_related from utilities.utils import count_related
@ -2804,72 +2804,49 @@ class PathTraceView(generic.ObjectView):
} }
class CableCreateView(generic.ObjectEditView): class CableEditView(generic.ObjectEditView):
queryset = Cable.objects.all() queryset = Cable.objects.all()
template_name = 'dcim/cable_connect.html' template_name = 'dcim/cable_edit.html'
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
# Set the form class based on the type of component being connected # If creating a new Cable, initialize the form class using URL query params
self.form = { if 'pk' not in kwargs:
'dcim.consoleport': forms.ConnectCableToConsolePortForm, termination_types = {
'dcim.consoleserverport': forms.ConnectCableToConsoleServerPortForm, 'dcim.consoleport': ConsolePort,
'dcim.powerport': forms.ConnectCableToPowerPortForm, 'dcim.consoleserverport': ConsoleServerPort,
'dcim.poweroutlet': forms.ConnectCableToPowerOutletForm, 'dcim.powerport': PowerPort,
'dcim.interface': forms.ConnectCableToInterfaceForm, 'dcim.poweroutlet': PowerOutlet,
'dcim.frontport': forms.ConnectCableToFrontPortForm, 'dcim.interface': Interface,
'dcim.rearport': forms.ConnectCableToRearPortForm, 'dcim.frontport': FrontPort,
'dcim.powerfeed': forms.ConnectCableToPowerFeedForm, 'dcim.rearport': RearPort,
'circuits.circuittermination': forms.ConnectCableToCircuitTerminationForm, 'dcim.powerfeed': PowerFeed,
}[request.GET.get('termination_b_type')] 'circuits.circuittermination': CircuitTermination,
}
a_type = kwargs.pop('termination_a_type')
b_type = termination_types[request.GET.get('termination_b_type')]
self.form = forms.get_cable_form(a_type, b_type)
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_object(self, **kwargs): def get_object(self, **kwargs):
# Always return a new instance """
return self.queryset.model() Hack into get_object() to set the form class when editing an existing Cable, since ObjectEditView
doesn't currently provide a hook for dynamic class resolution.
"""
obj = super().get_object(**kwargs)
def get(self, request, *args, **kwargs): if obj.pk:
obj = self.get_object(**kwargs) # TODO: Optimize this logic
obj = self.alter_object(obj, request, args, kwargs) termination_a = obj.terminations.filter(cable_end='A').first()
initial_data = request.GET a_type = termination_a.termination._meta.model if termination_a else None
termination_b = obj.terminations.filter(cable_end='B').first()
b_type = termination_b.termination._meta.model if termination_a else None
self.form = forms.get_cable_form(a_type, b_type)
app_label, model = request.GET.get('termination_b_type').split('.') return obj
termination_b_type = ContentType.objects.get(app_label=app_label, model=model)
# TODO
# # Set initial site and rack based on side A termination (if not already set)
# termination_a_site = getattr(obj.termination_a.parent_object, 'site', None)
# if 'termination_b_site' not in initial_data:
# initial_data['termination_b_site'] = termination_a_site
# if 'termination_b_rack' not in initial_data:
# initial_data['termination_b_rack'] = getattr(obj.termination_a.parent_object, 'rack', None)
form = self.form(instance=obj, initial=initial_data)
# TODO Find a better way to infer the near-end parent object
termination_a = kwargs['termination_a_type'].objects.filter(pk=int(initial_data['a_terminations'])).first()
# Set the queryset of termination A
form.fields['a_terminations'].queryset = kwargs['termination_a_type'].objects.all()
form.fields['a_terminations'].widget.add_query_params({
'device_id': termination_a.device_id,
})
return render(request, self.template_name, {
'obj': obj,
'obj_type': Cable._meta.verbose_name,
'termination_a_type': kwargs['termination_a_type']._meta.model_name,
'termination_a_parent': termination_a.parent_object,
'termination_b_type': termination_b_type.name,
'form': form,
'return_url': self.get_return_url(request, obj),
})
class CableEditView(generic.ObjectEditView):
queryset = Cable.objects.all()
form = forms.CableForm
template_name = 'dcim/cable_edit.html'
class CableDeleteView(generic.ObjectDeleteView): class CableDeleteView(generic.ObjectDeleteView):

View File

@ -1,167 +0,0 @@
{% extends 'base/layout.html' %}
{% load static %}
{% load helpers %}
{% load form_helpers %}
{% block title %}Connect Cable to {{ termination_b_type|bettertitle }}{% endblock %}
{% block tabs %}
<ul class="nav nav-tabs px-3">
<li class="nav-item" role="presentation">
<a href="#" role="tab" data-bs-toggle="tab" class="nav-link active">Connect Cable</a>
</li>
</ul>
{% endblock %}
{% block content-wrapper %}
<div class="tab-content">
{% render_errors form %}
<form method="post">
{% csrf_token %}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
<div class="row my-3">
<div class="col col-md-5">
<div class="card h-100">
<h5 class="card-header offset-sm-3">A Side</h5>
<div class="card-body">
{% if termination_a_type == 'circuit' %}
{# Circuit termination #}
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">Provider</label>
<div class="col">
<input class="form-control" value="{{ termination_a_parent.provider }}" disabled />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">Circuit</label>
<div class="col">
<input class="form-control" value="{{ termination_a_parent.cid }}" disabled />
</div>
</div>
{% else %}
{# Device component #}
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">Region</label>
<div class="col">
<input class="form-control" value="{{ termination_a_parent.site.region }}" disabled />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">Site Group</label>
<div class="col">
<input class="form-control" value="{{ termination_a_parent.site.group }}" disabled />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">Site</label>
<div class="col">
<input class="form-control" value="{{ termination_a_parent.site }}" disabled />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">Location</label>
<div class="col">
<input class="form-control" value="{{ termination_a_parent.location|default:"None" }}" disabled />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">Rack</label>
<div class="col">
<input class="form-control" value="{{ termination_a_parent.rack|default:"None" }}" disabled />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">Device</label>
<div class="col">
<input class="form-control" value="{{ termination_a_parent }}" disabled />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">Type</label>
<div class="col">
<input class="form-control" value="{{ termination_a_type|capfirst }}" disabled />
</div>
</div>
{% endif %}
{% render_field form.a_terminations %}
</div>
</div>
</div>
<div class="col col-md-2 flex-column justify-content-center align-items-center d-none d-md-flex">
<i class="mdi mdi-swap-horizontal-bold mdi-48px"></i>
</div>
<div class="col col-md-5">
<div class="card h-100">
<h5 class="card-header offset-sm-3">B Side</h5>
<div class="card-body">
{% if tabs %}
<ul class="nav nav-tabs">
{% for url, link in tabs %}
<li class="nav-item" role="presentation">
<a class="nav-link" href="{{ url }}">{{ link }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% if 'termination_b_provider' in form.fields %}
{% render_field form.termination_b_provider %}
{% endif %}
{% if 'termination_b_region' in form.fields %}
{% render_field form.termination_b_region %}
{% endif %}
{% if 'termination_b_sitegroup' in form.fields %}
{% render_field form.termination_b_sitegroup %}
{% endif %}
{% if 'termination_b_site' in form.fields %}
{% render_field form.termination_b_site %}
{% endif %}
{% if 'termination_b_location' in form.fields %}
{% render_field form.termination_b_location %}
{% endif %}
{% if 'termination_b_rack' in form.fields %}
{% render_field form.termination_b_rack %}
{% endif %}
{% if 'termination_b_device' in form.fields %}
{% render_field form.termination_b_device %}
{% endif %}
{% if 'termination_b_type' in form.fields %}
{% render_field form.termination_b_type %}
{% endif %}
{% if 'termination_b_powerpanel' in form.fields %}
{% render_field form.termination_b_powerpanel %}
{% endif %}
{% if 'termination_b_circuit' in form.fields %}
{% render_field form.termination_b_circuit %}
{% endif %}
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">Type</label>
<div class="col">
<input class="form-control" value="{{ termination_b_type|capfirst }}" disabled />
</div>
</div>
{% render_field form.b_terminations %}
</div>
</div>
</div>
</div>
<div class="row my-3 justify-content-center">
<div class="col col-md-8">
<div class="card">
<h5 class="card-header offset-sm-3">Cable</h5>
<div class="card-body">
{% include 'dcim/inc/cable_form.html' %}
</div>
</div>
</div>
</div>
<div class="row my-3">
<div class="col col-md-12 text-center">
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
<button type="submit" name="_update" class="btn btn-primary">Connect</button>
</div>
</div>
</form>
</div>
{% endblock %}

View File

@ -1,5 +1,125 @@
{% extends 'generic/object_edit.html' %} {% extends 'base/layout.html' %}
{% load static %}
{% load helpers %}
{% load form_helpers %}
{% block form %} {% block title %}Connect Cable{% endblock %}
{% include 'dcim/inc/cable_form.html' %}
{% block tabs %}
<ul class="nav nav-tabs px-3">
<li class="nav-item" role="presentation">
<a href="#" role="tab" data-bs-toggle="tab" class="nav-link active">Connect Cable</a>
</li>
</ul>
{% endblock %}
{% block content-wrapper %}
<div class="tab-content">
{% render_errors form %}
<form method="post">
{% csrf_token %}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
<div class="row my-3">
<div class="col col-md-5">
<div class="card h-100">
<h5 class="card-header offset-sm-3">A Side</h5>
<div class="card-body">
{% render_field form.termination_a_region %}
{% render_field form.termination_a_sitegroup %}
{% render_field form.termination_a_site %}
{% render_field form.termination_a_location %}
{% if 'termination_a_rack' in form.fields %}
{% render_field form.termination_a_rack %}
{% endif %}
{% if 'termination_a_device' in form.fields %}
{% render_field form.termination_a_device %}
{% endif %}
{% if 'termination_a_powerpanel' in form.fields %}
{% render_field form.termination_a_powerpanel %}
{% endif %}
{% if 'termination_a_provider' in form.fields %}
{% render_field form.termination_a_provider %}
{% endif %}
{% if 'termination_a_circuit' in form.fields %}
{% render_field form.termination_a_circuit %}
{% endif %}
{% render_field form.a_terminations %}
</div>
</div>
</div>
<div class="col col-md-2 flex-column justify-content-center align-items-center d-none d-md-flex">
<i class="mdi mdi-swap-horizontal-bold mdi-48px"></i>
</div>
<div class="col col-md-5">
<div class="card h-100">
<h5 class="card-header offset-sm-3">B Side</h5>
<div class="card-body">
{% render_field form.termination_b_region %}
{% render_field form.termination_b_sitegroup %}
{% render_field form.termination_b_site %}
{% render_field form.termination_b_location %}
{% if 'termination_b_rack' in form.fields %}
{% render_field form.termination_b_rack %}
{% endif %}
{% if 'termination_b_device' in form.fields %}
{% render_field form.termination_b_device %}
{% endif %}
{% if 'termination_b_powerpanel' in form.fields %}
{% render_field form.termination_b_powerpanel %}
{% endif %}
{% if 'termination_b_provider' in form.fields %}
{% render_field form.termination_b_provider %}
{% endif %}
{% if 'termination_b_circuit' in form.fields %}
{% render_field form.termination_b_circuit %}
{% endif %}
{% render_field form.b_terminations %}
</div>
</div>
</div>
</div>
<div class="row my-3 justify-content-center">
<div class="col col-md-8">
<div class="card">
<h5 class="card-header offset-sm-3">Cable</h5>
<div class="card-body">
{% render_field form.status %}
{% render_field form.type %}
{% render_field form.tenant_group %}
{% render_field form.tenant %}
{% render_field form.label %}
{% render_field form.color %}
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">{{ form.length.label }}</label>
<div class="col-md-5">
{{ form.length }}
</div>
<div class="col-md-4">
{{ form.length_unit }}
</div>
<div class="invalid-feedback"></div>
</div>
{% render_field form.tags %}
{% if form.custom_fields %}
<div class="field-group">
<div class="row mb-3">
<h5 class="offset-sm-3">Custom Fields</h5>
</div>
{% render_custom_fields form %}
</div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="row my-3">
<div class="col col-md-12 text-center">
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
<button type="submit" name="_update" class="btn btn-primary">Connect</button>
</div>
</div>
</form>
</div>
{% endblock %} {% endblock %}

View File

@ -1,27 +0,0 @@
{% load form_helpers %}
{% render_field form.status %}
{% render_field form.type %}
{% render_field form.tenant_group %}
{% render_field form.tenant %}
{% render_field form.label %}
{% render_field form.color %}
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">{{ form.length.label }}</label>
<div class="col-md-5">
{{ form.length }}
</div>
<div class="col-md-4">
{{ form.length_unit }}
</div>
<div class="invalid-feedback"></div>
</div>
{% render_field form.tags %}
{% if form.custom_fields %}
<div class="field-group">
<div class="row mb-3">
<h5 class="offset-sm-3">Custom Fields</h5>
</div>
{% render_custom_fields form %}
</div>
{% endif %}

View File

@ -6,13 +6,31 @@
<td>Site</td> <td>Site</td>
<td>{{ terminations.0.device.site|linkify }}</td> <td>{{ terminations.0.device.site|linkify }}</td>
</tr> </tr>
<tr>
<td>Rack</td>
<td>{{ terminations.0.device.rack|linkify|placeholder }}</td>
</tr>
<tr> <tr>
<td>Device</td> <td>Device</td>
<td>{{ terminations.0.device|linkify }}</td> <td>{{ terminations.0.device|linkify }}</td>
</tr> </tr>
<tr> <tr>
<td>Rack</td> <td>{{ terminations.0|meta:"verbose_name"|capfirst }}</td>
<td>{{ terminations.0.device.rack|linkify|placeholder }}</td> <td>
{% for term in terminations %}
{{ term|linkify }}{% if not forloop.last %},{% endif %}
{% endfor %}
</td>
</tr>
{% elif terminations.0.power_panel %}
{# Power feed #}
<tr>
<td>Site</td>
<td>{{ terminations.0.power_panel.site|linkify }}</td>
</tr>
<tr>
<td>Power Panel</td>
<td>{{ terminations.0.power_panel|linkify }}</td>
</tr> </tr>
<tr> <tr>
<td>{{ terminations.0|meta:"verbose_name"|capfirst }}</td> <td>{{ terminations.0|meta:"verbose_name"|capfirst }}</td>