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 * [#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 * [#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 * [#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 ### Configuration Changes

View File

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

View File

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

View File

@ -384,28 +384,28 @@ class DeviceTypeFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFil
) )
def _console_ports(self, queryset, name, value): 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): 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): 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): 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): 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): def _pass_through_ports(self, queryset, name, value):
return queryset.exclude( return queryset.exclude(
frontport_templates__isnull=value, frontporttemplates__isnull=value,
rearport_templates__isnull=value rearporttemplates__isnull=value
) )
def _device_bays(self, queryset, name, 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): class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet):
@ -656,7 +656,7 @@ class DeviceFilterSet(
return queryset.filter( return queryset.filter(
Q(name__icontains=value) | Q(name__icontains=value) |
Q(serial__icontains=value.strip()) | 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(asset_tag__icontains=value.strip()) |
Q(comments__icontains=value) Q(comments__icontains=value)
).distinct() ).distinct()
@ -698,7 +698,7 @@ class DeviceFilterSet(
) )
def _device_bays(self, queryset, name, value): 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): class DeviceComponentFilterSet(django_filters.FilterSet):
@ -747,6 +747,7 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
return queryset return queryset
return queryset.filter( return queryset.filter(
Q(name__icontains=value) | Q(name__icontains=value) |
Q(label__icontains=value) |
Q(description__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): class DeviceComponentFilterForm(BootstrapMixin, forms.Form):
field_order = [ field_order = [
'q', 'region', 'site' '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( name_pattern = ExpandableNameField(
label='Name' label='Name'
) )
@ -1033,7 +1036,7 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm):
# Device component templates # Device component templates
# #
class ComponentTemplateCreateForm(LabeledComponentForm): class ComponentTemplateCreateForm(ComponentForm):
""" """
Base form for the creation of device component templates. Base form for the creation of device component templates.
""" """
@ -1350,7 +1353,7 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta: class Meta:
model = FrontPortTemplate model = FrontPortTemplate
fields = [ fields = [
'device_type', 'name', 'type', 'rear_port', 'rear_port_position', 'description', 'device_type', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description',
] ]
widgets = { widgets = {
'device_type': forms.HiddenInput(), '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. # Determine which rear port positions are occupied. These will be excluded from the list of available mappings.
occupied_port_positions = [ occupied_port_positions = [
(front_port.rear_port_id, front_port.rear_port_position) (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 # Populate rear port choices
@ -1430,6 +1433,10 @@ class FrontPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
queryset=FrontPortTemplate.objects.all(), queryset=FrontPortTemplate.objects.all(),
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
label = forms.CharField(
max_length=64,
required=False
)
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(PortTypeChoices), choices=add_blank_choice(PortTypeChoices),
required=False, required=False,
@ -1448,7 +1455,7 @@ class RearPortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta: class Meta:
model = RearPortTemplate model = RearPortTemplate
fields = [ fields = [
'device_type', 'name', 'type', 'positions', 'description', 'device_type', 'name', 'label', 'type', 'positions', 'description',
] ]
widgets = { widgets = {
'device_type': forms.HiddenInput(), 'device_type': forms.HiddenInput(),
@ -1474,6 +1481,10 @@ class RearPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
queryset=RearPortTemplate.objects.all(), queryset=RearPortTemplate.objects.all(),
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
label = forms.CharField(
max_length=64,
required=False
)
type = forms.ChoiceField( type = forms.ChoiceField(
choices=add_blank_choice(PortTypeChoices), choices=add_blank_choice(PortTypeChoices),
required=False, required=False,
@ -2248,7 +2259,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
# Device components # Device components
# #
class ComponentCreateForm(LabeledComponentForm): class ComponentCreateForm(ComponentForm):
""" """
Base form for the creation of device components. Base form for the creation of device components.
""" """
@ -2261,7 +2272,7 @@ class ComponentCreateForm(LabeledComponentForm):
) )
class DeviceBulkAddComponentForm(LabeledComponentForm): class DeviceBulkAddComponentForm(ComponentForm):
pk = forms.ModelMultipleChoiceField( pk = forms.ModelMultipleChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
@ -2967,13 +2978,12 @@ class InterfaceCSVForm(CSVModelForm):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Limit LAG choices to interfaces belonging to this device (or VC master) # Limit LAG choices to interfaces belonging to this device (or VC master)
device = None
if self.is_bound and 'device' in self.data: if self.is_bound and 'device' in self.data:
try: try:
device = self.fields['device'].to_python(self.data['device']) device = self.fields['device'].to_python(self.data['device'])
except forms.ValidationError: except forms.ValidationError:
device = None pass
else:
device = self.instance.device
if device: if device:
self.fields['lag'].queryset = Interface.objects.filter( self.fields['lag'].queryset = Interface.objects.filter(
@ -3013,7 +3023,7 @@ class FrontPortForm(BootstrapMixin, forms.ModelForm):
class Meta: class Meta:
model = FrontPort model = FrontPort
fields = [ fields = [
'device', 'name', 'type', 'rear_port', 'rear_port_position', 'description', 'tags', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'tags',
] ]
widgets = { widgets = {
'device': forms.HiddenInput(), 'device': forms.HiddenInput(),
@ -3094,14 +3104,14 @@ class FrontPortCreateForm(ComponentCreateForm):
# class FrontPortBulkCreateForm( # class FrontPortBulkCreateForm(
# form_from_model(FrontPort, ['type', 'description', 'tags']), # form_from_model(FrontPort, ['label', 'type', 'description', 'tags']),
# DeviceBulkAddComponentForm # DeviceBulkAddComponentForm
# ): # ):
# pass # pass
class FrontPortBulkEditForm( class FrontPortBulkEditForm(
form_from_model(FrontPort, ['type', 'description']), form_from_model(FrontPort, ['label', 'type', 'description']),
BootstrapMixin, BootstrapMixin,
AddRemoveTagsForm, AddRemoveTagsForm,
BulkEditForm BulkEditForm
@ -3112,9 +3122,7 @@ class FrontPortBulkEditForm(
) )
class Meta: class Meta:
nullable_fields = [ nullable_fields = ('label', 'description')
'description',
]
class FrontPortCSVForm(CSVModelForm): class FrontPortCSVForm(CSVModelForm):
@ -3185,7 +3193,7 @@ class RearPortForm(BootstrapMixin, forms.ModelForm):
class Meta: class Meta:
model = RearPort model = RearPort
fields = [ fields = [
'device', 'name', 'type', 'positions', 'description', 'tags', 'device', 'name', 'label', 'type', 'positions', 'description', 'tags',
] ]
widgets = { widgets = {
'device': forms.HiddenInput(), 'device': forms.HiddenInput(),
@ -3210,14 +3218,14 @@ class RearPortCreateForm(ComponentCreateForm):
class RearPortBulkCreateForm( class RearPortBulkCreateForm(
form_from_model(RearPort, ['type', 'positions', 'description', 'tags']), form_from_model(RearPort, ['label', 'type', 'positions', 'description', 'tags']),
DeviceBulkAddComponentForm DeviceBulkAddComponentForm
): ):
pass pass
class RearPortBulkEditForm( class RearPortBulkEditForm(
form_from_model(RearPort, ['type', 'description']), form_from_model(RearPort, ['label', 'type', 'description']),
BootstrapMixin, BootstrapMixin,
AddRemoveTagsForm, AddRemoveTagsForm,
BulkEditForm BulkEditForm
@ -3228,9 +3236,7 @@ class RearPortBulkEditForm(
) )
class Meta: class Meta:
nullable_fields = [ nullable_fields = ('label', 'description')
'description',
]
class RearPortCSVForm(CSVModelForm): class RearPortCSVForm(CSVModelForm):
@ -3392,17 +3398,11 @@ class InventoryItemForm(BootstrapMixin, forms.ModelForm):
class Meta: class Meta:
model = InventoryItem model = InventoryItem
fields = [ 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): class InventoryItemCreateForm(ComponentCreateForm):
device = DynamicModelChoiceField(
queryset=Device.objects.prefetch_related('device_type__manufacturer')
)
name_pattern = ExpandableNameField(
label='Name'
)
manufacturer = DynamicModelChoiceField( manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
required=False required=False
@ -3443,7 +3443,7 @@ class InventoryItemCSVForm(CSVModelForm):
class InventoryItemBulkCreateForm( 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 DeviceBulkAddComponentForm
): ):
tags = DynamicModelMultipleChoiceField( 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( pk = forms.ModelMultipleChoiceField(
queryset=InventoryItem.objects.all(), queryset=InventoryItem.objects.all(),
widget=forms.MultipleHiddenInput() widget=forms.MultipleHiddenInput()
) )
device = DynamicModelChoiceField(
queryset=Device.objects.all(),
required=False
)
manufacturer = DynamicModelChoiceField( manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
required=False required=False
) )
part_id = forms.CharField(
max_length=50,
required=False,
label='Part ID'
)
description = forms.CharField(
max_length=100,
required=False
)
class Meta: class Meta:
nullable_fields = [ nullable_fields = ('label', 'manufacturer', 'part_id', 'description')
'manufacturer', 'part_id', 'description',
]
class InventoryItemFilterForm(BootstrapMixin, forms.Form): class InventoryItemFilterForm(DeviceComponentFilterForm):
model = InventoryItem 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( manufacturer = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(), queryset=Manufacturer.objects.all(),
to_field_name='slug', to_field_name='slug',
@ -3522,6 +3481,12 @@ class InventoryItemFilterForm(BootstrapMixin, forms.Form):
value_field="slug", value_field="slug",
) )
) )
serial = forms.CharField(
required=False
)
asset_tag = forms.CharField(
required=False
)
discovered = forms.NullBooleanField( discovered = forms.NullBooleanField(
required=False, required=False,
widget=StaticSelect2( 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 from django.db import migrations, models
@ -10,16 +8,6 @@ class Migration(migrations.Migration):
] ]
operations = [ 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( migrations.AddField(
model_name='consoleport', model_name='consoleport',
name='label', name='label',
@ -40,6 +28,41 @@ class Migration(migrations.Migration):
name='label', name='label',
field=models.CharField(blank=True, max_length=64), 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( migrations.AddField(
model_name='poweroutlet', model_name='poweroutlet',
name='label', name='label',
@ -61,12 +84,12 @@ class Migration(migrations.Migration):
field=models.CharField(blank=True, max_length=64), field=models.CharField(blank=True, max_length=64),
), ),
migrations.AddField( migrations.AddField(
model_name='devicebay', model_name='rearport',
name='label', name='label',
field=models.CharField(blank=True, max_length=64), field=models.CharField(blank=True, max_length=64),
), ),
migrations.AddField( migrations.AddField(
model_name='devicebaytemplate', model_name='rearporttemplate',
name='label', name='label',
field=models.CharField(blank=True, max_length=64), 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, models
import django.db.models.deletion
from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -15,4 +14,11 @@ class Migration(migrations.Migration):
model_name='interface', model_name='interface',
name='virtual_machine', 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_type__manufacturer',
'device_role' 'device_role'
).annotate( ).annotate(
devicebay_count=Count('device_bays') devicebay_count=Count('devicebays')
).exclude( ).exclude(
pk=exclude pk=exclude
).filter( ).filter(
@ -1049,23 +1049,23 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
)) ))
# Component templates # Component templates
if self.consoleport_templates.exists(): if self.consoleporttemplates.exists():
data['console-ports'] = [ data['console-ports'] = [
{ {
'name': c.name, 'name': c.name,
'type': c.type, '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'] = [ data['console-server-ports'] = [
{ {
'name': c.name, 'name': c.name,
'type': c.type, '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'] = [ data['power-ports'] = [
{ {
'name': c.name, 'name': c.name,
@ -1073,9 +1073,9 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
'maximum_draw': c.maximum_draw, 'maximum_draw': c.maximum_draw,
'allocated_draw': c.allocated_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'] = [ data['power-outlets'] = [
{ {
'name': c.name, 'name': c.name,
@ -1083,18 +1083,18 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
'power_port': c.power_port.name if c.power_port else None, 'power_port': c.power_port.name if c.power_port else None,
'feed_leg': c.feed_leg, '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'] = [ data['interfaces'] = [
{ {
'name': c.name, 'name': c.name,
'type': c.type, 'type': c.type,
'mgmt_only': c.mgmt_only, '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'] = [ data['front-ports'] = [
{ {
'name': c.name, 'name': c.name,
@ -1102,23 +1102,23 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
'rear_port': c.rear_port.name, 'rear_port': c.rear_port.name,
'rear_port_position': c.rear_port_position, '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'] = [ data['rear-ports'] = [
{ {
'name': c.name, 'name': c.name,
'type': c.type, 'type': c.type,
'positions': c.positions, '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'] = [ data['device-bays'] = [
{ {
'name': c.name, '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) return yaml.dump(dict(data), sort_keys=False)
@ -1159,7 +1159,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel):
if ( if (
self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT
) and self.device_bay_templates.count(): ) and self.devicebaytemplates.count():
raise ValidationError({ raise ValidationError({
'subdevice_role': "Must delete all device bay templates associated with this device before " 'subdevice_role': "Must delete all device bay templates associated with this device before "
"declassifying it as a parent device." "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 this is a new Device, instantiate all of the related components per the DeviceType definition
if is_new: if is_new:
ConsolePort.objects.bulk_create( 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( 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( 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( 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( 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( 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( 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( 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 # Update Site and Rack assignment for any child Devices

View File

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

View File

@ -36,6 +36,24 @@ __all__ = (
class ComponentModel(models.Model): 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( description = models.CharField(
max_length=200, max_length=200,
blank=True blank=True
@ -233,24 +251,6 @@ class ConsolePort(CableTermination, ComponentModel):
""" """
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts. 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( type = models.CharField(
max_length=50, max_length=50,
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
@ -270,7 +270,7 @@ class ConsolePort(CableTermination, ComponentModel):
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'type', 'description'] csv_headers = ['device', 'name', 'label', 'type', 'description']
class Meta: class Meta:
ordering = ('device', '_name') ordering = ('device', '_name')
@ -283,6 +283,7 @@ class ConsolePort(CableTermination, ComponentModel):
return ( return (
self.device.identifier, self.device.identifier,
self.name, self.name,
self.label,
self.type, self.type,
self.description, 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. 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( type = models.CharField(
max_length=50, max_length=50,
choices=ConsolePortTypeChoices, choices=ConsolePortTypeChoices,
@ -327,7 +310,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'type', 'description'] csv_headers = ['device', 'name', 'label', 'type', 'description']
class Meta: class Meta:
ordering = ('device', '_name') ordering = ('device', '_name')
@ -340,6 +323,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
return ( return (
self.device.identifier, self.device.identifier,
self.name, self.name,
self.label,
self.type, self.type,
self.description, self.description,
) )
@ -354,24 +338,6 @@ class PowerPort(CableTermination, ComponentModel):
""" """
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets. 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( type = models.CharField(
max_length=50, max_length=50,
choices=PowerPortTypeChoices, choices=PowerPortTypeChoices,
@ -410,7 +376,7 @@ class PowerPort(CableTermination, ComponentModel):
) )
tags = TaggableManager(through=TaggedItem) 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: class Meta:
ordering = ('device', '_name') ordering = ('device', '_name')
@ -423,6 +389,7 @@ class PowerPort(CableTermination, ComponentModel):
return ( return (
self.device.identifier, self.device.identifier,
self.name, self.name,
self.label,
self.get_type_display(), self.get_type_display(),
self.maximum_draw, self.maximum_draw,
self.allocated_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. 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( type = models.CharField(
max_length=50, max_length=50,
choices=PowerOutletTypeChoices, choices=PowerOutletTypeChoices,
@ -562,7 +511,7 @@ class PowerOutlet(CableTermination, ComponentModel):
) )
tags = TaggableManager(through=TaggedItem) 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: class Meta:
ordering = ('device', '_name') ordering = ('device', '_name')
@ -575,6 +524,7 @@ class PowerOutlet(CableTermination, ComponentModel):
return ( return (
self.device.identifier, self.device.identifier,
self.name, self.name,
self.label,
self.get_type_display(), self.get_type_display(),
self.power_port.name if self.power_port else None, self.power_port.name if self.power_port else None,
self.get_feed_leg_display(), self.get_feed_leg_display(),
@ -595,15 +545,9 @@ class PowerOutlet(CableTermination, ComponentModel):
# #
class BaseInterface(models.Model): class BaseInterface(models.Model):
name = models.CharField( """
max_length=64 Abstract base class for fields shared by dcim.Interface and virtualization.VMInterface.
) """
_name = NaturalOrderingField(
target_field='name',
naturalize_function=naturalize_interface,
max_length=100,
blank=True
)
enabled = models.BooleanField( enabled = models.BooleanField(
default=True 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. A network interface within a Device. A physical Interface can connect to exactly one other Interface.
""" """
device = models.ForeignKey( # Override ComponentModel._name to specify naturalize_interface function
to='Device', _name = NaturalOrderingField(
on_delete=models.CASCADE, target_field='name',
related_name='interfaces', naturalize_function=naturalize_interface,
null=True, max_length=100,
blank=True blank=True
) )
label = models.CharField(
max_length=64,
blank=True,
help_text="Physical label"
)
_connected_interface = models.OneToOneField( _connected_interface = models.OneToOneField(
to='self', to='self',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
@ -703,7 +642,7 @@ class Interface(CableTermination, ComponentModel, BaseInterface):
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = [ 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: class Meta:
@ -717,6 +656,7 @@ class Interface(CableTermination, ComponentModel, BaseInterface):
return ( return (
self.device.identifier if self.device else None, self.device.identifier if self.device else None,
self.name, self.name,
self.label,
self.lag.name if self.lag else None, self.lag.name if self.lag else None,
self.get_type_display(), self.get_type_display(),
self.enabled, self.enabled,
@ -849,19 +789,6 @@ class FrontPort(CableTermination, ComponentModel):
""" """
A pass-through port on the front of a Device. 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( type = models.CharField(
max_length=50, max_length=50,
choices=PortTypeChoices choices=PortTypeChoices
@ -877,7 +804,7 @@ class FrontPort(CableTermination, ComponentModel):
) )
tags = TaggableManager(through=TaggedItem) 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: class Meta:
ordering = ('device', '_name') ordering = ('device', '_name')
@ -886,9 +813,6 @@ class FrontPort(CableTermination, ComponentModel):
('rear_port', 'rear_port_position'), ('rear_port', 'rear_port_position'),
) )
def __str__(self):
return self.name
def get_absolute_url(self): def get_absolute_url(self):
return reverse('dcim:frontport', kwargs={'pk': self.pk}) return reverse('dcim:frontport', kwargs={'pk': self.pk})
@ -896,6 +820,7 @@ class FrontPort(CableTermination, ComponentModel):
return ( return (
self.device.identifier, self.device.identifier,
self.name, self.name,
self.label,
self.get_type_display(), self.get_type_display(),
self.rear_port.name, self.rear_port.name,
self.rear_port_position, self.rear_port_position,
@ -924,19 +849,6 @@ class RearPort(CableTermination, ComponentModel):
""" """
A pass-through port on the rear of a Device. 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( type = models.CharField(
max_length=50, max_length=50,
choices=PortTypeChoices choices=PortTypeChoices
@ -947,15 +859,12 @@ class RearPort(CableTermination, ComponentModel):
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'type', 'positions', 'description'] csv_headers = ['device', 'name', 'label', 'type', 'positions', 'description']
class Meta: class Meta:
ordering = ('device', '_name') ordering = ('device', '_name')
unique_together = ('device', 'name') unique_together = ('device', 'name')
def __str__(self):
return self.name
def get_absolute_url(self): def get_absolute_url(self):
return reverse('dcim:rearport', kwargs={'pk': self.pk}) return reverse('dcim:rearport', kwargs={'pk': self.pk})
@ -963,6 +872,7 @@ class RearPort(CableTermination, ComponentModel):
return ( return (
self.device.identifier, self.device.identifier,
self.name, self.name,
self.label,
self.get_type_display(), self.get_type_display(),
self.positions, self.positions,
self.description, self.description,
@ -978,25 +888,6 @@ class DeviceBay(ComponentModel):
""" """
An empty space within a Device which can house a child device 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( installed_device = models.OneToOneField(
to='dcim.Device', to='dcim.Device',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
@ -1006,17 +897,12 @@ class DeviceBay(ComponentModel):
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'installed_device', 'description'] csv_headers = ['device', 'name', 'label', 'installed_device', 'description']
class Meta: class Meta:
ordering = ('device', '_name') ordering = ('device', '_name')
unique_together = ('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): def get_absolute_url(self):
return reverse('dcim:devicebay', kwargs={'pk': self.pk}) return reverse('dcim:devicebay', kwargs={'pk': self.pk})
@ -1024,6 +910,7 @@ class DeviceBay(ComponentModel):
return ( return (
self.device.identifier, self.device.identifier,
self.name, self.name,
self.label,
self.installed_device.identifier if self.installed_device else None, self.installed_device.identifier if self.installed_device else None,
self.description, 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. 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. InventoryItems are used only for inventory purposes.
""" """
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='inventory_items'
)
parent = models.ForeignKey( parent = models.ForeignKey(
to='self', to='self',
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -1073,15 +955,6 @@ class InventoryItem(ComponentModel):
blank=True, blank=True,
null=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( manufacturer = models.ForeignKey(
to='dcim.Manufacturer', to='dcim.Manufacturer',
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -1116,16 +989,13 @@ class InventoryItem(ComponentModel):
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
csv_headers = [ 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: class Meta:
ordering = ('device__id', 'parent__id', '_name') ordering = ('device__id', 'parent__id', '_name')
unique_together = ('device', 'parent', 'name') unique_together = ('device', 'parent', 'name')
def __str__(self):
return self.name
def get_absolute_url(self): def get_absolute_url(self):
return reverse('dcim:inventoryitem', kwargs={'pk': self.pk}) return reverse('dcim:inventoryitem', kwargs={'pk': self.pk})
@ -1133,6 +1003,7 @@ class InventoryItem(ComponentModel):
return ( return (
self.device.name or '{{{}}}'.format(self.device.pk), self.device.name or '{{{}}}'.format(self.device.pk),
self.name, self.name,
self.label,
self.manufacturer.name if self.manufacturer else None, self.manufacturer.name if self.manufacturer else None,
self.part_id, self.part_id,
self.serial, 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 # Regions
# #
@ -401,10 +386,9 @@ class ComponentTemplateTable(BaseTable):
class ConsolePortTemplateTable(ComponentTemplateTable): class ConsolePortTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn( actions = ButtonsColumn(
template_code=get_component_template_actions('consoleporttemplate'), model=ConsolePortTemplate,
attrs={'td': {'class': 'text-right noprint'}}, buttons=('edit', 'delete')
verbose_name=''
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -414,10 +398,9 @@ class ConsolePortTemplateTable(ComponentTemplateTable):
class ConsoleServerPortTemplateTable(ComponentTemplateTable): class ConsoleServerPortTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn( actions = ButtonsColumn(
template_code=get_component_template_actions('consoleserverporttemplate'), model=ConsoleServerPortTemplate,
attrs={'td': {'class': 'text-right noprint'}}, buttons=('edit', 'delete')
verbose_name=''
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -427,10 +410,9 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable):
class PowerPortTemplateTable(ComponentTemplateTable): class PowerPortTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn( actions = ButtonsColumn(
template_code=get_component_template_actions('powerporttemplate'), model=PowerPortTemplate,
attrs={'td': {'class': 'text-right noprint'}}, buttons=('edit', 'delete')
verbose_name=''
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -440,10 +422,9 @@ class PowerPortTemplateTable(ComponentTemplateTable):
class PowerOutletTemplateTable(ComponentTemplateTable): class PowerOutletTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn( actions = ButtonsColumn(
template_code=get_component_template_actions('poweroutlettemplate'), model=PowerOutletTemplate,
attrs={'td': {'class': 'text-right noprint'}}, buttons=('edit', 'delete')
verbose_name=''
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -456,10 +437,9 @@ class InterfaceTemplateTable(ComponentTemplateTable):
mgmt_only = BooleanColumn( mgmt_only = BooleanColumn(
verbose_name='Management Only' verbose_name='Management Only'
) )
actions = tables.TemplateColumn( actions = ButtonsColumn(
template_code=get_component_template_actions('interfacetemplate'), model=InterfaceTemplate,
attrs={'td': {'class': 'text-right noprint'}}, buttons=('edit', 'delete')
verbose_name=''
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -472,10 +452,9 @@ class FrontPortTemplateTable(ComponentTemplateTable):
rear_port_position = tables.Column( rear_port_position = tables.Column(
verbose_name='Position' verbose_name='Position'
) )
actions = tables.TemplateColumn( actions = ButtonsColumn(
template_code=get_component_template_actions('frontporttemplate'), model=FrontPortTemplate,
attrs={'td': {'class': 'text-right noprint'}}, buttons=('edit', 'delete')
verbose_name=''
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -485,10 +464,9 @@ class FrontPortTemplateTable(ComponentTemplateTable):
class RearPortTemplateTable(ComponentTemplateTable): class RearPortTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn( actions = ButtonsColumn(
template_code=get_component_template_actions('rearporttemplate'), model=RearPortTemplate,
attrs={'td': {'class': 'text-right noprint'}}, buttons=('edit', 'delete')
verbose_name=''
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -498,10 +476,9 @@ class RearPortTemplateTable(ComponentTemplateTable):
class DeviceBayTemplateTable(ComponentTemplateTable): class DeviceBayTemplateTable(ComponentTemplateTable):
actions = tables.TemplateColumn( actions = ButtonsColumn(
template_code=get_component_template_actions('devicebaytemplate'), model=DeviceBayTemplate,
attrs={'td': {'class': 'text-right noprint'}}, buttons=('edit', 'delete')
verbose_name=''
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
@ -784,9 +761,10 @@ class InventoryItemTable(DeviceComponentTable):
class Meta(DeviceComponentTable.Meta): class Meta(DeviceComponentTable.Meta):
model = InventoryItem model = InventoryItem
fields = ( 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.auth.models import User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.test import override_settings
from django.urls import reverse from django.urls import reverse
from rest_framework import status from rest_framework import status
@ -131,6 +132,7 @@ class SiteTest(APIViewTestCases.APIViewTestCase):
}, },
] ]
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
def test_get_site_graphs(self): def test_get_site_graphs(self):
""" """
Test retrieval of Graphs assigned to Sites. 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): def test_get_device_graphs(self):
""" """
Test retrieval of Graphs assigned to Devices. 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): def test_get_interface_graphs(self):
""" """
Test retrieval of Graphs assigned to Devices. Test retrieval of Graphs assigned to Devices.

View File

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

View File

@ -98,6 +98,7 @@ urlpatterns = [
# Console port templates # Console port templates
path('console-port-templates/add/', views.ConsolePortTemplateCreateView.as_view(), name='consoleporttemplate_add'), 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/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/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>/edit/', views.ConsolePortTemplateEditView.as_view(), name='consoleporttemplate_edit'),
path('console-port-templates/<int:pk>/delete/', views.ConsolePortTemplateDeleteView.as_view(), name='consoleporttemplate_delete'), path('console-port-templates/<int:pk>/delete/', views.ConsolePortTemplateDeleteView.as_view(), name='consoleporttemplate_delete'),
@ -105,6 +106,7 @@ urlpatterns = [
# Console server port templates # Console server port templates
path('console-server-port-templates/add/', views.ConsoleServerPortTemplateCreateView.as_view(), name='consoleserverporttemplate_add'), 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/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/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>/edit/', views.ConsoleServerPortTemplateEditView.as_view(), name='consoleserverporttemplate_edit'),
path('console-server-port-templates/<int:pk>/delete/', views.ConsoleServerPortTemplateDeleteView.as_view(), name='consoleserverporttemplate_delete'), path('console-server-port-templates/<int:pk>/delete/', views.ConsoleServerPortTemplateDeleteView.as_view(), name='consoleserverporttemplate_delete'),
@ -112,6 +114,7 @@ urlpatterns = [
# Power port templates # Power port templates
path('power-port-templates/add/', views.PowerPortTemplateCreateView.as_view(), name='powerporttemplate_add'), 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/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/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>/edit/', views.PowerPortTemplateEditView.as_view(), name='powerporttemplate_edit'),
path('power-port-templates/<int:pk>/delete/', views.PowerPortTemplateDeleteView.as_view(), name='powerporttemplate_delete'), path('power-port-templates/<int:pk>/delete/', views.PowerPortTemplateDeleteView.as_view(), name='powerporttemplate_delete'),
@ -119,6 +122,7 @@ urlpatterns = [
# Power outlet templates # Power outlet templates
path('power-outlet-templates/add/', views.PowerOutletTemplateCreateView.as_view(), name='poweroutlettemplate_add'), 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/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/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>/edit/', views.PowerOutletTemplateEditView.as_view(), name='poweroutlettemplate_edit'),
path('power-outlet-templates/<int:pk>/delete/', views.PowerOutletTemplateDeleteView.as_view(), name='poweroutlettemplate_delete'), path('power-outlet-templates/<int:pk>/delete/', views.PowerOutletTemplateDeleteView.as_view(), name='poweroutlettemplate_delete'),
@ -126,6 +130,7 @@ urlpatterns = [
# Interface templates # Interface templates
path('interface-templates/add/', views.InterfaceTemplateCreateView.as_view(), name='interfacetemplate_add'), 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/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/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>/edit/', views.InterfaceTemplateEditView.as_view(), name='interfacetemplate_edit'),
path('interface-templates/<int:pk>/delete/', views.InterfaceTemplateDeleteView.as_view(), name='interfacetemplate_delete'), path('interface-templates/<int:pk>/delete/', views.InterfaceTemplateDeleteView.as_view(), name='interfacetemplate_delete'),
@ -133,6 +138,7 @@ urlpatterns = [
# Front port templates # Front port templates
path('front-port-templates/add/', views.FrontPortTemplateCreateView.as_view(), name='frontporttemplate_add'), 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/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/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>/edit/', views.FrontPortTemplateEditView.as_view(), name='frontporttemplate_edit'),
path('front-port-templates/<int:pk>/delete/', views.FrontPortTemplateDeleteView.as_view(), name='frontporttemplate_delete'), path('front-port-templates/<int:pk>/delete/', views.FrontPortTemplateDeleteView.as_view(), name='frontporttemplate_delete'),
@ -140,6 +146,7 @@ urlpatterns = [
# Rear port templates # Rear port templates
path('rear-port-templates/add/', views.RearPortTemplateCreateView.as_view(), name='rearporttemplate_add'), 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/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/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>/edit/', views.RearPortTemplateEditView.as_view(), name='rearporttemplate_edit'),
path('rear-port-templates/<int:pk>/delete/', views.RearPortTemplateDeleteView.as_view(), name='rearporttemplate_delete'), path('rear-port-templates/<int:pk>/delete/', views.RearPortTemplateDeleteView.as_view(), name='rearporttemplate_delete'),
@ -147,6 +154,7 @@ urlpatterns = [
# Device bay templates # Device bay templates
path('device-bay-templates/add/', views.DeviceBayTemplateCreateView.as_view(), name='devicebaytemplate_add'), 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/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/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>/edit/', views.DeviceBayTemplateEditView.as_view(), name='devicebaytemplate_edit'),
path('device-bay-templates/<int:pk>/delete/', views.DeviceBayTemplateDeleteView.as_view(), name='devicebaytemplate_delete'), 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 form = forms.ConsolePortTemplateBulkEditForm
class ConsolePortTemplateBulkRenameView(BulkRenameView):
queryset = ConsolePortTemplate.objects.all()
class ConsolePortTemplateBulkDeleteView(BulkDeleteView): class ConsolePortTemplateBulkDeleteView(BulkDeleteView):
queryset = ConsolePortTemplate.objects.all() queryset = ConsolePortTemplate.objects.all()
table = tables.ConsolePortTemplateTable table = tables.ConsolePortTemplateTable
@ -671,6 +675,10 @@ class ConsoleServerPortTemplateBulkEditView(BulkEditView):
form = forms.ConsoleServerPortTemplateBulkEditForm form = forms.ConsoleServerPortTemplateBulkEditForm
class ConsoleServerPortTemplateBulkRenameView(BulkRenameView):
queryset = ConsoleServerPortTemplate.objects.all()
class ConsoleServerPortTemplateBulkDeleteView(BulkDeleteView): class ConsoleServerPortTemplateBulkDeleteView(BulkDeleteView):
queryset = ConsoleServerPortTemplate.objects.all() queryset = ConsoleServerPortTemplate.objects.all()
table = tables.ConsoleServerPortTemplateTable table = tables.ConsoleServerPortTemplateTable
@ -702,6 +710,10 @@ class PowerPortTemplateBulkEditView(BulkEditView):
form = forms.PowerPortTemplateBulkEditForm form = forms.PowerPortTemplateBulkEditForm
class PowerPortTemplateBulkRenameView(BulkRenameView):
queryset = PowerPortTemplate.objects.all()
class PowerPortTemplateBulkDeleteView(BulkDeleteView): class PowerPortTemplateBulkDeleteView(BulkDeleteView):
queryset = PowerPortTemplate.objects.all() queryset = PowerPortTemplate.objects.all()
table = tables.PowerPortTemplateTable table = tables.PowerPortTemplateTable
@ -733,6 +745,10 @@ class PowerOutletTemplateBulkEditView(BulkEditView):
form = forms.PowerOutletTemplateBulkEditForm form = forms.PowerOutletTemplateBulkEditForm
class PowerOutletTemplateBulkRenameView(BulkRenameView):
queryset = PowerOutletTemplate.objects.all()
class PowerOutletTemplateBulkDeleteView(BulkDeleteView): class PowerOutletTemplateBulkDeleteView(BulkDeleteView):
queryset = PowerOutletTemplate.objects.all() queryset = PowerOutletTemplate.objects.all()
table = tables.PowerOutletTemplateTable table = tables.PowerOutletTemplateTable
@ -764,6 +780,10 @@ class InterfaceTemplateBulkEditView(BulkEditView):
form = forms.InterfaceTemplateBulkEditForm form = forms.InterfaceTemplateBulkEditForm
class InterfaceTemplateBulkRenameView(BulkRenameView):
queryset = InterfaceTemplate.objects.all()
class InterfaceTemplateBulkDeleteView(BulkDeleteView): class InterfaceTemplateBulkDeleteView(BulkDeleteView):
queryset = InterfaceTemplate.objects.all() queryset = InterfaceTemplate.objects.all()
table = tables.InterfaceTemplateTable table = tables.InterfaceTemplateTable
@ -795,6 +815,10 @@ class FrontPortTemplateBulkEditView(BulkEditView):
form = forms.FrontPortTemplateBulkEditForm form = forms.FrontPortTemplateBulkEditForm
class FrontPortTemplateBulkRenameView(BulkRenameView):
queryset = FrontPortTemplate.objects.all()
class FrontPortTemplateBulkDeleteView(BulkDeleteView): class FrontPortTemplateBulkDeleteView(BulkDeleteView):
queryset = FrontPortTemplate.objects.all() queryset = FrontPortTemplate.objects.all()
table = tables.FrontPortTemplateTable table = tables.FrontPortTemplateTable
@ -826,6 +850,10 @@ class RearPortTemplateBulkEditView(BulkEditView):
form = forms.RearPortTemplateBulkEditForm form = forms.RearPortTemplateBulkEditForm
class RearPortTemplateBulkRenameView(BulkRenameView):
queryset = RearPortTemplate.objects.all()
class RearPortTemplateBulkDeleteView(BulkDeleteView): class RearPortTemplateBulkDeleteView(BulkDeleteView):
queryset = RearPortTemplate.objects.all() queryset = RearPortTemplate.objects.all()
table = tables.RearPortTemplateTable table = tables.RearPortTemplateTable
@ -857,6 +885,10 @@ class DeviceBayTemplateBulkEditView(BulkEditView):
form = forms.DeviceBayTemplateBulkEditForm form = forms.DeviceBayTemplateBulkEditForm
class DeviceBayTemplateBulkRenameView(BulkRenameView):
queryset = DeviceBayTemplate.objects.all()
class DeviceBayTemplateBulkDeleteView(BulkDeleteView): class DeviceBayTemplateBulkDeleteView(BulkDeleteView):
queryset = DeviceBayTemplate.objects.all() queryset = DeviceBayTemplate.objects.all()
table = tables.DeviceBayTemplateTable table = tables.DeviceBayTemplateTable
@ -952,7 +984,7 @@ class DeviceView(ObjectView):
vc_members = [] vc_members = []
# Console ports # 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', 'connected_endpoint__device', 'cable',
) )
@ -964,7 +996,7 @@ class DeviceView(ObjectView):
) )
# Power ports # 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', '_connected_poweroutlet__device', 'cable',
) )
@ -982,15 +1014,15 @@ class DeviceView(ObjectView):
) )
# Front ports # 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_port', 'cable',
) )
# Rear ports # 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
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', 'installed_device__device_type__manufacturer',
) )
@ -1011,14 +1043,14 @@ class DeviceView(ObjectView):
return render(request, 'dcim/device.html', { return render(request, 'dcim/device.html', {
'device': device, 'device': device,
'console_ports': console_ports, 'consoleports': consoleports,
'consoleserverports': consoleserverports, 'consoleserverports': consoleserverports,
'power_ports': power_ports, 'powerports': powerports,
'poweroutlets': poweroutlets, 'poweroutlets': poweroutlets,
'interfaces': interfaces, 'interfaces': interfaces,
'device_bays': device_bays, 'devicebays': devicebays,
'front_ports': front_ports, 'frontports': frontports,
'rear_ports': rear_ports, 'rearports': rearports,
'services': services, 'services': services,
'secrets': secrets, 'secrets': secrets,
'vc_members': vc_members, 'vc_members': vc_members,

View File

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

View File

@ -101,7 +101,7 @@
</li> </li>
<li role="presentation"{% if active_tab == 'inventory' %} class="active"{% endif %}> <li role="presentation"{% if active_tab == 'inventory' %} class="active"{% endif %}>
<a href="{% url 'dcim:device_inventory' pk=device.pk %}"> <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> </a>
</li> </li>
{% if perms.dcim.napalm_read_device %} {% if perms.dcim.napalm_read_device %}
@ -329,86 +329,6 @@
{% plugin_left_page device %} {% plugin_left_page device %}
</div> </div>
<div class="col-md-6"> <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 %} {% if power_ports and poweroutlets %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
@ -554,355 +474,490 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% if device_bays or device.device_type.is_parent_device %} <ul class="nav nav-tabs" role="tablist">
<form method="post"> <li role="presentation" class="active">
{% csrf_token %} <a href="#interfaces" role="tab" data-toggle="tab">Interfaces {% badge interfaces|length %}</a>
<div class="panel panel-default"> </li>
<div class="panel-heading"> <li role="presentation">
<strong>Device Bays</strong> <a href="#frontports" role="tab" data-toggle="tab">Front Ports {% badge frontports|length %}</a>
</div> </li>
<table class="table table-hover table-headings panel-body component-list"> <li role="presentation">
<thead> <a href="#rearports" role="tab" data-toggle="tab">Rear Ports {% badge rearports|length %}</a>
<tr> </li>
{% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %} <li role="presentation">
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th> <a href="#consoleports" role="tab" data-toggle="tab">Console Ports {% badge consoleports|length %}</a>
{% endif %} </li>
<th>Name</th> <li role="presentation">
<th>Status</th> <a href="#consoleserverports" role="tab" data-toggle="tab">Console Server Ports {% badge consoleserverports|length %}</a>
<th>Description</th> </li>
<th colspan="2">Installed Device</th> <li role="presentation">
<th></th> <a href="#powerports" role="tab" data-toggle="tab">Power Ports {% badge powerports|length %}</a>
</tr> </li>
</thead> <li role="presentation">
<tbody> <a href="#poweroutlets" role="tab" data-toggle="tab">Power Outlets {% badge poweroutlets|length %}</a>
{% for devicebay in device_bays %} </li>
{% include 'dcim/inc/devicebay.html' %} <li role="presentation">
{% empty %} <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> <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> </tr>
{% endfor %} </thead>
</tbody> <tbody>
</table> {% for iface in interfaces %}
<div class="panel-footer noprint"> {% include 'dcim/inc/interface.html' %}
{% if device_bays and perms.dcim.change_devicebay %} {% endfor %}
<button type="submit" name="_rename" formaction="{% url 'dcim:devicebay_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs"> </tbody>
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename </table>
</button> <div class="panel-footer noprint">
{% endif %} {% if interfaces and perms.dcim.change_interface %}
{% if device_bays and perms.dcim.delete_devicebay %} <button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
<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-pencil" aria-hidden="true"></span> Rename
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected </button>
</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">
{% endif %} <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
{% if perms.dcim.add_devicebay %} </button>
<div class="pull-right"> {% endif %}
<a href="{% url 'dcim:devicebay_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs"> {% if interfaces and perms.dcim.change_interface %}
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add device bays <button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
</a> <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
</div> </button>
<div class="clearfix"></div> {% endif %}
{% endif %} {% if interfaces and perms.dcim.delete_interface %}
</div> <button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
</div> <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</form> </button>
{% endif %} {% endif %}
{% if interfaces %} {% if perms.dcim.add_interface %}
<form method="post"> <div class="pull-right">
{% csrf_token %} <a href="{% url 'dcim:interface_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
<div class="panel panel-default"> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
<div class="panel-heading"> </a>
<strong>Interfaces</strong> </div>
<div class="pull-right noprint"> <div class="clearfix"></div>
<button class="btn btn-default btn-xs toggle-ips" selected="selected"> {% endif %}
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs </div>
</button> </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>
<div class="col-md-2 pull-right noprint"> <table class="table table-hover table-headings panel-body component-list">
<input class="form-control interface-filter" type="text" placeholder="Filter" title="Filter text (regular expressions supported)" style="height: 23px" /> <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>
</div> </div>
<table id="interfaces_table" class="table table-hover table-headings panel-body component-list"> </form>
<thead> </div>
<tr> <div role="tabpanel" class="tab-pane" id="rearports">
{% if perms.dcim.change_interface or perms.dcim.delete_interface %} <form method="post">
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th> {% csrf_token %}
{% endif %} <div class="panel panel-default">
<th>Name</th> <div class="panel-heading">
<th>LAG</th> <strong>Rear Ports</strong>
<th>Description</th> </div>
<th>MTU</th> <table class="table table-hover table-headings panel-body component-list">
<th>Mode</th> <thead>
<th>Cable</th> <tr>
<th colspan="2">Connection</th> {% if perms.dcim.change_rearport or perms.dcim.delete_rearport %}
<th></th> <th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
</tr> {% endif %}
</thead> <th>Name</th>
<tbody> <th>Type</th>
{% for iface in interfaces %} <th>Positions</th>
{% include 'dcim/inc/interface.html' %} <th>Description</th>
{% endfor %} <th>Cable</th>
</tbody> <th colspan="2">Connection</th>
</table> <th></th>
<div class="panel-footer noprint"> </tr>
{% if interfaces and perms.dcim.change_interface %} </thead>
<button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs"> <tbody>
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename {% for rearport in rearports %}
</button> {% include 'dcim/inc/rearport.html' %}
<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"> {% endfor %}
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit </tbody>
</button> </table>
{% endif %} <div class="panel-footer noprint">
{% if interfaces and perms.dcim.change_interface %} {% if rearports and perms.dcim.change_rearport %}
<button type="submit" name="_disconnect" formaction="{% url 'dcim:interface_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs"> <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-resize-full" aria-hidden="true"></span> Disconnect <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
</button> </button>
{% endif %} <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">
{% if interfaces and perms.dcim.delete_interface %} <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
<button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs"> </button>
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete <button type="submit" name="_disconnect" formaction="{% url 'dcim:rearport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
</button> <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
{% endif %} </button>
{% if perms.dcim.add_interface %} {% endif %}
<div class="pull-right"> {% if rearports and perms.dcim.delete_rearport %}
<a href="{% url 'dcim:interface_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs"> <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-plus" aria-hidden="true"></span> Add interfaces <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</a> </button>
</div> {% endif %}
<div class="clearfix"></div> {% if perms.dcim.add_rearport %}
{% endif %} <div class="pull-right">
</div> <a href="{% url 'dcim:rearport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
</div> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add rear ports
</form> </a>
{% endif %} </div>
{% if consoleserverports %} <div class="clearfix"></div>
<form method="post"> {% endif %}
{% csrf_token %} </div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Console Server Ports</strong>
</div> </div>
<table class="table table-hover table-headings panel-body component-list"> </form>
<thead> </div>
<tr> <div role="tabpanel" class="tab-pane" id="consoleports">
{% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %} <form method="post">
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th> {% csrf_token %}
{% endif %} <div class="panel panel-default">
<th>Name</th> <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>Type</th>
<th>Description</th> <th>Input/Leg</th>
<th>Cable</th> <th>Description</th>
<th colspan="2">Connection</th> <th>Cable</th>
<th></th> <th colspan="3">Connection</th>
</tr> <th></th>
</thead> </tr>
<tbody> </thead>
{% for csp in consoleserverports %} <tbody>
{% include 'dcim/inc/consoleserverport.html' %} {% for po in poweroutlets %}
{% endfor %} {% include 'dcim/inc/poweroutlet.html' %}
</tbody> {% endfor %}
</table> </tbody>
<div class="panel-footer noprint"> </table>
{% if consoleserverports and perms.dcim.change_consoleport %} <div class="panel-footer noprint">
<button type="submit" name="_rename" formaction="{% url 'dcim:consoleserverport_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs"> {% if poweroutlets and perms.dcim.change_powerport %}
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename <button type="submit" name="_rename" formaction="{% url 'dcim:poweroutlet_bulk_rename' %}?return_url={{ device.get_absolute_url }}" class="btn btn-warning btn-xs">
</button> <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
<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"> </button>
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit <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">
</button> <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
<button type="submit" name="_disconnect" formaction="{% url 'dcim:consoleserverport_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs"> </button>
<span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect <button type="submit" name="_disconnect" formaction="{% url 'dcim:poweroutlet_bulk_disconnect' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
</button> <span class="glyphicon glyphicon-resize-full" aria-hidden="true"></span> Disconnect
{% endif %} </button>
{% if consoleserverports and perms.dcim.delete_consoleserverport %} {% endif %}
<button type="submit" formaction="{% url 'dcim:consoleserverport_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs"> {% if poweroutlets and perms.dcim.delete_poweroutlet %}
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete <button type="submit" formaction="{% url 'dcim:poweroutlet_bulk_delete' %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
</button> <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
{% endif %} </button>
{% if perms.dcim.add_consoleserverport %} {% endif %}
<div class="pull-right"> {% if perms.dcim.add_poweroutlet %}
<a href="{% url 'dcim:consoleserverport_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs"> <div class="pull-right">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add console server ports <a href="{% url 'dcim:poweroutlet_add' %}?device={{ device.pk }}&return_url={{ device.get_absolute_url }}" class="btn btn-primary btn-xs">
</a> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add power outlets
</div> </a>
<div class="clearfix"></div> </div>
{% endif %} <div class="clearfix"></div>
{% endif %}
</div>
</div> </div>
</div> </form>
</form> </div>
{% endif %} <div role="tabpanel" class="tab-pane" id="devicebays">
{% if poweroutlets %} <form method="post">
<form method="post"> {% csrf_token %}
{% csrf_token %} <div class="panel panel-default">
<div class="panel panel-default"> <div class="panel-heading">
<div class="panel-heading"> <strong>Device Bays</strong>
<strong>Power Outlets</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> </div>
<table class="table table-hover table-headings panel-body component-list"> </form>
<thead> </div>
<tr> </div>
{% 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 %}
</div> </div>
</div> </div>
{% include 'inc/modal.html' with name='graphs' title='Graphs' %} {% include 'inc/modal.html' with name='graphs' title='Graphs' %}

View File

@ -63,149 +63,157 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<strong>Chassis</strong> <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> </div>
<table class="table table-hover panel-body attr-table"> {% plugin_left_page devicetype %}
<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> </div>
{% plugin_left_page devicetype %} <div class="col-md-6">
</div> {% include 'inc/custom_fields_panel.html' with obj=devicetype %}
<div class="col-md-6"> {% include 'extras/inc/tags_panel.html' with tags=devicetype.tags.all url='dcim:devicetype_list' %}
{% include 'inc/custom_fields_panel.html' with obj=devicetype %} <div class="panel panel-default">
{% include 'extras/inc/tags_panel.html' with tags=devicetype.tags.all url='dcim:devicetype_list' %} <div class="panel-heading">
<div class="panel panel-default"> <strong>Comments</strong>
<div class="panel-heading"> </div>
<strong>Comments</strong> <div class="panel-body rendered-markdown">
{% if devicetype.comments %}
{{ devicetype.comments|render_markdown }}
{% else %}
<span class="text-muted">None</span>
{% endif %}
</div>
</div> </div>
<div class="panel-body rendered-markdown"> {% plugin_right_page devicetype %}
{% if devicetype.comments %} </div>
{{ devicetype.comments|render_markdown }} </div>
{% else %} <div class="row">
<span class="text-muted">None</span> <div class="col-md-12">
{% endif %} {% 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>
</div> </div>
{% plugin_right_page devicetype %}
</div> </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 %} {% endblock %}

View File

@ -18,8 +18,6 @@
{% if cp.type %}{{ cp.get_type_display }}{% else %}&mdash;{% endif %} {% if cp.type %}{{ cp.get_type_display }}{% else %}&mdash;{% endif %}
</td> </td>
<td></td>
{# Description #} {# Description #}
<td> <td>
{{ cp.description }} {{ 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 %} {% if perms.dcim.change_devicetype %}
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
@ -8,19 +9,18 @@
{% include 'responsive_table.html' %} {% include 'responsive_table.html' %}
<div class="panel-footer noprint"> <div class="panel-footer noprint">
{% if table.rows %} {% if table.rows %}
{% if edit_url %} <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">
<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> Rename
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit Selected </button>
</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">
{% endif %} <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
{% if delete_url %} </button>
<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"> <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 Selected <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
</button> </button>
{% endif %}
{% endif %} {% endif %}
<div class="pull-right"> <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> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
Add {{ title }} Add {{ title }}
</a> </a>

View File

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

View File

@ -4,7 +4,7 @@
<strong>Tags</strong> <strong>Tags</strong>
</div> </div>
<div class="panel-body"> <div class="panel-body">
{% for tag in tags.all %} {% for tag in tags.all.unrestricted %}
{% tag tag url %} {% tag tag url %}
{% empty %} {% empty %}
<span class="text-muted">No tags assigned</span> <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 model: Model class to use for calculating URL view names
:param prepend_content: Additional template content to render in the column (optional) :param prepend_content: Additional template content to render in the column (optional)
""" """
buttons = ('changelog', 'edit', 'delete')
attrs = {'td': {'class': 'text-right text-nowrap noprint'}} attrs = {'td': {'class': 'text-right text-nowrap noprint'}}
# Note that braces are escaped to allow for string formatting prior to template rendering # Note that braces are escaped to allow for string formatting prior to template rendering
template_code = """ template_code = """
<a href="{{% url '{app_label}:{model_name}_changelog' {pk_field}=record.{pk_field} %}}" class="btn btn-default btn-xs" title="Change log"> {{% if "changelog" in buttons %}}
<i class="fa fa-history"></i> <a href="{{% url '{app_label}:{model_name}_changelog' {pk_field}=record.{pk_field} %}}" class="btn btn-default btn-xs" title="Change log">
</a> <i class="fa fa-history"></i>
{{% if perms.{app_label}.change_{model_name} %}} </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"> <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> <i class="fa fa-pencil"></i>
</a> </a>
{{% endif %}} {{% 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"> <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> <i class="fa fa-trash"></i>
</a> </a>
{{% endif %}} {{% 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: if prepend_template:
prepend_template = prepend_template.replace('{', '{{') prepend_template = prepend_template.replace('{', '{{')
prepend_template = prepend_template.replace('}', '}}') prepend_template = prepend_template.replace('}', '}}')
@ -157,11 +160,16 @@ class ButtonsColumn(tables.TemplateColumn):
template_code = self.template_code.format( template_code = self.template_code.format(
app_label=model._meta.app_label, app_label=model._meta.app_label,
model_name=model._meta.model_name, model_name=model._meta.model_name,
pk_field=pk_field pk_field=pk_field,
buttons=buttons
) )
super().__init__(template_code=template_code, *args, **kwargs) super().__init__(template_code=template_code, *args, **kwargs)
self.extra_context.update({
'buttons': buttons or self.buttons,
})
def header(self): def header(self):
return '' return ''

View File

@ -242,3 +242,14 @@ def tag(tag, url_name=None):
'tag': tag, 'tag': tag,
'url_name': url_name, '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 # Copy tags
if is_taggable(instance): 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 # Concatenate parameters into a URL query string
param_string = '&'.join( param_string = '&'.join(

View File

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

View File

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

View File

@ -1,4 +1,5 @@
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.test import override_settings
from django.urls import reverse from django.urls import reverse
from rest_framework import status 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. Test retrieval of Graphs assigned to VM interfaces.
""" """