mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
WIP: Initial work on the cable connection form
This commit is contained in:
parent
a36b120c8b
commit
471bddea09
@ -284,12 +284,9 @@ CONNECTION_STATUS_CHOICES = [
|
||||
]
|
||||
|
||||
# Cable endpoint types
|
||||
CABLE_ENDPOINT_TYPES = (
|
||||
'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport',
|
||||
)
|
||||
CABLE_CONNECTION_TYPES = CABLE_ENDPOINT_TYPES + (
|
||||
'frontpanelport', 'rearpanelport',
|
||||
)
|
||||
CABLE_ENDPOINT_TYPES = [
|
||||
'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport',
|
||||
]
|
||||
|
||||
# Cable types
|
||||
# TODO: Add more types
|
||||
@ -299,3 +296,24 @@ CABLE_TYPE_CHOICES = (
|
||||
(CABLE_TYPE_COPPER, 'Copper'),
|
||||
(CABLE_TYPE_FIBER, 'Fiber'),
|
||||
)
|
||||
|
||||
CABLE_ENDPOINT_TYPE_CHOICES = {
|
||||
# (API endpoint, human-friendly name)
|
||||
'consoleport': ('console-ports', 'Console port'),
|
||||
'consoleserverport': ('console-server-ports', 'Console server port'),
|
||||
'powerport': ('power-ports', 'Power port'),
|
||||
'poweroutlet': ('power-outlets', 'Power outlet'),
|
||||
'interface': ('interfaces', 'Interface'),
|
||||
'frontpanelport': ('front-panel-ports', 'Front panel port'),
|
||||
'rearpanelport': ('rear-panel-ports', 'Rear panel port'),
|
||||
}
|
||||
|
||||
COMPATIBLE_ENDPOINT_TYPES = {
|
||||
'consoleport': ['consoleserverport', 'frontpanelport', 'rearpanelport'],
|
||||
'consoleserverport': ['consoleport', 'frontpanelport', 'rearpanelport'],
|
||||
'powerport': ['poweroutlet'],
|
||||
'poweroutlet': ['powerport'],
|
||||
'interface': ['interface', 'frontpanelport', 'rearpanelport'],
|
||||
'frontpanelport': ['consoleport', 'consoleserverport', 'interface', 'frontpanelport', 'rearpanelport'],
|
||||
'rearpanelport': ['consoleport', 'consoleserverport', 'interface', 'frontpanelport', 'rearpanelport'],
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import re
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.postgres.forms.array import SimpleArrayField
|
||||
from django.db.models import Count, Q
|
||||
from mptt.forms import TreeNodeChoiceField
|
||||
@ -19,11 +20,12 @@ from utilities.forms import (
|
||||
BulkEditNullBooleanSelect, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, ComponentForm,
|
||||
ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, FilterTreeNodeMultipleChoiceField,
|
||||
FlexibleModelChoiceField, JSONField, Livesearch, SelectWithDisabled, SelectWithPK, SmallTextarea, SlugField,
|
||||
ContentTypeSelect
|
||||
)
|
||||
from virtualization.models import Cluster
|
||||
from .constants import *
|
||||
from .models import (
|
||||
DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
|
||||
Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
|
||||
Device, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection,
|
||||
InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site,
|
||||
@ -2298,6 +2300,95 @@ class RearPanelPortBulkRenameForm(BulkRenameForm):
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Cables
|
||||
#
|
||||
|
||||
class CableForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
||||
endpoint_b_site = forms.ModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
label='Site',
|
||||
required=False,
|
||||
widget=forms.Select(
|
||||
attrs={'filter-for': 'endpoint_b_rack'}
|
||||
)
|
||||
)
|
||||
endpoint_b_rack = ChainedModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
chains=(
|
||||
('site', 'endpoint_b_site'),
|
||||
),
|
||||
label='Rack',
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/racks/?site_id={{endpoint_b_site}}',
|
||||
attrs={'filter-for': 'endpoint_b_device', 'nullable': 'true'}
|
||||
)
|
||||
)
|
||||
endpoint_b_device = ChainedModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
chains=(
|
||||
('site', 'endpoint_b_site'),
|
||||
('rack', 'endpoint_b_rack'),
|
||||
),
|
||||
label='Device',
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/devices/?site_id={{endpoint_b_site}}&rack_id={{endpoint_b_rack}}',
|
||||
display_field='display_name',
|
||||
attrs={'filter-for': 'endpoint_b_id'}
|
||||
)
|
||||
)
|
||||
livesearch = forms.CharField(
|
||||
required=False,
|
||||
label='Device',
|
||||
widget=Livesearch(
|
||||
query_key='q',
|
||||
query_url='dcim-api:device-list',
|
||||
field_to_update='endpoint_b_device'
|
||||
)
|
||||
)
|
||||
endpoint_b_type = forms.ModelChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
label='Type',
|
||||
widget=ContentTypeSelect(
|
||||
attrs={'filter-for': 'endpoint_b_id'}
|
||||
)
|
||||
)
|
||||
endpoint_b_id = forms.ChoiceField(
|
||||
choices=[],
|
||||
label='Name',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/{{endpoint_b_type}}s/?device_id={{endpoint_b_device}}',
|
||||
disabled_indicator='is_connected'
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Cable
|
||||
fields = [
|
||||
'endpoint_b_site', 'endpoint_b_rack', 'endpoint_b_device', 'livesearch', 'endpoint_b_type',
|
||||
'endpoint_b_id', 'status', 'label',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CableForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# Define available types for endpoint B based on the type of endpoint A
|
||||
endpoint_a_type = self.instance.endpoint_a._meta.model_name
|
||||
self.fields['endpoint_b_type'].queryset = ContentType.objects.filter(
|
||||
model__in=COMPATIBLE_ENDPOINT_TYPES.get(endpoint_a_type)
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
|
||||
# Assign endpoint B
|
||||
cleaned_data = super(CableForm, self).clean()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Device bays
|
||||
#
|
||||
|
@ -2455,7 +2455,7 @@ class Cable(ChangeLoggedModel):
|
||||
"""
|
||||
endpoint_a_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
limit_choices_to={'model__in': CABLE_CONNECTION_TYPES},
|
||||
limit_choices_to={'model__in': CABLE_ENDPOINT_TYPES},
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+'
|
||||
)
|
||||
@ -2466,7 +2466,7 @@ class Cable(ChangeLoggedModel):
|
||||
)
|
||||
endpoint_b_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
limit_choices_to={'model__in': CABLE_CONNECTION_TYPES},
|
||||
limit_choices_to={'model__in': CABLE_ENDPOINT_TYPES},
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+'
|
||||
)
|
||||
|
@ -161,7 +161,8 @@ urlpatterns = [
|
||||
url(r'^devices/console-ports/add/$', views.DeviceBulkAddConsolePortView.as_view(), name='device_bulk_add_consoleport'),
|
||||
url(r'^devices/(?P<pk>\d+)/console-ports/add/$', views.ConsolePortCreateView.as_view(), name='consoleport_add'),
|
||||
url(r'^devices/(?P<pk>\d+)/console-ports/delete/$', views.ConsolePortBulkDeleteView.as_view(), name='consoleport_bulk_delete'),
|
||||
url(r'^console-ports/(?P<pk>\d+)/connect/$', views.ConsolePortConnectView.as_view(), name='consoleport_connect'),
|
||||
# url(r'^console-ports/(?P<pk>\d+)/connect/$', views.ConsolePortConnectView.as_view(), name='consoleport_connect'),
|
||||
url(r'^console-ports/(?P<endpoint_a_id>\d+)/connect/$', views.CableConnectView.as_view(), name='consoleport_connect', kwargs={'endpoint_a_type': 'consoleport'}),
|
||||
url(r'^console-ports/(?P<pk>\d+)/disconnect/$', views.ConsolePortDisconnectView.as_view(), name='consoleport_disconnect'),
|
||||
url(r'^console-ports/(?P<pk>\d+)/edit/$', views.ConsolePortEditView.as_view(), name='consoleport_edit'),
|
||||
url(r'^console-ports/(?P<pk>\d+)/delete/$', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'),
|
||||
@ -171,7 +172,8 @@ urlpatterns = [
|
||||
url(r'^devices/(?P<pk>\d+)/console-server-ports/add/$', views.ConsoleServerPortCreateView.as_view(), name='consoleserverport_add'),
|
||||
url(r'^devices/(?P<pk>\d+)/console-server-ports/disconnect/$', views.ConsoleServerPortBulkDisconnectView.as_view(), name='consoleserverport_bulk_disconnect'),
|
||||
url(r'^devices/(?P<pk>\d+)/console-server-ports/delete/$', views.ConsoleServerPortBulkDeleteView.as_view(), name='consoleserverport_bulk_delete'),
|
||||
url(r'^console-server-ports/(?P<pk>\d+)/connect/$', views.ConsoleServerPortConnectView.as_view(), name='consoleserverport_connect'),
|
||||
# url(r'^console-server-ports/(?P<pk>\d+)/connect/$', views.ConsoleServerPortConnectView.as_view(), name='consoleserverport_connect'),
|
||||
url(r'^console-server-ports/(?P<endpoint_a_id>\d+)/connect/$', views.CableConnectView.as_view(), name='consoleserverport_connect', kwargs={'endpoint_a_type': 'consoleserverport'}),
|
||||
url(r'^console-server-ports/(?P<pk>\d+)/disconnect/$', views.ConsoleServerPortDisconnectView.as_view(), name='consoleserverport_disconnect'),
|
||||
url(r'^console-server-ports/(?P<pk>\d+)/edit/$', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'),
|
||||
url(r'^console-server-ports/(?P<pk>\d+)/delete/$', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'),
|
||||
@ -181,7 +183,8 @@ urlpatterns = [
|
||||
url(r'^devices/power-ports/add/$', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'),
|
||||
url(r'^devices/(?P<pk>\d+)/power-ports/add/$', views.PowerPortCreateView.as_view(), name='powerport_add'),
|
||||
url(r'^devices/(?P<pk>\d+)/power-ports/delete/$', views.PowerPortBulkDeleteView.as_view(), name='powerport_bulk_delete'),
|
||||
url(r'^power-ports/(?P<pk>\d+)/connect/$', views.PowerPortConnectView.as_view(), name='powerport_connect'),
|
||||
# url(r'^power-ports/(?P<pk>\d+)/connect/$', views.PowerPortConnectView.as_view(), name='powerport_connect'),
|
||||
url(r'^power-ports/(?P<endpoint_a_id>\d+)/connect/$', views.CableConnectView.as_view(), name='powerport_connect', kwargs={'endpoint_a_type': 'powerport'}),
|
||||
url(r'^power-ports/(?P<pk>\d+)/disconnect/$', views.PowerPortDisconnectView.as_view(), name='powerport_disconnect'),
|
||||
url(r'^power-ports/(?P<pk>\d+)/edit/$', views.PowerPortEditView.as_view(), name='powerport_edit'),
|
||||
url(r'^power-ports/(?P<pk>\d+)/delete/$', views.PowerPortDeleteView.as_view(), name='powerport_delete'),
|
||||
@ -191,7 +194,8 @@ urlpatterns = [
|
||||
url(r'^devices/(?P<pk>\d+)/power-outlets/add/$', views.PowerOutletCreateView.as_view(), name='poweroutlet_add'),
|
||||
url(r'^devices/(?P<pk>\d+)/power-outlets/disconnect/$', views.PowerOutletBulkDisconnectView.as_view(), name='poweroutlet_bulk_disconnect'),
|
||||
url(r'^devices/(?P<pk>\d+)/power-outlets/delete/$', views.PowerOutletBulkDeleteView.as_view(), name='poweroutlet_bulk_delete'),
|
||||
url(r'^power-outlets/(?P<pk>\d+)/connect/$', views.PowerOutletConnectView.as_view(), name='poweroutlet_connect'),
|
||||
# url(r'^power-outlets/(?P<pk>\d+)/connect/$', views.PowerOutletConnectView.as_view(), name='poweroutlet_connect'),
|
||||
url(r'^power-outlets/(?P<endpoint_a_id>\d+)/connect/$', views.CableConnectView.as_view(), name='poweroutlet_connect', kwargs={'endpoint_a_type': 'poweroutlet'}),
|
||||
url(r'^power-outlets/(?P<pk>\d+)/disconnect/$', views.PowerOutletDisconnectView.as_view(), name='poweroutlet_disconnect'),
|
||||
url(r'^power-outlets/(?P<pk>\d+)/edit/$', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'),
|
||||
url(r'^power-outlets/(?P<pk>\d+)/delete/$', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'),
|
||||
|
@ -1,12 +1,14 @@
|
||||
from operator import attrgetter
|
||||
|
||||
from django.apps import apps
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.paginator import EmptyPage, PageNotAnInteger
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, Q
|
||||
from django.forms import modelformset_factory
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.html import escape
|
||||
@ -30,7 +32,7 @@ from virtualization.models import VirtualMachine
|
||||
from . import filters, forms, tables
|
||||
from .constants import CONNECTION_STATUS_CONNECTED
|
||||
from .models import (
|
||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPanelPort, FrontPanelPortTemplate, Interface, InterfaceConnection,
|
||||
InterfaceTemplate, Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort,
|
||||
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPanelPort, RearPanelPortTemplate, Region, Site,
|
||||
@ -2152,6 +2154,27 @@ class InterfaceConnectionsListView(ObjectListView):
|
||||
template_name = 'dcim/interface_connections_list.html'
|
||||
|
||||
|
||||
class CableConnectView(PermissionRequiredMixin, ObjectEditView):
|
||||
permission_required = 'dcim.add_cable'
|
||||
model = Cable
|
||||
model_form = forms.CableForm
|
||||
template_name = 'dcim/cable_connect.html'
|
||||
|
||||
def alter_obj(self, obj, request, url_args, url_kwargs):
|
||||
# Retrieve endpoint A based on the given type and PK
|
||||
endpoint_a_type = url_kwargs.get('endpoint_a_type')
|
||||
endpoint_a_id = url_kwargs.get('endpoint_a_id')
|
||||
try:
|
||||
model = apps.get_model(
|
||||
app_label='dcim',
|
||||
model_name=endpoint_a_type
|
||||
)
|
||||
obj.endpoint_a = model.objects.get(pk=endpoint_a_id)
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404
|
||||
return obj
|
||||
|
||||
|
||||
#
|
||||
# Inventory items
|
||||
#
|
||||
|
@ -93,7 +93,10 @@ $(document).ready(function() {
|
||||
var rendered_url = api_url;
|
||||
while (match = filter_regex.exec(api_url)) {
|
||||
var filter_field = $('#id_' + match[1]);
|
||||
if (filter_field.val()) {
|
||||
var custom_attr = $('option:selected', filter_field).attr('api-value');
|
||||
if (custom_attr) {
|
||||
rendered_url = rendered_url.replace(match[0], custom_attr);
|
||||
} else if (filter_field.val()) {
|
||||
rendered_url = rendered_url.replace(match[0], filter_field.val());
|
||||
} else if (filter_field.attr('nullable') == 'true') {
|
||||
rendered_url = rendered_url.replace(match[0], '0');
|
||||
|
106
netbox/templates/dcim/cable_connect.html
Normal file
106
netbox/templates/dcim/cable_connect.html
Normal file
@ -0,0 +1,106 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load static from staticfiles %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block content %}
|
||||
<form action="." method="post" class="form form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading"><strong>Errors</strong></div>
|
||||
<div class="panel-body">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% with endpoint_a=form.instance.endpoint_a %}
|
||||
<h3>{% block title %}Connect {{ endpoint_a.device }} {{ endpoint_a }}{% endblock %}</h3>
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading text-center">
|
||||
<strong>A Side</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label required">Site</label>
|
||||
<div class="col-md-9">
|
||||
<p class="form-control-static">{{ endpoint_a.device.site }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label required">Rack</label>
|
||||
<div class="col-md-9">
|
||||
<p class="form-control-static">{{ endpoint_a.device.rack|default:"None" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label required">Device</label>
|
||||
<div class="col-md-9">
|
||||
<p class="form-control-static">{{ endpoint_a.device }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label required">Name</label>
|
||||
<div class="col-md-9">
|
||||
<p class="form-control-static">{{ endpoint_a }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2 text-center" style="padding-top: 90px;">
|
||||
<i class="fa fa-exchange fa-4x"></i>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading text-center">
|
||||
<strong>B Side</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#search" aria-controls="search" role="tab" data-toggle="tab">Search</a></li>
|
||||
<li role="presentation"><a href="#select" aria-controls="home" role="tab" data-toggle="tab">Select</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="search">
|
||||
{% render_field form.livesearch %}
|
||||
</div>
|
||||
<div class="tab-pane" id="select">
|
||||
{% render_field form.endpoint_b_site %}
|
||||
{% render_field form.endpoint_b_rack %}
|
||||
{% render_field form.endpoint_b_device %}
|
||||
</div>
|
||||
</div>
|
||||
{% render_field form.endpoint_b_type %}
|
||||
{% render_field form.endpoint_b_id %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
{% render_field form.status %}
|
||||
{% render_field form.label %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-md-12 text-center">
|
||||
<button type="submit" name="_update" class="btn btn-primary">Connect</button>
|
||||
<a href="{{ return_url }}" class="btn btn-default">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'js/livesearch.js' %}?v{{ settings.VERSION }}"></script>
|
||||
{% endblock %}
|
@ -30,7 +30,7 @@
|
||||
<i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:consoleport_connect' pk=cp.pk %}" title="Connect" class="btn btn-success btn-xs">
|
||||
<a href="{% url 'dcim:consoleport_connect' endpoint_a_id=cp.pk %}" title="Connect" class="btn btn-success btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -35,7 +35,7 @@
|
||||
<i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:consoleserverport_connect' pk=csp.pk %}" title="Connect" class="btn btn-success btn-xs">
|
||||
<a href="{% url 'dcim:consoleserverport_connect' endpoint_a_id=csp.pk %}" title="Connect" class="btn btn-success btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -35,7 +35,7 @@
|
||||
<i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:poweroutlet_connect' pk=po.pk %}" title="Connect" class="btn btn-success btn-xs">
|
||||
<a href="{% url 'dcim:poweroutlet_connect' endpoint_a_id=po.pk %}" title="Connect" class="btn btn-success btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -30,7 +30,7 @@
|
||||
<i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:powerport_connect' pk=pp.pk %}" title="Connect" class="btn btn-success btn-xs">
|
||||
<a href="{% url 'dcim:powerport_connect' endpoint_a_id=pp.pk %}" title="Connect" class="btn btn-success btn-xs">
|
||||
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -207,6 +207,15 @@ class SelectWithPK(forms.Select):
|
||||
option_template_name = 'widgets/select_option_with_pk.html'
|
||||
|
||||
|
||||
class ContentTypeSelect(forms.Select):
|
||||
"""
|
||||
Appends an `api-value` attribute equal to the slugified model name for each ContentType. For example:
|
||||
<option value="37" api-value="console-server-port">console server port</option>
|
||||
This attribute can be used to reference the relevant API endpoint for a particular ContentType.
|
||||
"""
|
||||
option_template_name = 'widgets/select_contenttype.html'
|
||||
|
||||
|
||||
class ArrayFieldSelectMultiple(SelectWithDisabled, forms.SelectMultiple):
|
||||
"""
|
||||
MultiSelect widget for a SimpleArrayField. Choices must be populated on the widget.
|
||||
|
@ -0,0 +1 @@
|
||||
<option value="{{ widget.value }}"{% include "django/forms/widgets/attrs.html" %}{% if widget.value %} api-value="{{ widget.label|slugify }}"{% endif %}>{{ widget.label.label|default:widget.label }}</option>
|
Loading…
Reference in New Issue
Block a user