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 dcim.views import CableCreateView, PathTraceView
from dcim.views import CableEditView, PathTraceView
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
from . import views
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>/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/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 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):

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 %}
{% include 'dcim/inc/cable_form.html' %}
{% block title %}Connect Cable{% 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">
{% 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 %}

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>{{ terminations.0.device.site|linkify }}</td>
</tr>
<tr>
<td>Rack</td>
<td>{{ terminations.0.device.rack|linkify|placeholder }}</td>
</tr>
<tr>
<td>Device</td>
<td>{{ terminations.0.device|linkify }}</td>
</tr>
<tr>
<td>Rack</td>
<td>{{ terminations.0.device.rack|linkify|placeholder }}</td>
<td>{{ terminations.0|meta:"verbose_name"|capfirst }}</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>
<td>{{ terminations.0|meta:"verbose_name"|capfirst }}</td>