Merge branch 'develop-2.9' into 2006-scripts-reports-background

This commit is contained in:
John Anderson 2020-07-06 02:06:53 -04:00
commit 4a74927fa2
31 changed files with 1135 additions and 1101 deletions

View File

@ -19,6 +19,7 @@ NetBox v2.9 replaces Django's built-in permissions framework with one that suppo
* [#4793](https://github.com/netbox-community/netbox/issues/4793) - Add `description` field to device component templates
* [#4795](https://github.com/netbox-community/netbox/issues/4795) - Add bulk disconnect capability for console and power ports
* [#4807](https://github.com/netbox-community/netbox/issues/4807) - Add bulk edit ability for device bay templates
* [#4817](https://github.com/netbox-community/netbox/issues/4817) - Standardize device/VM component `name` field to 64 characters
### Configuration Changes

View File

@ -1,4 +1,5 @@
from django.contrib.contenttypes.models import ContentType
from django.test import override_settings
from django.urls import reverse
from circuits.choices import *
@ -45,6 +46,7 @@ class ProviderTest(APIViewTestCases.APIViewTestCase):
)
Provider.objects.bulk_create(providers)
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_get_provider_graphs(self):
"""
Test retrieval of Graphs assigned to Providers.

View File

@ -311,7 +311,7 @@ class RearPortTemplateSerializer(ValidatedModelSerializer):
class Meta:
model = RearPortTemplate
fields = ['id', 'device_type', 'name', 'type', 'positions', 'description']
fields = ['id', 'device_type', 'name', 'label', 'type', 'positions', 'description']
class FrontPortTemplateSerializer(ValidatedModelSerializer):
@ -321,7 +321,7 @@ class FrontPortTemplateSerializer(ValidatedModelSerializer):
class Meta:
model = FrontPortTemplate
fields = ['id', 'device_type', 'name', 'type', 'rear_port', 'rear_port_position', 'description']
fields = ['id', 'device_type', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description']
class DeviceBayTemplateSerializer(ValidatedModelSerializer):
@ -559,7 +559,7 @@ class RearPortSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
class Meta:
model = RearPort
fields = ['id', 'device', 'name', 'type', 'positions', 'description', 'cable', 'tags']
fields = ['id', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'tags']
class FrontPortRearPortSerializer(WritableNestedSerializer):
@ -570,7 +570,7 @@ class FrontPortRearPortSerializer(WritableNestedSerializer):
class Meta:
model = RearPort
fields = ['id', 'url', 'name']
fields = ['id', 'url', 'name', 'label']
class FrontPortSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
@ -581,7 +581,9 @@ class FrontPortSerializer(TaggedObjectSerializer, ValidatedModelSerializer):
class Meta:
model = FrontPort
fields = ['id', 'device', 'name', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'tags']
fields = [
'id', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'tags',
]
class DeviceBaySerializer(TaggedObjectSerializer, ValidatedModelSerializer):

View File

@ -384,28 +384,28 @@ class DeviceTypeFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFil
)
def _console_ports(self, queryset, name, value):
return queryset.exclude(consoleport_templates__isnull=value)
return queryset.exclude(consoleporttemplates__isnull=value)
def _console_server_ports(self, queryset, name, value):
return queryset.exclude(consoleserverport_templates__isnull=value)
return queryset.exclude(consoleserverporttemplates__isnull=value)
def _power_ports(self, queryset, name, value):
return queryset.exclude(powerport_templates__isnull=value)
return queryset.exclude(powerporttemplates__isnull=value)
def _power_outlets(self, queryset, name, value):
return queryset.exclude(poweroutlet_templates__isnull=value)
return queryset.exclude(poweroutlettemplates__isnull=value)
def _interfaces(self, queryset, name, value):
return queryset.exclude(interface_templates__isnull=value)
return queryset.exclude(interfacetemplates__isnull=value)
def _pass_through_ports(self, queryset, name, value):
return queryset.exclude(
frontport_templates__isnull=value,
rearport_templates__isnull=value
frontporttemplates__isnull=value,
rearporttemplates__isnull=value
)
def _device_bays(self, queryset, name, value):
return queryset.exclude(device_bay_templates__isnull=value)
return queryset.exclude(devicebaytemplates__isnull=value)
class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet):
@ -656,7 +656,7 @@ class DeviceFilterSet(
return queryset.filter(
Q(name__icontains=value) |
Q(serial__icontains=value.strip()) |
Q(inventory_items__serial__icontains=value.strip()) |
Q(inventoryitems__serial__icontains=value.strip()) |
Q(asset_tag__icontains=value.strip()) |
Q(comments__icontains=value)
).distinct()
@ -698,7 +698,7 @@ class DeviceFilterSet(
)
def _device_bays(self, queryset, name, value):
return queryset.exclude(device_bays__isnull=value)
return queryset.exclude(devicebays__isnull=value)
class DeviceComponentFilterSet(django_filters.FilterSet):
@ -747,6 +747,7 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(label__icontains=value) |
Q(description__icontains=value)
)

View File

@ -59,7 +59,6 @@ def get_device_by_name_or_pk(name):
class DeviceComponentFilterForm(BootstrapMixin, forms.Form):
field_order = [
'q', 'region', 'site'
]
@ -127,7 +126,11 @@ class InterfaceCommonForm:
})
class LabeledComponentForm(BootstrapMixin, forms.Form):
class ComponentForm(BootstrapMixin, forms.Form):
"""
Subclass this form when facilitating the creation of one or more device component or component templates based on
a name pattern.
"""
name_pattern = ExpandableNameField(
label='Name'
)
@ -1033,7 +1036,7 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm):
# Device component templates
#
class ComponentTemplateCreateForm(LabeledComponentForm):
class ComponentTemplateCreateForm(ComponentForm):
"""
Base form for the creation of device component templates.
"""
@ -1350,7 +1353,7 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = FrontPortTemplate
fields = [
'device_type', 'name', 'type', 'rear_port', 'rear_port_position', 'description',
'device_type', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description',
]
widgets = {
'device_type': forms.HiddenInput(),
@ -1389,7 +1392,7 @@ class FrontPortTemplateCreateForm(ComponentTemplateCreateForm):
# Determine which rear port positions are occupied. These will be excluded from the list of available mappings.
occupied_port_positions = [
(front_port.rear_port_id, front_port.rear_port_position)
for front_port in device_type.frontport_templates.all()
for front_port in device_type.frontporttemplates.all()
]
# Populate rear port choices
@ -1430,6 +1433,10 @@ class FrontPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
queryset=FrontPortTemplate.objects.all(),
widget=forms.MultipleHiddenInput()
)
label = forms.CharField(
max_length=64,
required=False
)
type = forms.ChoiceField(
choices=add_blank_choice(PortTypeChoices),
required=False,
@ -1448,7 +1455,7 @@ class RearPortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = RearPortTemplate
fields = [
'device_type', 'name', 'type', 'positions', 'description',
'device_type', 'name', 'label', 'type', 'positions', 'description',
]
widgets = {
'device_type': forms.HiddenInput(),
@ -1474,6 +1481,10 @@ class RearPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
queryset=RearPortTemplate.objects.all(),
widget=forms.MultipleHiddenInput()
)
label = forms.CharField(
max_length=64,
required=False
)
type = forms.ChoiceField(
choices=add_blank_choice(PortTypeChoices),
required=False,
@ -2248,7 +2259,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
# Device components
#
class ComponentCreateForm(LabeledComponentForm):
class ComponentCreateForm(ComponentForm):
"""
Base form for the creation of device components.
"""
@ -2261,7 +2272,7 @@ class ComponentCreateForm(LabeledComponentForm):
)
class DeviceBulkAddComponentForm(LabeledComponentForm):
class DeviceBulkAddComponentForm(ComponentForm):
pk = forms.ModelMultipleChoiceField(
queryset=Device.objects.all(),
widget=forms.MultipleHiddenInput()
@ -2967,13 +2978,12 @@ class InterfaceCSVForm(CSVModelForm):
super().__init__(*args, **kwargs)
# Limit LAG choices to interfaces belonging to this device (or VC master)
device = None
if self.is_bound and 'device' in self.data:
try:
device = self.fields['device'].to_python(self.data['device'])
except forms.ValidationError:
device = None
else:
device = self.instance.device
pass
if device:
self.fields['lag'].queryset = Interface.objects.filter(
@ -3013,7 +3023,7 @@ class FrontPortForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = FrontPort
fields = [
'device', 'name', 'type', 'rear_port', 'rear_port_position', 'description', 'tags',
'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'tags',
]
widgets = {
'device': forms.HiddenInput(),
@ -3094,14 +3104,14 @@ class FrontPortCreateForm(ComponentCreateForm):
# class FrontPortBulkCreateForm(
# form_from_model(FrontPort, ['type', 'description', 'tags']),
# form_from_model(FrontPort, ['label', 'type', 'description', 'tags']),
# DeviceBulkAddComponentForm
# ):
# pass
class FrontPortBulkEditForm(
form_from_model(FrontPort, ['type', 'description']),
form_from_model(FrontPort, ['label', 'type', 'description']),
BootstrapMixin,
AddRemoveTagsForm,
BulkEditForm
@ -3112,9 +3122,7 @@ class FrontPortBulkEditForm(
)
class Meta:
nullable_fields = [
'description',
]
nullable_fields = ('label', 'description')
class FrontPortCSVForm(CSVModelForm):
@ -3185,7 +3193,7 @@ class RearPortForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = RearPort
fields = [
'device', 'name', 'type', 'positions', 'description', 'tags',
'device', 'name', 'label', 'type', 'positions', 'description', 'tags',
]
widgets = {
'device': forms.HiddenInput(),
@ -3210,14 +3218,14 @@ class RearPortCreateForm(ComponentCreateForm):
class RearPortBulkCreateForm(
form_from_model(RearPort, ['type', 'positions', 'description', 'tags']),
form_from_model(RearPort, ['label', 'type', 'positions', 'description', 'tags']),
DeviceBulkAddComponentForm
):
pass
class RearPortBulkEditForm(
form_from_model(RearPort, ['type', 'description']),
form_from_model(RearPort, ['label', 'type', 'description']),
BootstrapMixin,
AddRemoveTagsForm,
BulkEditForm
@ -3228,9 +3236,7 @@ class RearPortBulkEditForm(
)
class Meta:
nullable_fields = [
'description',
]
nullable_fields = ('label', 'description')
class RearPortCSVForm(CSVModelForm):
@ -3392,17 +3398,11 @@ class InventoryItemForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = InventoryItem
fields = [
'name', 'device', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'tags',
'name', 'label', 'device', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'tags',
]
class InventoryItemCreateForm(BootstrapMixin, forms.Form):
device = DynamicModelChoiceField(
queryset=Device.objects.prefetch_related('device_type__manufacturer')
)
name_pattern = ExpandableNameField(
label='Name'
)
class InventoryItemCreateForm(ComponentCreateForm):
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
@ -3443,7 +3443,7 @@ class InventoryItemCSVForm(CSVModelForm):
class InventoryItemBulkCreateForm(
form_from_model(InventoryItem, ['manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'tags']),
form_from_model(InventoryItem, ['label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'tags']),
DeviceBulkAddComponentForm
):
tags = DynamicModelMultipleChoiceField(
@ -3452,68 +3452,27 @@ class InventoryItemBulkCreateForm(
)
class InventoryItemBulkEditForm(BootstrapMixin, BulkEditForm):
class InventoryItemBulkEditForm(
form_from_model(InventoryItem, ['label', 'manufacturer', 'part_id', 'description']),
BootstrapMixin,
AddRemoveTagsForm,
BulkEditForm
):
pk = forms.ModelMultipleChoiceField(
queryset=InventoryItem.objects.all(),
widget=forms.MultipleHiddenInput()
)
device = DynamicModelChoiceField(
queryset=Device.objects.all(),
required=False
)
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
)
part_id = forms.CharField(
max_length=50,
required=False,
label='Part ID'
)
description = forms.CharField(
max_length=100,
required=False
)
class Meta:
nullable_fields = [
'manufacturer', 'part_id', 'description',
]
nullable_fields = ('label', 'manufacturer', 'part_id', 'description')
class InventoryItemFilterForm(BootstrapMixin, forms.Form):
class InventoryItemFilterForm(DeviceComponentFilterForm):
model = InventoryItem
q = forms.CharField(
required=False,
label='Search'
)
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
to_field_name='slug',
required=False,
widget=APISelectMultiple(
value_field="slug",
filter_for={
'site': 'region'
}
)
)
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
required=False,
widget=APISelectMultiple(
value_field="slug",
filter_for={
'device_id': 'site'
}
)
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
label='Device'
)
manufacturer = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
to_field_name='slug',
@ -3522,6 +3481,12 @@ class InventoryItemFilterForm(BootstrapMixin, forms.Form):
value_field="slug",
)
)
serial = forms.CharField(
required=False
)
asset_tag = forms.CharField(
required=False
)
discovered = forms.NullBooleanField(
required=False,
widget=StaticSelect2(

View File

@ -1,5 +1,3 @@
# Generated by Django 3.0.7 on 2020-06-04 20:37
from django.db import migrations, models
@ -10,16 +8,6 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AddField(
model_name='interface',
name='label',
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='interfacetemplate',
name='label',
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='consoleport',
name='label',
@ -40,6 +28,41 @@ class Migration(migrations.Migration):
name='label',
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='devicebay',
name='label',
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='devicebaytemplate',
name='label',
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='frontport',
name='label',
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='frontporttemplate',
name='label',
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='interface',
name='label',
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='interfacetemplate',
name='label',
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='inventoryitem',
name='label',
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='poweroutlet',
name='label',
@ -61,12 +84,12 @@ class Migration(migrations.Migration):
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='devicebay',
model_name='rearport',
name='label',
field=models.CharField(blank=True, max_length=64),
),
migrations.AddField(
model_name='devicebaytemplate',
model_name='rearporttemplate',
name='label',
field=models.CharField(blank=True, max_length=64),
),

View File

@ -1,6 +1,5 @@
# Generated by Django 3.0.6 on 2020-06-22 16:03
from django.db import migrations
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
@ -15,4 +14,11 @@ class Migration(migrations.Migration):
model_name='interface',
name='virtual_machine',
),
# device is now a required field
migrations.AlterField(
model_name='interface',
name='device',
field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='dcim.Device'),
preserve_default=False,
),
]

View File

@ -0,0 +1,120 @@
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0111_component_template_description'),
]
operations = [
# Set max_length=64 for all name fields
migrations.AlterField(
model_name='consoleport',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='consoleporttemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='consoleserverport',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='consoleserverporttemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='devicebay',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='devicebaytemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='inventoryitem',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='poweroutlet',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='poweroutlettemplate',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='powerport',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='powerporttemplate',
name='name',
field=models.CharField(max_length=64),
),
# Update related_name for necessary component and component template models
migrations.AlterField(
model_name='consoleporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleporttemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='consoleserverporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverporttemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='devicebay',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='devicebays', to='dcim.Device'),
),
migrations.AlterField(
model_name='devicebaytemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='devicebaytemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='frontporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontporttemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='interfacetemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfacetemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='inventoryitem',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventoryitems', to='dcim.Device'),
),
migrations.AlterField(
model_name='poweroutlettemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlettemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='powerporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='powerporttemplates', to='dcim.DeviceType'),
),
migrations.AlterField(
model_name='rearporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rearporttemplates', to='dcim.DeviceType'),
),
]

View File

@ -678,7 +678,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
'device_type__manufacturer',
'device_role'
).annotate(
devicebay_count=Count('device_bays')
devicebay_count=Count('devicebays')
).exclude(
pk=exclude
).filter(
@ -1049,23 +1049,23 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
))
# Component templates
if self.consoleport_templates.exists():
if self.consoleporttemplates.exists():
data['console-ports'] = [
{
'name': c.name,
'type': c.type,
}
for c in self.consoleport_templates.all()
for c in self.consoleporttemplates.all()
]
if self.consoleserverport_templates.exists():
if self.consoleserverporttemplates.exists():
data['console-server-ports'] = [
{
'name': c.name,
'type': c.type,
}
for c in self.consoleserverport_templates.all()
for c in self.consoleserverporttemplates.all()
]
if self.powerport_templates.exists():
if self.powerporttemplates.exists():
data['power-ports'] = [
{
'name': c.name,
@ -1073,9 +1073,9 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
'maximum_draw': c.maximum_draw,
'allocated_draw': c.allocated_draw,
}
for c in self.powerport_templates.all()
for c in self.powerporttemplates.all()
]
if self.poweroutlet_templates.exists():
if self.poweroutlettemplates.exists():
data['power-outlets'] = [
{
'name': c.name,
@ -1083,18 +1083,18 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
'power_port': c.power_port.name if c.power_port else None,
'feed_leg': c.feed_leg,
}
for c in self.poweroutlet_templates.all()
for c in self.poweroutlettemplates.all()
]
if self.interface_templates.exists():
if self.interfacetemplates.exists():
data['interfaces'] = [
{
'name': c.name,
'type': c.type,
'mgmt_only': c.mgmt_only,
}
for c in self.interface_templates.all()
for c in self.interfacetemplates.all()
]
if self.frontport_templates.exists():
if self.frontporttemplates.exists():
data['front-ports'] = [
{
'name': c.name,
@ -1102,23 +1102,23 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
'rear_port': c.rear_port.name,
'rear_port_position': c.rear_port_position,
}
for c in self.frontport_templates.all()
for c in self.frontporttemplates.all()
]
if self.rearport_templates.exists():
if self.rearporttemplates.exists():
data['rear-ports'] = [
{
'name': c.name,
'type': c.type,
'positions': c.positions,
}
for c in self.rearport_templates.all()
for c in self.rearporttemplates.all()
]
if self.device_bay_templates.exists():
if self.devicebaytemplates.exists():
data['device-bays'] = [
{
'name': c.name,
}
for c in self.device_bay_templates.all()
for c in self.devicebaytemplates.all()
]
return yaml.dump(dict(data), sort_keys=False)
@ -1159,7 +1159,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
if (
self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT
) and self.device_bay_templates.count():
) and self.devicebaytemplates.count():
raise ValidationError({
'subdevice_role': "Must delete all device bay templates associated with this device before "
"declassifying it as a parent device."
@ -1634,28 +1634,28 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
# If this is a new Device, instantiate all of the related components per the DeviceType definition
if is_new:
ConsolePort.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.consoleport_templates.unrestricted()]
[x.instantiate(self) for x in self.device_type.consoleporttemplates.unrestricted()]
)
ConsoleServerPort.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.consoleserverport_templates.unrestricted()]
[x.instantiate(self) for x in self.device_type.consoleserverporttemplates.unrestricted()]
)
PowerPort.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.powerport_templates.unrestricted()]
[x.instantiate(self) for x in self.device_type.powerporttemplates.unrestricted()]
)
PowerOutlet.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.poweroutlet_templates.unrestricted()]
[x.instantiate(self) for x in self.device_type.poweroutlettemplates.unrestricted()]
)
Interface.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.interface_templates.unrestricted()]
[x.instantiate(self) for x in self.device_type.interfacetemplates.unrestricted()]
)
RearPort.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.rearport_templates.unrestricted()]
[x.instantiate(self) for x in self.device_type.rearporttemplates.unrestricted()]
)
FrontPort.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.frontport_templates.unrestricted()]
[x.instantiate(self) for x in self.device_type.frontporttemplates.unrestricted()]
)
DeviceBay.objects.bulk_create(
[x.instantiate(self) for x in self.device_type.device_bay_templates.unrestricted()]
[x.instantiate(self) for x in self.device_type.devicebaytemplates.unrestricted()]
)
# Update Site and Rack assignment for any child Devices

View File

@ -27,6 +27,24 @@ __all__ = (
class ComponentTemplateModel(models.Model):
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='%(class)ss'
)
name = models.CharField(
max_length=64
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
description = models.CharField(
max_length=200,
blank=True
@ -68,24 +86,6 @@ class ConsolePortTemplate(ComponentTemplateModel):
"""
A template for a ConsolePort to be created for a new Device.
"""
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='consoleport_templates'
)
name = models.CharField(
max_length=50
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
type = models.CharField(
max_length=50,
choices=ConsolePortTypeChoices,
@ -108,24 +108,6 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
"""
A template for a ConsoleServerPort to be created for a new Device.
"""
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='consoleserverport_templates'
)
name = models.CharField(
max_length=50
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
type = models.CharField(
max_length=50,
choices=ConsolePortTypeChoices,
@ -148,24 +130,6 @@ class PowerPortTemplate(ComponentTemplateModel):
"""
A template for a PowerPort to be created for a new Device.
"""
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='powerport_templates'
)
name = models.CharField(
max_length=50
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
type = models.CharField(
max_length=50,
choices=PowerPortTypeChoices,
@ -202,24 +166,6 @@ class PowerOutletTemplate(ComponentTemplateModel):
"""
A template for a PowerOutlet to be created for a new Device.
"""
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='poweroutlet_templates'
)
name = models.CharField(
max_length=50
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
type = models.CharField(
max_length=50,
choices=PowerOutletTypeChoices,
@ -269,25 +215,13 @@ class InterfaceTemplate(ComponentTemplateModel):
"""
A template for a physical data interface on a new Device.
"""
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='interface_templates'
)
name = models.CharField(
max_length=64
)
# Override ComponentTemplateModel._name to specify naturalize_interface function
_name = NaturalOrderingField(
target_field='name',
naturalize_function=naturalize_interface,
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
type = models.CharField(
max_length=50,
choices=InterfaceTypeChoices
@ -314,19 +248,6 @@ class FrontPortTemplate(ComponentTemplateModel):
"""
Template for a pass-through port on the front of a new Device.
"""
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='frontport_templates'
)
name = models.CharField(
max_length=64
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
type = models.CharField(
max_length=50,
choices=PortTypeChoices
@ -348,9 +269,6 @@ class FrontPortTemplate(ComponentTemplateModel):
('rear_port', 'rear_port_position'),
)
def __str__(self):
return self.name
def clean(self):
# Validate rear port assignment
@ -385,19 +303,6 @@ class RearPortTemplate(ComponentTemplateModel):
"""
Template for a pass-through port on the rear of a new Device.
"""
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='rearport_templates'
)
name = models.CharField(
max_length=64
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
type = models.CharField(
max_length=50,
choices=PortTypeChoices
@ -411,9 +316,6 @@ class RearPortTemplate(ComponentTemplateModel):
ordering = ('device_type', '_name')
unique_together = ('device_type', 'name')
def __str__(self):
return self.name
def instantiate(self, device):
return RearPort(
device=device,
@ -427,25 +329,6 @@ class DeviceBayTemplate(ComponentTemplateModel):
"""
A template for a DeviceBay to be created for a new parent Device.
"""
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
related_name='device_bay_templates'
)
name = models.CharField(
max_length=50
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
class Meta:
ordering = ('device_type', '_name')
unique_together = ('device_type', 'name')

View File

@ -36,6 +36,24 @@ __all__ = (
class ComponentModel(models.Model):
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='%(class)ss'
)
name = models.CharField(
max_length=64
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
description = models.CharField(
max_length=200,
blank=True
@ -233,24 +251,6 @@ class ConsolePort(CableTermination, ComponentModel):
"""
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
"""
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='consoleports'
)
name = models.CharField(
max_length=50
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
type = models.CharField(
max_length=50,
choices=ConsolePortTypeChoices,
@ -270,7 +270,7 @@ class ConsolePort(CableTermination, ComponentModel):
)
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'type', 'description']
csv_headers = ['device', 'name', 'label', 'type', 'description']
class Meta:
ordering = ('device', '_name')
@ -283,6 +283,7 @@ class ConsolePort(CableTermination, ComponentModel):
return (
self.device.identifier,
self.name,
self.label,
self.type,
self.description,
)
@ -297,24 +298,6 @@ class ConsoleServerPort(CableTermination, ComponentModel):
"""
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
"""
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='consoleserverports'
)
name = models.CharField(
max_length=50
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
type = models.CharField(
max_length=50,
choices=ConsolePortTypeChoices,
@ -327,7 +310,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
)
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'type', 'description']
csv_headers = ['device', 'name', 'label', 'type', 'description']
class Meta:
ordering = ('device', '_name')
@ -340,6 +323,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
return (
self.device.identifier,
self.name,
self.label,
self.type,
self.description,
)
@ -354,24 +338,6 @@ class PowerPort(CableTermination, ComponentModel):
"""
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
"""
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='powerports'
)
name = models.CharField(
max_length=50
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
type = models.CharField(
max_length=50,
choices=PowerPortTypeChoices,
@ -410,7 +376,7 @@ class PowerPort(CableTermination, ComponentModel):
)
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'type', 'maximum_draw', 'allocated_draw', 'description']
csv_headers = ['device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description']
class Meta:
ordering = ('device', '_name')
@ -423,6 +389,7 @@ class PowerPort(CableTermination, ComponentModel):
return (
self.device.identifier,
self.name,
self.label,
self.get_type_display(),
self.maximum_draw,
self.allocated_draw,
@ -519,24 +486,6 @@ class PowerOutlet(CableTermination, ComponentModel):
"""
A physical power outlet (output) within a Device which provides power to a PowerPort.
"""
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='poweroutlets'
)
name = models.CharField(
max_length=50
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
type = models.CharField(
max_length=50,
choices=PowerOutletTypeChoices,
@ -562,7 +511,7 @@ class PowerOutlet(CableTermination, ComponentModel):
)
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'type', 'power_port', 'feed_leg', 'description']
csv_headers = ['device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description']
class Meta:
ordering = ('device', '_name')
@ -575,6 +524,7 @@ class PowerOutlet(CableTermination, ComponentModel):
return (
self.device.identifier,
self.name,
self.label,
self.get_type_display(),
self.power_port.name if self.power_port else None,
self.get_feed_leg_display(),
@ -595,15 +545,9 @@ class PowerOutlet(CableTermination, ComponentModel):
#
class BaseInterface(models.Model):
name = models.CharField(
max_length=64
)
_name = NaturalOrderingField(
target_field='name',
naturalize_function=naturalize_interface,
max_length=100,
blank=True
)
"""
Abstract base class for fields shared by dcim.Interface and virtualization.VMInterface.
"""
enabled = models.BooleanField(
default=True
)
@ -633,18 +577,13 @@ class Interface(CableTermination, ComponentModel, BaseInterface):
"""
A network interface within a Device. A physical Interface can connect to exactly one other Interface.
"""
device = models.ForeignKey(
to='Device',
on_delete=models.CASCADE,
related_name='interfaces',
null=True,
# Override ComponentModel._name to specify naturalize_interface function
_name = NaturalOrderingField(
target_field='name',
naturalize_function=naturalize_interface,
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
_connected_interface = models.OneToOneField(
to='self',
on_delete=models.SET_NULL,
@ -703,7 +642,7 @@ class Interface(CableTermination, ComponentModel, BaseInterface):
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'device', 'name', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only', 'description', 'mode',
'device', 'name', 'label', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only', 'description', 'mode',
]
class Meta:
@ -717,6 +656,7 @@ class Interface(CableTermination, ComponentModel, BaseInterface):
return (
self.device.identifier if self.device else None,
self.name,
self.label,
self.lag.name if self.lag else None,
self.get_type_display(),
self.enabled,
@ -849,19 +789,6 @@ class FrontPort(CableTermination, ComponentModel):
"""
A pass-through port on the front of a Device.
"""
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='frontports'
)
name = models.CharField(
max_length=64
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
type = models.CharField(
max_length=50,
choices=PortTypeChoices
@ -877,7 +804,7 @@ class FrontPort(CableTermination, ComponentModel):
)
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'type', 'rear_port', 'rear_port_position', 'description']
csv_headers = ['device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description']
class Meta:
ordering = ('device', '_name')
@ -886,9 +813,6 @@ class FrontPort(CableTermination, ComponentModel):
('rear_port', 'rear_port_position'),
)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('dcim:frontport', kwargs={'pk': self.pk})
@ -896,6 +820,7 @@ class FrontPort(CableTermination, ComponentModel):
return (
self.device.identifier,
self.name,
self.label,
self.get_type_display(),
self.rear_port.name,
self.rear_port_position,
@ -924,19 +849,6 @@ class RearPort(CableTermination, ComponentModel):
"""
A pass-through port on the rear of a Device.
"""
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='rearports'
)
name = models.CharField(
max_length=64
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
type = models.CharField(
max_length=50,
choices=PortTypeChoices
@ -947,15 +859,12 @@ class RearPort(CableTermination, ComponentModel):
)
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'type', 'positions', 'description']
csv_headers = ['device', 'name', 'label', 'type', 'positions', 'description']
class Meta:
ordering = ('device', '_name')
unique_together = ('device', 'name')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('dcim:rearport', kwargs={'pk': self.pk})
@ -963,6 +872,7 @@ class RearPort(CableTermination, ComponentModel):
return (
self.device.identifier,
self.name,
self.label,
self.get_type_display(),
self.positions,
self.description,
@ -978,25 +888,6 @@ class DeviceBay(ComponentModel):
"""
An empty space within a Device which can house a child device
"""
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='device_bays'
)
name = models.CharField(
max_length=50,
verbose_name='Name'
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
installed_device = models.OneToOneField(
to='dcim.Device',
on_delete=models.SET_NULL,
@ -1006,17 +897,12 @@ class DeviceBay(ComponentModel):
)
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'installed_device', 'description']
csv_headers = ['device', 'name', 'label', 'installed_device', 'description']
class Meta:
ordering = ('device', '_name')
unique_together = ('device', 'name')
def __str__(self):
if self.label:
return '{} - {} ({})'.format(self.device.name, self.name, self.label)
return '{} - {}'.format(self.device.name, self.name)
def get_absolute_url(self):
return reverse('dcim:devicebay', kwargs={'pk': self.pk})
@ -1024,6 +910,7 @@ class DeviceBay(ComponentModel):
return (
self.device.identifier,
self.name,
self.label,
self.installed_device.identifier if self.installed_device else None,
self.description,
)
@ -1061,11 +948,6 @@ class InventoryItem(ComponentModel):
An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply.
InventoryItems are used only for inventory purposes.
"""
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='inventory_items'
)
parent = models.ForeignKey(
to='self',
on_delete=models.CASCADE,
@ -1073,15 +955,6 @@ class InventoryItem(ComponentModel):
blank=True,
null=True
)
name = models.CharField(
max_length=50,
verbose_name='Name'
)
_name = NaturalOrderingField(
target_field='name',
max_length=100,
blank=True
)
manufacturer = models.ForeignKey(
to='dcim.Manufacturer',
on_delete=models.PROTECT,
@ -1116,16 +989,13 @@ class InventoryItem(ComponentModel):
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description',
'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description',
]
class Meta:
ordering = ('device__id', 'parent__id', '_name')
unique_together = ('device', 'parent', 'name')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('dcim:inventoryitem', kwargs={'pk': self.pk})
@ -1133,6 +1003,7 @@ class InventoryItem(ComponentModel):
return (
self.device.name or '{{{}}}'.format(self.device.pk),
self.name,
self.label,
self.manufacturer.name if self.manufacturer else None,
self.part_id,
self.serial,

View File

@ -110,21 +110,6 @@ POWERPANEL_POWERFEED_COUNT = """
"""
def get_component_template_actions(model_name):
return """
{{% if perms.dcim.change_{model_name} %}}
<a href="{{% url 'dcim:{model_name}_edit' pk=record.pk %}}?return_url={{{{ request.path }}}}" class="btn btn-xs btn-warning">
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
</a>
{{% endif %}}
{{% if perms.dcim.delete_{model_name} %}}
<a href="{{% url 'dcim:{model_name}_delete' pk=record.pk %}}?return_url={{{{ request.path }}}}" class="btn btn-xs btn-danger">
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
</a>
{{% endif %}}
""".format(model_name=model_name).strip()
#
# Regions
#
@ -401,10 +386,9 @@ class ComponentTemplateTable(BaseTable):
class ConsolePortTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn(
template_code=get_component_template_actions('consoleporttemplate'),
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
actions = ButtonsColumn(
model=ConsolePortTemplate,
buttons=('edit', 'delete')
)
class Meta(BaseTable.Meta):
@ -414,10 +398,9 @@ class ConsolePortTemplateTable(ComponentTemplateTable):
class ConsoleServerPortTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn(
template_code=get_component_template_actions('consoleserverporttemplate'),
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
actions = ButtonsColumn(
model=ConsoleServerPortTemplate,
buttons=('edit', 'delete')
)
class Meta(BaseTable.Meta):
@ -427,10 +410,9 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable):
class PowerPortTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn(
template_code=get_component_template_actions('powerporttemplate'),
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
actions = ButtonsColumn(
model=PowerPortTemplate,
buttons=('edit', 'delete')
)
class Meta(BaseTable.Meta):
@ -440,10 +422,9 @@ class PowerPortTemplateTable(ComponentTemplateTable):
class PowerOutletTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn(
template_code=get_component_template_actions('poweroutlettemplate'),
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
actions = ButtonsColumn(
model=PowerOutletTemplate,
buttons=('edit', 'delete')
)
class Meta(BaseTable.Meta):
@ -456,10 +437,9 @@ class InterfaceTemplateTable(ComponentTemplateTable):
mgmt_only = BooleanColumn(
verbose_name='Management Only'
)
actions = tables.TemplateColumn(
template_code=get_component_template_actions('interfacetemplate'),
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
actions = ButtonsColumn(
model=InterfaceTemplate,
buttons=('edit', 'delete')
)
class Meta(BaseTable.Meta):
@ -472,10 +452,9 @@ class FrontPortTemplateTable(ComponentTemplateTable):
rear_port_position = tables.Column(
verbose_name='Position'
)
actions = tables.TemplateColumn(
template_code=get_component_template_actions('frontporttemplate'),
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
actions = ButtonsColumn(
model=FrontPortTemplate,
buttons=('edit', 'delete')
)
class Meta(BaseTable.Meta):
@ -485,10 +464,9 @@ class FrontPortTemplateTable(ComponentTemplateTable):
class RearPortTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn(
template_code=get_component_template_actions('rearporttemplate'),
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
actions = ButtonsColumn(
model=RearPortTemplate,
buttons=('edit', 'delete')
)
class Meta(BaseTable.Meta):
@ -498,10 +476,9 @@ class RearPortTemplateTable(ComponentTemplateTable):
class DeviceBayTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn(
template_code=get_component_template_actions('devicebaytemplate'),
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
actions = ButtonsColumn(
model=DeviceBayTemplate,
buttons=('edit', 'delete')
)
class Meta(BaseTable.Meta):
@ -784,9 +761,10 @@ class InventoryItemTable(DeviceComponentTable):
class Meta(DeviceComponentTable.Meta):
model = InventoryItem
fields = (
'pk', 'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered'
'pk', 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description',
'discovered',
)
default_columns = ('pk', 'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag')
default_columns = ('pk', 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag')
#

View File

@ -1,5 +1,6 @@
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.test import override_settings
from django.urls import reverse
from rest_framework import status
@ -131,6 +132,7 @@ class SiteTest(APIViewTestCases.APIViewTestCase):
},
]
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_get_site_graphs(self):
"""
Test retrieval of Graphs assigned to Sites.
@ -900,6 +902,7 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
},
]
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_get_device_graphs(self):
"""
Test retrieval of Graphs assigned to Devices.
@ -1156,6 +1159,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase
},
]
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_get_interface_graphs(self):
"""
Test retrieval of Graphs assigned to Devices.

View File

@ -4,6 +4,7 @@ import pytz
import yaml
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.test import override_settings
from django.urls import reverse
from netaddr import EUI
@ -376,6 +377,7 @@ class DeviceTypeTestCase(
'is_full_depth': False,
}
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_import_objects(self):
"""
Custom import test for YAML-based imports (versus CSV)
@ -479,45 +481,45 @@ device-bays:
self.assertEqual(dt.comments, 'test comment')
# Verify all of the components were created
self.assertEqual(dt.consoleport_templates.count(), 3)
self.assertEqual(dt.consoleporttemplates.count(), 3)
cp1 = ConsolePortTemplate.objects.first()
self.assertEqual(cp1.name, 'Console Port 1')
self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
self.assertEqual(dt.consoleserverport_templates.count(), 3)
self.assertEqual(dt.consoleserverporttemplates.count(), 3)
csp1 = ConsoleServerPortTemplate.objects.first()
self.assertEqual(csp1.name, 'Console Server Port 1')
self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
self.assertEqual(dt.powerport_templates.count(), 3)
self.assertEqual(dt.powerporttemplates.count(), 3)
pp1 = PowerPortTemplate.objects.first()
self.assertEqual(pp1.name, 'Power Port 1')
self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
self.assertEqual(dt.poweroutlet_templates.count(), 3)
self.assertEqual(dt.poweroutlettemplates.count(), 3)
po1 = PowerOutletTemplate.objects.first()
self.assertEqual(po1.name, 'Power Outlet 1')
self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
self.assertEqual(po1.power_port, pp1)
self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
self.assertEqual(dt.interface_templates.count(), 3)
self.assertEqual(dt.interfacetemplates.count(), 3)
iface1 = InterfaceTemplate.objects.first()
self.assertEqual(iface1.name, 'Interface 1')
self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
self.assertTrue(iface1.mgmt_only)
self.assertEqual(dt.rearport_templates.count(), 3)
self.assertEqual(dt.rearporttemplates.count(), 3)
rp1 = RearPortTemplate.objects.first()
self.assertEqual(rp1.name, 'Rear Port 1')
self.assertEqual(dt.frontport_templates.count(), 3)
self.assertEqual(dt.frontporttemplates.count(), 3)
fp1 = FrontPortTemplate.objects.first()
self.assertEqual(fp1.name, 'Front Port 1')
self.assertEqual(fp1.rear_port, rp1)
self.assertEqual(fp1.rear_port_position, 1)
self.assertEqual(dt.device_bay_templates.count(), 3)
self.assertEqual(dt.devicebaytemplates.count(), 3)
db1 = DeviceBayTemplate.objects.first()
self.assertEqual(db1.name, 'Device Bay 1')

View File

@ -98,6 +98,7 @@ urlpatterns = [
# Console port templates
path('console-port-templates/add/', views.ConsolePortTemplateCreateView.as_view(), name='consoleporttemplate_add'),
path('console-port-templates/edit/', views.ConsolePortTemplateBulkEditView.as_view(), name='consoleporttemplate_bulk_edit'),
path('console-port-templates/rename/', views.ConsolePortTemplateBulkRenameView.as_view(), name='consoleporttemplate_bulk_rename'),
path('console-port-templates/delete/', views.ConsolePortTemplateBulkDeleteView.as_view(), name='consoleporttemplate_bulk_delete'),
path('console-port-templates/<int:pk>/edit/', views.ConsolePortTemplateEditView.as_view(), name='consoleporttemplate_edit'),
path('console-port-templates/<int:pk>/delete/', views.ConsolePortTemplateDeleteView.as_view(), name='consoleporttemplate_delete'),
@ -105,6 +106,7 @@ urlpatterns = [
# Console server port templates
path('console-server-port-templates/add/', views.ConsoleServerPortTemplateCreateView.as_view(), name='consoleserverporttemplate_add'),
path('console-server-port-templates/edit/', views.ConsoleServerPortTemplateBulkEditView.as_view(), name='consoleserverporttemplate_bulk_edit'),
path('console-server-port-templates/rename/', views.ConsoleServerPortTemplateBulkRenameView.as_view(), name='consoleserverporttemplate_bulk_rename'),
path('console-server-port-templates/delete/', views.ConsoleServerPortTemplateBulkDeleteView.as_view(), name='consoleserverporttemplate_bulk_delete'),
path('console-server-port-templates/<int:pk>/edit/', views.ConsoleServerPortTemplateEditView.as_view(), name='consoleserverporttemplate_edit'),
path('console-server-port-templates/<int:pk>/delete/', views.ConsoleServerPortTemplateDeleteView.as_view(), name='consoleserverporttemplate_delete'),
@ -112,6 +114,7 @@ urlpatterns = [
# Power port templates
path('power-port-templates/add/', views.PowerPortTemplateCreateView.as_view(), name='powerporttemplate_add'),
path('power-port-templates/edit/', views.PowerPortTemplateBulkEditView.as_view(), name='powerporttemplate_bulk_edit'),
path('power-port-templates/rename/', views.PowerPortTemplateBulkRenameView.as_view(), name='powerporttemplate_bulk_rename'),
path('power-port-templates/delete/', views.PowerPortTemplateBulkDeleteView.as_view(), name='powerporttemplate_bulk_delete'),
path('power-port-templates/<int:pk>/edit/', views.PowerPortTemplateEditView.as_view(), name='powerporttemplate_edit'),
path('power-port-templates/<int:pk>/delete/', views.PowerPortTemplateDeleteView.as_view(), name='powerporttemplate_delete'),
@ -119,6 +122,7 @@ urlpatterns = [
# Power outlet templates
path('power-outlet-templates/add/', views.PowerOutletTemplateCreateView.as_view(), name='poweroutlettemplate_add'),
path('power-outlet-templates/edit/', views.PowerOutletTemplateBulkEditView.as_view(), name='poweroutlettemplate_bulk_edit'),
path('power-outlet-templates/rename/', views.PowerOutletTemplateBulkRenameView.as_view(), name='poweroutlettemplate_bulk_rename'),
path('power-outlet-templates/delete/', views.PowerOutletTemplateBulkDeleteView.as_view(), name='poweroutlettemplate_bulk_delete'),
path('power-outlet-templates/<int:pk>/edit/', views.PowerOutletTemplateEditView.as_view(), name='poweroutlettemplate_edit'),
path('power-outlet-templates/<int:pk>/delete/', views.PowerOutletTemplateDeleteView.as_view(), name='poweroutlettemplate_delete'),
@ -126,6 +130,7 @@ urlpatterns = [
# Interface templates
path('interface-templates/add/', views.InterfaceTemplateCreateView.as_view(), name='interfacetemplate_add'),
path('interface-templates/edit/', views.InterfaceTemplateBulkEditView.as_view(), name='interfacetemplate_bulk_edit'),
path('interface-templates/rename/', views.InterfaceTemplateBulkRenameView.as_view(), name='interfacetemplate_bulk_rename'),
path('interface-templates/delete/', views.InterfaceTemplateBulkDeleteView.as_view(), name='interfacetemplate_bulk_delete'),
path('interface-templates/<int:pk>/edit/', views.InterfaceTemplateEditView.as_view(), name='interfacetemplate_edit'),
path('interface-templates/<int:pk>/delete/', views.InterfaceTemplateDeleteView.as_view(), name='interfacetemplate_delete'),
@ -133,6 +138,7 @@ urlpatterns = [
# Front port templates
path('front-port-templates/add/', views.FrontPortTemplateCreateView.as_view(), name='frontporttemplate_add'),
path('front-port-templates/edit/', views.FrontPortTemplateBulkEditView.as_view(), name='frontporttemplate_bulk_edit'),
path('front-port-templates/rename/', views.FrontPortTemplateBulkRenameView.as_view(), name='frontporttemplate_bulk_rename'),
path('front-port-templates/delete/', views.FrontPortTemplateBulkDeleteView.as_view(), name='frontporttemplate_bulk_delete'),
path('front-port-templates/<int:pk>/edit/', views.FrontPortTemplateEditView.as_view(), name='frontporttemplate_edit'),
path('front-port-templates/<int:pk>/delete/', views.FrontPortTemplateDeleteView.as_view(), name='frontporttemplate_delete'),
@ -140,6 +146,7 @@ urlpatterns = [
# Rear port templates
path('rear-port-templates/add/', views.RearPortTemplateCreateView.as_view(), name='rearporttemplate_add'),
path('rear-port-templates/edit/', views.RearPortTemplateBulkEditView.as_view(), name='rearporttemplate_bulk_edit'),
path('rear-port-templates/rename/', views.RearPortTemplateBulkRenameView.as_view(), name='rearporttemplate_bulk_rename'),
path('rear-port-templates/delete/', views.RearPortTemplateBulkDeleteView.as_view(), name='rearporttemplate_bulk_delete'),
path('rear-port-templates/<int:pk>/edit/', views.RearPortTemplateEditView.as_view(), name='rearporttemplate_edit'),
path('rear-port-templates/<int:pk>/delete/', views.RearPortTemplateDeleteView.as_view(), name='rearporttemplate_delete'),
@ -147,6 +154,7 @@ urlpatterns = [
# Device bay templates
path('device-bay-templates/add/', views.DeviceBayTemplateCreateView.as_view(), name='devicebaytemplate_add'),
path('device-bay-templates/edit/', views.DeviceBayTemplateBulkEditView.as_view(), name='devicebaytemplate_bulk_edit'),
path('device-bay-templates/rename/', views.DeviceBayTemplateBulkRenameView.as_view(), name='devicebaytemplate_bulk_rename'),
path('device-bay-templates/delete/', views.DeviceBayTemplateBulkDeleteView.as_view(), name='devicebaytemplate_bulk_delete'),
path('device-bay-templates/<int:pk>/edit/', views.DeviceBayTemplateEditView.as_view(), name='devicebaytemplate_edit'),
path('device-bay-templates/<int:pk>/delete/', views.DeviceBayTemplateDeleteView.as_view(), name='devicebaytemplate_delete'),

View File

@ -640,6 +640,10 @@ class ConsolePortTemplateBulkEditView(BulkEditView):
form = forms.ConsolePortTemplateBulkEditForm
class ConsolePortTemplateBulkRenameView(BulkRenameView):
queryset = ConsolePortTemplate.objects.all()
class ConsolePortTemplateBulkDeleteView(BulkDeleteView):
queryset = ConsolePortTemplate.objects.all()
table = tables.ConsolePortTemplateTable
@ -671,6 +675,10 @@ class ConsoleServerPortTemplateBulkEditView(BulkEditView):
form = forms.ConsoleServerPortTemplateBulkEditForm
class ConsoleServerPortTemplateBulkRenameView(BulkRenameView):
queryset = ConsoleServerPortTemplate.objects.all()
class ConsoleServerPortTemplateBulkDeleteView(BulkDeleteView):
queryset = ConsoleServerPortTemplate.objects.all()
table = tables.ConsoleServerPortTemplateTable
@ -702,6 +710,10 @@ class PowerPortTemplateBulkEditView(BulkEditView):
form = forms.PowerPortTemplateBulkEditForm
class PowerPortTemplateBulkRenameView(BulkRenameView):
queryset = PowerPortTemplate.objects.all()
class PowerPortTemplateBulkDeleteView(BulkDeleteView):
queryset = PowerPortTemplate.objects.all()
table = tables.PowerPortTemplateTable
@ -733,6 +745,10 @@ class PowerOutletTemplateBulkEditView(BulkEditView):
form = forms.PowerOutletTemplateBulkEditForm
class PowerOutletTemplateBulkRenameView(BulkRenameView):
queryset = PowerOutletTemplate.objects.all()
class PowerOutletTemplateBulkDeleteView(BulkDeleteView):
queryset = PowerOutletTemplate.objects.all()
table = tables.PowerOutletTemplateTable
@ -764,6 +780,10 @@ class InterfaceTemplateBulkEditView(BulkEditView):
form = forms.InterfaceTemplateBulkEditForm
class InterfaceTemplateBulkRenameView(BulkRenameView):
queryset = InterfaceTemplate.objects.all()
class InterfaceTemplateBulkDeleteView(BulkDeleteView):
queryset = InterfaceTemplate.objects.all()
table = tables.InterfaceTemplateTable
@ -795,6 +815,10 @@ class FrontPortTemplateBulkEditView(BulkEditView):
form = forms.FrontPortTemplateBulkEditForm
class FrontPortTemplateBulkRenameView(BulkRenameView):
queryset = FrontPortTemplate.objects.all()
class FrontPortTemplateBulkDeleteView(BulkDeleteView):
queryset = FrontPortTemplate.objects.all()
table = tables.FrontPortTemplateTable
@ -826,6 +850,10 @@ class RearPortTemplateBulkEditView(BulkEditView):
form = forms.RearPortTemplateBulkEditForm
class RearPortTemplateBulkRenameView(BulkRenameView):
queryset = RearPortTemplate.objects.all()
class RearPortTemplateBulkDeleteView(BulkDeleteView):
queryset = RearPortTemplate.objects.all()
table = tables.RearPortTemplateTable
@ -857,6 +885,10 @@ class DeviceBayTemplateBulkEditView(BulkEditView):
form = forms.DeviceBayTemplateBulkEditForm
class DeviceBayTemplateBulkRenameView(BulkRenameView):
queryset = DeviceBayTemplate.objects.all()
class DeviceBayTemplateBulkDeleteView(BulkDeleteView):
queryset = DeviceBayTemplate.objects.all()
table = tables.DeviceBayTemplateTable
@ -952,7 +984,7 @@ class DeviceView(ObjectView):
vc_members = []
# Console ports
console_ports = ConsolePort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
consoleports = ConsolePort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
'connected_endpoint__device', 'cable',
)
@ -964,7 +996,7 @@ class DeviceView(ObjectView):
)
# Power ports
power_ports = PowerPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
powerports = PowerPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
'_connected_poweroutlet__device', 'cable',
)
@ -982,15 +1014,15 @@ class DeviceView(ObjectView):
)
# Front ports
front_ports = FrontPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
frontports = FrontPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
'rear_port', 'cable',
)
# Rear ports
rear_ports = RearPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related('cable')
rearports = RearPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related('cable')
# Device bays
device_bays = DeviceBay.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
devicebays = DeviceBay.objects.restrict(request.user, 'view').filter(device=device).prefetch_related(
'installed_device__device_type__manufacturer',
)
@ -1011,14 +1043,14 @@ class DeviceView(ObjectView):
return render(request, 'dcim/device.html', {
'device': device,
'console_ports': console_ports,
'consoleports': consoleports,
'consoleserverports': consoleserverports,
'power_ports': power_ports,
'powerports': powerports,
'poweroutlets': poweroutlets,
'interfaces': interfaces,
'device_bays': device_bays,
'front_ports': front_ports,
'rear_ports': rear_ports,
'devicebays': devicebays,
'frontports': frontports,
'rearports': rearports,
'services': services,
'secrets': secrets,
'vc_members': vc_members,

View File

@ -1,5 +1,6 @@
import base64
from django.test import override_settings
from django.urls import reverse
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
@ -96,6 +97,7 @@ class SecretTestCase(
self.session_key = SessionKey(userkey=userkey)
self.session_key.save(master_key)
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_import_objects(self):
self.add_permissions('secrets.add_secret')

View File

@ -101,7 +101,7 @@
</li>
<li role="presentation"{% if active_tab == 'inventory' %} class="active"{% endif %}>
<a href="{% url 'dcim:device_inventory' pk=device.pk %}">
Inventory <span class="badge">{{ device.inventory_items.count }}</span>
Inventory <span class="badge">{{ device.inventoryitems.unrestricted.count }}</span>
</a>
</li>
{% if perms.dcim.napalm_read_device %}
@ -329,86 +329,6 @@
{% plugin_left_page device %}
</div>
<div class="col-md-6">
{% if console_ports %}
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Console Ports</strong>
</div>
<table class="table table-hover panel-body component-list">
{% for cp in console_ports %}
{% include 'dcim/inc/consoleport.html' %}
{% endfor %}
</table>
<div class="panel-footer noprint">
{% if console_ports and perms.dcim.change_consoleport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if console_ports and perms.dcim.delete_consoleport %}
<button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if console_ports and perms.dcim.add_consoleport %}
<div class="pull-right">
<a href="{% url 'dcim:consoleport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
</a>
</div>
{% endif %}
</div>
</div>
</form>
{% endif %}
{% if power_ports %}
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Power Ports</strong>
</div>
<table class="table table-hover panel-body component-list">
{% for pp in power_ports %}
{% include 'dcim/inc/powerport.html' %}
{% endfor %}
</table>
<div class="panel-footer noprint">
{% if power_ports and perms.dcim.change_powerport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:powerport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:powerport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if power_ports and perms.dcim.delete_powerport %}
<button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if power_ports and perms.dcim.add_powerport %}
<div class="pull-right">
<a href="{% url 'dcim:powerport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power port
</a>
</div>
{% endif %}
</div>
</div>
</form>
{% endif %}
{% if power_ports and poweroutlets %}
<div class="panel panel-default">
<div class="panel-heading">
@ -554,355 +474,490 @@
</div>
<div class="row">
<div class="col-md-12">
{% if device_bays or device.device_type.is_parent_device %}
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Device Bays</strong>
</div>
<table class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Status</th>
<th>Description</th>
<th colspan="2">Installed Device</th>
<th></th>
</tr>
</thead>
<tbody>
{% for devicebay in device_bays %}
{% include 'dcim/inc/devicebay.html' %}
{% empty %}
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active">
<a href="#interfaces" role="tab" data-toggle="tab">Interfaces {% badge interfaces|length %}</a>
</li>
<li role="presentation">
<a href="#frontports" role="tab" data-toggle="tab">Front Ports {% badge frontports|length %}</a>
</li>
<li role="presentation">
<a href="#rearports" role="tab" data-toggle="tab">Rear Ports {% badge rearports|length %}</a>
</li>
<li role="presentation">
<a href="#consoleports" role="tab" data-toggle="tab">Console Ports {% badge consoleports|length %}</a>
</li>
<li role="presentation">
<a href="#consoleserverports" role="tab" data-toggle="tab">Console Server Ports {% badge consoleserverports|length %}</a>
</li>
<li role="presentation">
<a href="#powerports" role="tab" data-toggle="tab">Power Ports {% badge powerports|length %}</a>
</li>
<li role="presentation">
<a href="#poweroutlets" role="tab" data-toggle="tab">Power Outlets {% badge poweroutlets|length %}</a>
</li>
<li role="presentation">
<a href="#devicebays" role="tab" data-toggle="tab">Device Bays {% badge devicebays|length %}</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="interfaces">
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Interfaces</strong>
<div class="pull-right noprint">
<button class="btn btn-default btn-xs toggle-ips" selected="selected">
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
</button>
</div>
<div class="col-md-2 pull-right noprint">
<input class="form-control interface-filter" type="text" placeholder="Filter" title="Filter text (regular expressions supported)" style="height: 23px" />
</div>
</div>
<table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
<td colspan="5" class="text-center text-muted">&mdash; No device bays defined &mdash;</td>
{% if perms.dcim.change_interface or perms.dcim.delete_interface %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>LAG</th>
<th>Description</th>
<th>MTU</th>
<th>Mode</th>
<th>Cable</th>
<th colspan="2">Connection</th>
<th></th>
</tr>
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if device_bays and perms.dcim.change_devicebay %}
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
{% endif %}
{% if device_bays and perms.dcim.delete_devicebay %}
<button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
</button>
{% endif %}
{% if perms.dcim.add_devicebay %}
<div class="pull-right">
<a href="{% url 'dcim:devicebay_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add device bays
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
</div>
</form>
{% endif %}
{% if interfaces %}
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Interfaces</strong>
<div class="pull-right noprint">
<button class="btn btn-default btn-xs toggle-ips" selected="selected">
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
</button>
</thead>
<tbody>
{% for iface in interfaces %}
{% include 'dcim/inc/interface.html' %}
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if interfaces and perms.dcim.change_interface %}
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
{% endif %}
{% if interfaces and perms.dcim.change_interface %}
<button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if interfaces and perms.dcim.delete_interface %}
<button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_interface %}
<div class="pull-right">
<a href="{% url 'dcim:interface_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
</div>
</form>
</div>
<div role="tabpanel" class="tab-pane" id="frontports">
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Front Ports</strong>
</div>
<div class="col-md-2 pull-right noprint">
<input class="form-control interface-filter" type="text" placeholder="Filter" title="Filter text (regular expressions supported)" style="height: 23px" />
<table class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_frontport or perms.dcim.delete_frontport %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Type</th>
<th>Rear Port</th>
<th>Position</th>
<th>Description</th>
<th>Cable</th>
<th colspan="2">Connection</th>
<th></th>
</tr>
</thead>
<tbody>
{% for frontport in frontports %}
{% include 'dcim/inc/frontport.html' %}
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if frontports and perms.dcim.change_frontport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:frontport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:frontport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if frontports and perms.dcim.delete_frontport %}
<button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_frontport %}
<div class="pull-right">
<a href="{% url 'dcim:frontport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add front ports
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
</div>
<table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_interface or perms.dcim.delete_interface %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>LAG</th>
<th>Description</th>
<th>MTU</th>
<th>Mode</th>
<th>Cable</th>
<th colspan="2">Connection</th>
<th></th>
</tr>
</thead>
<tbody>
{% for iface in interfaces %}
{% include 'dcim/inc/interface.html' %}
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if interfaces and perms.dcim.change_interface %}
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
{% endif %}
{% if interfaces and perms.dcim.change_interface %}
<button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if interfaces and perms.dcim.delete_interface %}
<button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_interface %}
<div class="pull-right">
<a href="{% url 'dcim:interface_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
</div>
</form>
{% endif %}
{% if consoleserverports %}
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Console Server Ports</strong>
</form>
</div>
<div role="tabpanel" class="tab-pane" id="rearports">
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Rear Ports</strong>
</div>
<table class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_rearport or perms.dcim.delete_rearport %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Type</th>
<th>Positions</th>
<th>Description</th>
<th>Cable</th>
<th colspan="2">Connection</th>
<th></th>
</tr>
</thead>
<tbody>
{% for rearport in rearports %}
{% include 'dcim/inc/rearport.html' %}
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if rearports and perms.dcim.change_rearport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:rearport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:rearport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if rearports and perms.dcim.delete_rearport %}
<button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_rearport %}
<div class="pull-right">
<a href="{% url 'dcim:rearport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add rear ports
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
</div>
<table class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
</form>
</div>
<div role="tabpanel" class="tab-pane" id="consoleports">
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Console Ports</strong>
</div>
<table class="table table-hover panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_consoleport or perms.dcim.delete_consoleport %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Cable</th>
<th colspan="2">Connection</th>
<th></th>
</tr>
</thead>
{% for cp in consoleports %}
{% include 'dcim/inc/consoleport.html' %}
{% endfor %}
</table>
<div class="panel-footer noprint">
{% if consoleports and perms.dcim.change_consoleport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if consoleports and perms.dcim.delete_consoleport %}
<button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if consoleports and perms.dcim.add_consoleport %}
<div class="pull-right">
<a href="{% url 'dcim:consoleport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
</a>
</div>
{% endif %}
</div>
</div>
</form>
</div>
<div role="tabpanel" class="tab-pane" id="consoleserverports">
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Console Server Ports</strong>
</div>
<table class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Cable</th>
<th colspan="2">Connection</th>
<th></th>
</tr>
</thead>
<tbody>
{% for csp in consoleserverports %}
{% include 'dcim/inc/consoleserverport.html' %}
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if consoleserverports and perms.dcim.change_consoleport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleserverport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if consoleserverports and perms.dcim.delete_consoleserverport %}
<button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_consoleserverport %}
<div class="pull-right">
<a href="{% url 'dcim:consoleserverport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console server ports
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
</div>
</form>
</div>
<div role="tabpanel" class="tab-pane" id="powerports">
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Power Ports</strong>
</div>
<table class="table table-hover panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_consoleport or perms.dcim.delete_consoleport %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Type</th>
<th>Draw</th>
<th>Description</th>
<th>Cable</th>
<th colspan="2">Connection</th>
<th></th>
</tr>
</thead>
{% for pp in powerports %}
{% include 'dcim/inc/powerport.html' %}
{% endfor %}
</table>
<div class="panel-footer noprint">
{% if powerports and perms.dcim.change_powerport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:powerport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:powerport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:powerport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if powerports and perms.dcim.delete_powerport %}
<button type="submit" name="_delete" formaction="{% url 'dcim:powerport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if powerports and perms.dcim.add_powerport %}
<div class="pull-right">
<a href="{% url 'dcim:powerport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power port
</a>
</div>
{% endif %}
</div>
</div>
</form>
</div>
<div role="tabpanel" class="tab-pane" id="poweroutlets">
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Power Outlets</strong>
</div>
<table class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Cable</th>
<th colspan="2">Connection</th>
<th></th>
</tr>
</thead>
<tbody>
{% for csp in consoleserverports %}
{% include 'dcim/inc/consoleserverport.html' %}
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if consoleserverports and perms.dcim.change_consoleport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleserverport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if consoleserverports and perms.dcim.delete_consoleserverport %}
<button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_consoleserverport %}
<div class="pull-right">
<a href="{% url 'dcim:consoleserverport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console server ports
</a>
</div>
<div class="clearfix"></div>
{% endif %}
<th>Input/Leg</th>
<th>Description</th>
<th>Cable</th>
<th colspan="3">Connection</th>
<th></th>
</tr>
</thead>
<tbody>
{% for po in poweroutlets %}
{% include 'dcim/inc/poweroutlet.html' %}
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if poweroutlets and perms.dcim.change_powerport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:poweroutlet_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if poweroutlets and perms.dcim.delete_poweroutlet %}
<button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_poweroutlet %}
<div class="pull-right">
<a href="{% url 'dcim:poweroutlet_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power outlets
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
</div>
</div>
</form>
{% endif %}
{% if poweroutlets %}
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Power Outlets</strong>
</form>
</div>
<div role="tabpanel" class="tab-pane" id="devicebays">
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Device Bays</strong>
</div>
<table class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Status</th>
<th>Description</th>
<th colspan="2">Installed Device</th>
<th></th>
</tr>
</thead>
<tbody>
{% for devicebay in devicebays %}
{% include 'dcim/inc/devicebay.html' %}
{% empty %}
<tr>
<td colspan="5" class="text-center text-muted">&mdash; No device bays defined &mdash;</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if devicebays and perms.dcim.change_devicebay %}
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
{% endif %}
{% if devicebays and perms.dcim.delete_devicebay %}
<button type="submit" formaction="{% url 'dcim:devicebay_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
</button>
{% endif %}
{% if perms.dcim.add_devicebay %}
<div class="pull-right">
<a href="{% url 'dcim:devicebay_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add device bays
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
</div>
<table class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Type</th>
<th>Input/Leg</th>
<th>Description</th>
<th>Cable</th>
<th colspan="3">Connection</th>
<th></th>
</tr>
</thead>
<tbody>
{% for po in poweroutlets %}
{% include 'dcim/inc/poweroutlet.html' %}
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if poweroutlets and perms.dcim.change_powerport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:poweroutlet_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if poweroutlets and perms.dcim.delete_poweroutlet %}
<button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_poweroutlet %}
<div class="pull-right">
<a href="{% url 'dcim:poweroutlet_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power outlets
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
</div>
</form>
{% endif %}
{% if front_ports %}
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Front Ports</strong>
</div>
<table class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_frontport or perms.dcim.delete_frontport %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Type</th>
<th>Rear Port</th>
<th>Position</th>
<th>Description</th>
<th>Cable</th>
<th colspan="2">Connection</th>
<th></th>
</tr>
</thead>
<tbody>
{% for frontport in front_ports %}
{% include 'dcim/inc/frontport.html' %}
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if front_ports and perms.dcim.change_frontport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:frontport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:frontport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:frontport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if front_ports and perms.dcim.delete_frontport %}
<button type="submit" formaction="{% url 'dcim:frontport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_frontport %}
<div class="pull-right">
<a href="{% url 'dcim:frontport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add front ports
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
</div>
</form>
{% endif %}
{% if rear_ports %}
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Rear Ports</strong>
</div>
<table class="table table-hover table-headings panel-body component-list">
<thead>
<tr>
{% if perms.dcim.change_rearport or perms.dcim.delete_rearport %}
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Type</th>
<th>Positions</th>
<th>Description</th>
<th>Cable</th>
<th colspan="2">Connection</th>
<th></th>
</tr>
</thead>
<tbody>
{% for rearport in rear_ports %}
{% include 'dcim/inc/rearport.html' %}
{% endfor %}
</tbody>
</table>
<div class="panel-footer noprint">
{% if rear_ports and perms.dcim.change_rearport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:rearport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:rearport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:rearport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if rear_ports and perms.dcim.delete_rearport %}
<button type="submit" formaction="{% url 'dcim:rearport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_rearport %}
<div class="pull-right">
<a href="{% url 'dcim:rearport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add rear ports
</a>
</div>
<div class="clearfix"></div>
{% endif %}
</div>
</div>
</form>
{% endif %}
</form>
</div>
</div>
</div>
</div>
{% include 'inc/modal.html' with name='graphs' title='Graphs' %}

