1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

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,305 +1,142 @@
from django import forms
from circuits.models import Circuit, CircuitTermination, Provider
from dcim.models import *
from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect
__all__ = (
'ConnectCableToCircuitTerminationForm',
'ConnectCableToConsolePortForm',
'ConnectCableToConsoleServerPortForm',
'ConnectCableToFrontPortForm',
'ConnectCableToInterfaceForm',
'ConnectCableToPowerFeedForm',
'ConnectCableToPowerPortForm',
'ConnectCableToPowerOutletForm',
'ConnectCableToRearPortForm',
)
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField
from .models import CableForm
class BaseCableConnectionForm(TenancyForm, NetBoxModelForm):
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 get_cable_form(a_type, b_type):
def save(self, *args, **kwargs):
class FormMetaclass(forms.models.ModelFormMetaclass):
# Set the A/B terminations on the Cable instance
self.instance.a_terminations = self.cleaned_data['a_terminations']
self.instance.b_terminations = self.cleaned_data['b_terminations']
def __new__(mcs, name, bases, attrs):
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):
"""
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',
}
)
# Device component
if hasattr(term_cls, 'device'):
class Meta:
model = Cable
fields = [
'a_terminations', 'termination_b_region', 'termination_b_sitegroup', 'termination_b_site',
'termination_b_rack', 'termination_b_device', 'b_terminations', 'type', 'status', 'tenant_group',
'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
]
widgets = {
'status': StaticSelect,
'type': StaticSelect,
'length_unit': StaticSelect,
}
attrs[f'termination_{cable_end}_rack'] = DynamicModelChoiceField(
queryset=Rack.objects.all(),
label='Rack',
required=False,
null_option='None',
query_params={
'site_id': '$termination_{cable_end}_site',
'location_id': '$termination_{cable_end}_location',
}
)
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):
b_terminations = DynamicModelMultipleChoiceField(
queryset=ConsolePort.objects.all(),
label='Name',
disabled_indicator='_occupied',
query_params={
'device_id': '$termination_b_device'
}
)
attrs[f'termination_{cable_end}_powerpanel'] = DynamicModelChoiceField(
queryset=PowerPanel.objects.all(),
label='Power Panel',
required=False,
query_params={
'site_id': f'$termination_{cable_end}_site',
'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):
b_terminations = DynamicModelMultipleChoiceField(
queryset=ConsoleServerPort.objects.all(),
label='Name',
disabled_indicator='_occupied',
query_params={
'device_id': '$termination_b_device'
}
)
attrs[f'termination_{cable_end}_provider'] = DynamicModelChoiceField(
queryset=Provider.objects.all(),
label='Provider',
required=False
)
attrs[f'termination_{cable_end}_circuit'] = DynamicModelChoiceField(
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):
b_terminations = DynamicModelMultipleChoiceField(
queryset=PowerPort.objects.all(),
label='Name',
disabled_indicator='_occupied',
query_params={
'device_id': '$termination_b_device'
}
)
class _CableForm(CableForm, metaclass=FormMetaclass):
def save(self, *args, **kwargs):
class ConnectCableToPowerOutletForm(ConnectCableToDeviceForm):
b_terminations = DynamicModelMultipleChoiceField(
queryset=PowerOutlet.objects.all(),
label='Name',
disabled_indicator='_occupied',
query_params={
'device_id': '$termination_b_device'
}
)
# Set the A/B terminations on the Cable instance
self.instance.a_terminations = self.cleaned_data['a_terminations']
self.instance.b_terminations = self.cleaned_data['b_terminations']
return super().save(*args, **kwargs)
class ConnectCableToInterfaceForm(ConnectCableToDeviceForm):
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',
]
return _CableForm

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>/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/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'),
# 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>/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/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'),
# 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>/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/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'),
# 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>/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/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'),
# Interfaces
@@ -358,7 +358,7 @@ urlpatterns = [
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>/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'),
# 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>/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/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'),
# 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>/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/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'),
# 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>/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/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.views.generic import View
from circuits.models import Circuit
from circuits.models import Circuit, CircuitTermination
from extras.views import ObjectConfigContextView
from ipam.models import ASN, IPAddress, Prefix, Service, VLAN, VLANGroup
from ipam.tables import AssignedIPAddressesTable, InterfaceVLANTable
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.permissions import get_permission_for_model
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()
template_name = 'dcim/cable_connect.html'
template_name = 'dcim/cable_edit.html'
def dispatch(self, request, *args, **kwargs):
# Set the form class based on the type of component being connected
self.form = {
'dcim.consoleport': forms.ConnectCableToConsolePortForm,
'dcim.consoleserverport': forms.ConnectCableToConsoleServerPortForm,
'dcim.powerport': forms.ConnectCableToPowerPortForm,
'dcim.poweroutlet': forms.ConnectCableToPowerOutletForm,
'dcim.interface': forms.ConnectCableToInterfaceForm,
'dcim.frontport': forms.ConnectCableToFrontPortForm,
'dcim.rearport': forms.ConnectCableToRearPortForm,
'dcim.powerfeed': forms.ConnectCableToPowerFeedForm,
'circuits.circuittermination': forms.ConnectCableToCircuitTerminationForm,
}[request.GET.get('termination_b_type')]
# If creating a new Cable, initialize the form class using URL query params
if 'pk' not in kwargs:
termination_types = {
'dcim.consoleport': ConsolePort,
'dcim.consoleserverport': ConsoleServerPort,
'dcim.powerport': PowerPort,
'dcim.poweroutlet': PowerOutlet,
'dcim.interface': Interface,
'dcim.frontport': FrontPort,
'dcim.rearport': RearPort,
'dcim.powerfeed': PowerFeed,
'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)
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):
obj = self.get_object(**kwargs)
obj = self.alter_object(obj, request, args, kwargs)
initial_data = request.GET
if obj.pk:
# TODO: Optimize this logic
termination_a = obj.terminations.filter(cable_end='A').first()
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('.')
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'
return obj
class CableDeleteView(generic.ObjectDeleteView):