View File

@ -63,149 +63,157 @@
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Chassis</strong>
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Chassis</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Manufacturer</td>
<td><a href="{% url 'dcim:devicetype_list' %}?manufacturer={{ devicetype.manufacturer.slug }}">{{ devicetype.manufacturer }}</a></td>
</tr>
<tr>
<td>Model Name</td>
<td>
{{ devicetype.model }}<br/>
<small class="text-muted">{{ devicetype.slug }}</small>
</td>
</tr>
<tr>
<td>Part Number</td>
<td>{{ devicetype.part_number|placeholder }}</td>
</tr>
<tr>
<td>Height (U)</td>
<td>{{ devicetype.u_height }}</td>
</tr>
<tr>
<td>Full Depth</td>
<td>
{% if devicetype.is_full_depth %}
<i class="glyphicon glyphicon-ok text-success" title="Yes"></i>
{% else %}
<i class="glyphicon glyphicon-remove text-danger" title="No"></i>
{% endif %}
</td>
</tr>
<tr>
<td>Parent/Child</td>
<td>
{{ devicetype.get_subdevice_role_display|placeholder }}
</td>
</tr>
<tr>
<td>Front Image</td>
<td>
{% if devicetype.front_image %}
<a href="{{ devicetype.front_image.url }}">
<img src="{{ devicetype.front_image.url }}" alt="{{ devicetype.front_image.name }}" class="img-responsive" />
</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</td>
</tr>
<tr>
<td>Rear Image</td>
<td>
{% if devicetype.rear_image %}
<a href="{{ devicetype.rear_image.url }}">
<img src="{{ devicetype.rear_image.url }}" alt="{{ devicetype.rear_image.name }}" class="img-responsive" />
</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</td>
</tr>
<tr>
<td>Instances</td>
<td><a href="{% url 'dcim:device_list' %}?device_type_id={{ devicetype.pk }}">{{ instance_count }}</a></td>
</tr>
</table>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Manufacturer</td>
<td><a href="{% url 'dcim:devicetype_list' %}?manufacturer={{ devicetype.manufacturer.slug }}">{{ devicetype.manufacturer }}</a></td>
</tr>
<tr>
<td>Model Name</td>
<td>
{{ devicetype.model }}<br/>
<small class="text-muted">{{ devicetype.slug }}</small>
</td>
</tr>
<tr>
<td>Part Number</td>
<td>{{ devicetype.part_number|placeholder }}</td>
</tr>
<tr>
<td>Height (U)</td>
<td>{{ devicetype.u_height }}</td>
</tr>
<tr>
<td>Full Depth</td>
<td>
{% if devicetype.is_full_depth %}
<i class="glyphicon glyphicon-ok text-success" title="Yes"></i>
{% else %}
<i class="glyphicon glyphicon-remove text-danger" title="No"></i>
{% endif %}
</td>
</tr>
<tr>
<td>Parent/Child</td>
<td>
{{ devicetype.get_subdevice_role_display|placeholder }}
</td>
</tr>
<tr>
<td>Front Image</td>
<td>
{% if devicetype.front_image %}
<a href="{{ devicetype.front_image.url }}">
<img src="{{ devicetype.front_image.url }}" alt="{{ devicetype.front_image.name }}" class="img-responsive" />
</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</td>
</tr>
<tr>
<td>Rear Image</td>
<td>
{% if devicetype.rear_image %}
<a href="{{ devicetype.rear_image.url }}">
<img src="{{ devicetype.rear_image.url }}" alt="{{ devicetype.rear_image.name }}" class="img-responsive" />
</a>
{% else %}
<span class="text-muted">&mdash;</span>
{% endif %}
</td>
</tr>
<tr>
<td>Instances</td>
<td><a href="{% url 'dcim:device_list' %}?device_type_id={{ devicetype.pk }}">{{ instance_count }}</a></td>
</tr>
</table>
{% plugin_left_page devicetype %}
</div>
{% plugin_left_page devicetype %}
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' with obj=devicetype %}
{% include 'extras/inc/tags_panel.html' with tags=devicetype.tags.all url='dcim:devicetype_list' %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Comments</strong>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' with obj=devicetype %}
{% include 'extras/inc/tags_panel.html' with tags=devicetype.tags.all url='dcim:devicetype_list' %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Comments</strong>
</div>
<div class="panel-body rendered-markdown">
{% if devicetype.comments %}
{{ devicetype.comments|render_markdown }}
{% else %}
<span class="text-muted">None</span>
{% endif %}
</div>
</div>
<div class="panel-body rendered-markdown">
{% if devicetype.comments %}
{{ devicetype.comments|render_markdown }}
{% else %}
<span class="text-muted">None</span>
{% endif %}
{% plugin_right_page devicetype %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page devicetype %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active">
<a href="#interfaces" role="tab" data-toggle="tab">Interfaces {% badge interface_table.rows|length %}</a>
</li>
<li role="presentation">
<a href="#frontports" role="tab" data-toggle="tab">Front Ports {% badge front_port_table_table.rows|length %}</a>
</li>
<li role="presentation">
<a href="#rearports" role="tab" data-toggle="tab">Rear Ports {% badge rear_port_table.rows|length %}</a>
</li>
<li role="presentation">
<a href="#consoleports" role="tab" data-toggle="tab">Console Ports {% badge consoleport_table.rows|length %}</a>
</li>
<li role="presentation">
<a href="#consoleserverports" role="tab" data-toggle="tab">Console Server Ports {% badge consoleserverport_table.rows|length %}</a>
</li>
<li role="presentation">
<a href="#powerports" role="tab" data-toggle="tab">Power Ports {% badge powerport_table.rows|length %}</a>
</li>
<li role="presentation">
<a href="#poweroutlets" role="tab" data-toggle="tab">Power Outlets {% badge poweroutlet_table.rows|length %}</a>
</li>
<li role="presentation">
<a href="#devicebays" role="tab" data-toggle="tab">Device Bays {% badge devicebay_table.rows|length %}</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="interfaces">
{% include 'dcim/inc/devicetype_component_table.html' with table=interface_table title='Interfaces' %}
</div>
<div role="tabpanel" class="tab-pane" id="frontports">
{% include 'dcim/inc/devicetype_component_table.html' with table=front_port_table title='Front Ports' %}
</div>
<div role="tabpanel" class="tab-pane" id="rearports">
{% include 'dcim/inc/devicetype_component_table.html' with table=rear_port_table title='Rear Ports' %}
</div>
<div role="tabpanel" class="tab-pane" id="consoleports">
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleport_table title='Console Ports' %}
</div>
<div role="tabpanel" class="tab-pane" id="consoleserverports">
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' %}
</div>
<div role="tabpanel" class="tab-pane" id="powerports">
{% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' %}
</div>
<div role="tabpanel" class="tab-pane" id="poweroutlets">
{% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' %}
</div>
<div role="tabpanel" class="tab-pane" id="devicebays">
{% include 'dcim/inc/devicetype_component_table.html' with table=devicebay_table title='Device Bays' %}
</div>
</div>
</div>
{% plugin_right_page devicetype %}
</div>
</div>
{% if devicetype.consoleport_templates.exists or devicetype.powerport_templates.exists %}
<div class="row">
<div class="col-md-6">
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleport_table title='Console Ports' add_url='dcim:consoleporttemplate_add' edit_url='dcim:consoleporttemplate_bulk_edit' delete_url='dcim:consoleporttemplate_bulk_delete' %}
</div>
<div class="col-md-6">
{% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' add_url='dcim:powerporttemplate_add' edit_url='dcim:powerporttemplate_bulk_edit' delete_url='dcim:powerporttemplate_bulk_delete' %}
</div>
</div>
{% endif %}
<div class="row">
<div class="col-md-12">
{% plugin_full_width_page devicetype %}
</div>
</div>
{% if devicetype.is_parent_device or devicebay_table.rows %}
<div class="row">
<div class="col-md-12">
{% include 'dcim/inc/devicetype_component_table.html' with table=devicebay_table title='Device Bays' add_url='dcim:devicebaytemplate_add' edit_url='dcim:devicebaytemplate_bulk_edit' delete_url='dcim:devicebaytemplate_bulk_delete' %}
</div>
</div>
{% endif %}
{% if devicetype.consoleserverport_templates.exists %}
<div class="row">
<div class="col-md-12">
{% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' add_url='dcim:consoleserverporttemplate_add' edit_url='dcim:consoleserverporttemplate_bulk_edit' delete_url='dcim:consoleserverporttemplate_bulk_delete' %}
</div>
</div>
{% endif %}
{% if devicetype.poweroutlet_templates.exists %}
<div class="row">
<div class="col-md-12">
{% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' add_url='dcim:poweroutlettemplate_add' edit_url='dcim:poweroutlettemplate_bulk_edit' delete_url='dcim:poweroutlettemplate_bulk_delete' %}
</div>
</div>
{% endif %}
{% if devicetype.interface_templates.exists %}
<div class="row">
<div class="col-md-12">
{% include 'dcim/inc/devicetype_component_table.html' with table=interface_table title='Interfaces' add_url='dcim:interfacetemplate_add' edit_url='dcim:interfacetemplate_bulk_edit' delete_url='dcim:interfacetemplate_bulk_delete' %}
</div>
</div>
{% endif %}
{% if devicetype.frontport_templates.exists or devicetype.rearport_templates.exists %}
<div class="row">
<div class="col-md-6">
{% include 'dcim/inc/devicetype_component_table.html' with table=front_port_table title='Front Ports' add_url='dcim:frontporttemplate_add' edit_url='dcim:frontporttemplate_bulk_edit' delete_url='dcim:frontporttemplate_bulk_delete' %}
</div>
<div class="col-md-6">
{% include 'dcim/inc/devicetype_component_table.html' with table=rear_port_table title='Rear Ports' add_url='dcim:rearporttemplate_add' edit_url='dcim:rearporttemplate_bulk_edit' delete_url='dcim:rearporttemplate_bulk_delete' %}
</div>
</div>
{% endif %}
{% endblock %}

View File

@ -18,8 +18,6 @@
{% if cp.type %}{{ cp.get_type_display }}{% else %}&mdash;{% endif %}
</td>
<td></td>
{# Description #}
<td>
{{ cp.description }}

View File

@ -0,0 +1,40 @@
{% load helpers %}
{% load perms %}
<form method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>{{ title }}</strong>
</div>
<table class="table table-hover panel-body component-list">
{% for obj in components %}
{% include component_template %}
{% endfor %}
</table>
<div class="panel-footer noprint">
{% if components and perms.dcim.change_consoleport %}
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url 'dcim:consoleport_bulk_edit' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</button>
{% endif %}
{% if components and perms.dcim.delete_consoleport %}
<button type="submit" name="_delete" formaction="{% url 'dcim:consoleport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if components and perms.dcim.add_consoleport %}
<div class="pull-right">
<a href="{% url 'dcim:consoleport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-xs btn-primary">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console port
</a>
</div>
{% endif %}
</div>
</div>
</form>

View File

@ -1,3 +1,4 @@
{% load helpers %}
{% if perms.dcim.change_devicetype %}
<form method="post">
{% csrf_token %}
@ -8,19 +9,18 @@
{% include 'responsive_table.html' %}
<div class="panel-footer noprint">
{% if table.rows %}
{% if edit_url %}
<button type="submit" name="_edit" formaction="{% url edit_url %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}" class="btn btn-xs btn-warning">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit Selected
</button>
{% endif %}
{% if delete_url %}
<button type="submit" name="_delete" formaction="{% url delete_url %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}" class="btn btn-xs btn-danger">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
</button>
{% endif %}
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_rename" %}?return_url={{ devicetype.get_absolute_url }}" class="btn btn-xs btn-warning">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button>
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_edit" %}?return_url={{ devicetype.get_absolute_url }}" class="btn btn-xs btn-warning">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
</button>
<button type="submit" name="_delete" formaction="{% url table.Meta.model|viewname:"bulk_delete" %}?return_url={{ devicetype.get_absolute_url }}" class="btn btn-xs btn-danger">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button>
{% endif %}
<div class="pull-right">
<a href="{% url add_url %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}" class="btn btn-primary btn-xs">
<a href="{% url table.Meta.model|viewname:"add" %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
Add {{ title }}
</a>

View File

@ -31,7 +31,7 @@
{% if iface.description %}
{{ iface.description }}<br/>
{% endif %}
{% for tag in iface.tags.all %}
{% for tag in iface.tags.all.unrestricted %}
{% tag tag %}
{% empty %}
{% if not iface.description %}&mdash;{% endif %}

View File

@ -4,7 +4,7 @@
<strong>Tags</strong>
</div>
<div class="panel-body">
{% for tag in tags.all %}
{% for tag in tags.all.unrestricted %}
{% tag tag url %}
{% empty %}
<span class="text-muted">No tags assigned</span>

View File

@ -0,0 +1 @@
{% if value or show_empty %}<span class="badge">{{ value }}</span>{% endif %}

View File

@ -130,25 +130,28 @@ class ButtonsColumn(tables.TemplateColumn):
:param model: Model class to use for calculating URL view names
:param prepend_content: Additional template content to render in the column (optional)
"""
buttons = ('changelog', 'edit', 'delete')
attrs = {'td': {'class': 'text-right text-nowrap noprint'}}
# Note that braces are escaped to allow for string formatting prior to template rendering
template_code = """
<a href="{{% url '{app_label}:{model_name}_changelog' {pk_field}=record.{pk_field} %}}" class="btn btn-default btn-xs" title="Change log">
<i class="fa fa-history"></i>
</a>
{{% if perms.{app_label}.change_{model_name} %}}
{{% if "changelog" in buttons %}}
<a href="{{% url '{app_label}:{model_name}_changelog' {pk_field}=record.{pk_field} %}}" class="btn btn-default btn-xs" title="Change log">
<i class="fa fa-history"></i>
</a>
{{% endif %}}
{{% if "edit" in buttons and perms.{app_label}.change_{model_name} %}}
<a href="{{% url '{app_label}:{model_name}_edit' {pk_field}=record.{pk_field} %}}?return_url={{{{ request.path }}}}" class="btn btn-xs btn-warning" title="Edit">
<i class="fa fa-pencil"></i>
</a>
{{% endif %}}
{{% if perms.{app_label}.delete_{model_name} %}}
{{% if "delete" in buttons and perms.{app_label}.delete_{model_name} %}}
<a href="{{% url '{app_label}:{model_name}_delete' {pk_field}=record.{pk_field} %}}?return_url={{{{ request.path }}}}" class="btn btn-xs btn-danger" title="Delete">
<i class="fa fa-trash"></i>
</a>
{{% endif %}}
"""
def __init__(self, model, *args, pk_field='pk', prepend_template=None, **kwargs):
def __init__(self, model, *args, pk_field='pk', buttons=None, prepend_template=None, **kwargs):
if prepend_template:
prepend_template = prepend_template.replace('{', '{{')
prepend_template = prepend_template.replace('}', '}}')
@ -157,11 +160,16 @@ class ButtonsColumn(tables.TemplateColumn):
template_code = self.template_code.format(
app_label=model._meta.app_label,
model_name=model._meta.model_name,
pk_field=pk_field
pk_field=pk_field,
buttons=buttons
)
super().__init__(template_code=template_code, *args, **kwargs)
self.extra_context.update({
'buttons': buttons or self.buttons,
})
def header(self):
return ''

View File

@ -242,3 +242,14 @@ def tag(tag, url_name=None):
'tag': tag,
'url_name': url_name,
}
@register.inclusion_tag('utilities/templatetags/badge.html')
def badge(value, show_empty=False):
"""
Display the specified number as a badge.
"""
return {
'value': value,
'show_empty': show_empty,
}

View File

@ -217,7 +217,7 @@ def prepare_cloned_fields(instance):
# Copy tags
if is_taggable(instance):
params['tags'] = ','.join([t.name for t in instance.tags.all()])
params['tags'] = ','.join([t.name for t in instance.tags.all().unrestricted()])
# Concatenate parameters into a URL query string
param_string = '&'.join(

View File

@ -51,8 +51,8 @@ def replicate_interfaces(apps, schema_editor):
# Verify that all interfaces have been replicated
assert replicated_count == original_interfaces.count(), "Replicated interfaces count does not match original count!"
# Delete original VM interfaces
original_interfaces.delete()
# Delete all interfaces not assigned to a Device
Interface.objects.filter(device__isnull=True).delete()
class Migration(migrations.Migration):

View File

@ -9,7 +9,9 @@ from dcim.choices import InterfaceModeChoices
from dcim.models import BaseInterface, Device
from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange, TaggedItem
from extras.utils import extras_features
from utilities.fields import NaturalOrderingField
from utilities.models import ChangeLoggedModel
from utilities.ordering import naturalize_interface
from utilities.query_functions import CollateAsChar
from utilities.querysets import RestrictedQuerySet
from utilities.utils import serialize_object
@ -387,6 +389,15 @@ class VMInterface(BaseInterface):
on_delete=models.CASCADE,
related_name='interfaces'
)
name = models.CharField(
max_length=64
)
_name = NaturalOrderingField(
target_field='name',
naturalize_function=naturalize_interface,
max_length=100,
blank=True
)
description = models.CharField(
max_length=200,
blank=True

View File

@ -1,4 +1,5 @@
from django.contrib.contenttypes.models import ContentType
from django.test import override_settings
from django.urls import reverse
from rest_framework import status
@ -244,7 +245,8 @@ class VMInterfaceTest(APIViewTestCases.APIViewTestCase):
},
]
def test_get_interface_graphs(self):
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_get_vminterface_graphs(self):
"""
Test retrieval of Graphs assigned to VM interfaces.
"""