diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index c0d51afc2..c13d45f95 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -19,6 +19,7 @@ NetBox v2.9 replaces Django's built-in permissions framework with one that suppo * [#4793](https://github.com/netbox-community/netbox/issues/4793) - Add `description` field to device component templates * [#4795](https://github.com/netbox-community/netbox/issues/4795) - Add bulk disconnect capability for console and power ports * [#4807](https://github.com/netbox-community/netbox/issues/4807) - Add bulk edit ability for device bay templates +* [#4817](https://github.com/netbox-community/netbox/issues/4817) - Standardize device/VM component `name` field to 64 characters ### Configuration Changes diff --git a/netbox/circuits/tests/test_api.py b/netbox/circuits/tests/test_api.py index f887db29e..05a0febc7 100644 --- a/netbox/circuits/tests/test_api.py +++ b/netbox/circuits/tests/test_api.py @@ -1,4 +1,5 @@ from django.contrib.contenttypes.models import ContentType +from django.test import override_settings from django.urls import reverse from circuits.choices import * @@ -45,6 +46,7 @@ class ProviderTest(APIViewTestCases.APIViewTestCase): ) Provider.objects.bulk_create(providers) + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) def test_get_provider_graphs(self): """ Test retrieval of Graphs assigned to Providers. diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 6838c3987..3f1eff6a4 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -311,7 +311,7 @@ class RearPortTemplateSerializer(ValidatedModelSerializer): class Meta: model = RearPortTemplate - fields = ['id', 'device_type', 'name', 'type', 'positions', 'description'] + fields = ['id', 'device_type', 'name', 'label', 'type', 'positions', 'description'] class FrontPortTemplateSerializer(ValidatedModelSerializer): @@ -321,7 +321,7 @@ class FrontPortTemplateSerializer(ValidatedModelSerializer): class Meta: model = FrontPortTemplate - fields = ['id', 'device_type', 'name', 'type', 'rear_port', 'rear_port_position', 'description'] + fields = ['id', 'device_type', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description'] class DeviceBayTemplateSerializer(ValidatedModelSerializer): @@ -559,7 +559,7 @@ class RearPortSerializer(TaggedObjectSerializer, ValidatedModelSerializer): class Meta: model = RearPort - fields = ['id', 'device', 'name', 'type', 'positions', 'description', 'cable', 'tags'] + fields = ['id', 'device', 'name', 'label', 'type', 'positions', 'description', 'cable', 'tags'] class FrontPortRearPortSerializer(WritableNestedSerializer): @@ -570,7 +570,7 @@ class FrontPortRearPortSerializer(WritableNestedSerializer): class Meta: model = RearPort - fields = ['id', 'url', 'name'] + fields = ['id', 'url', 'name', 'label'] class FrontPortSerializer(TaggedObjectSerializer, ValidatedModelSerializer): @@ -581,7 +581,9 @@ class FrontPortSerializer(TaggedObjectSerializer, ValidatedModelSerializer): class Meta: model = FrontPort - fields = ['id', 'device', 'name', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'tags'] + fields = [ + 'id', 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'tags', + ] class DeviceBaySerializer(TaggedObjectSerializer, ValidatedModelSerializer): diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index d22511ede..fa0af7228 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -384,28 +384,28 @@ class DeviceTypeFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFil ) def _console_ports(self, queryset, name, value): - return queryset.exclude(consoleport_templates__isnull=value) + return queryset.exclude(consoleporttemplates__isnull=value) def _console_server_ports(self, queryset, name, value): - return queryset.exclude(consoleserverport_templates__isnull=value) + return queryset.exclude(consoleserverporttemplates__isnull=value) def _power_ports(self, queryset, name, value): - return queryset.exclude(powerport_templates__isnull=value) + return queryset.exclude(powerporttemplates__isnull=value) def _power_outlets(self, queryset, name, value): - return queryset.exclude(poweroutlet_templates__isnull=value) + return queryset.exclude(poweroutlettemplates__isnull=value) def _interfaces(self, queryset, name, value): - return queryset.exclude(interface_templates__isnull=value) + return queryset.exclude(interfacetemplates__isnull=value) def _pass_through_ports(self, queryset, name, value): return queryset.exclude( - frontport_templates__isnull=value, - rearport_templates__isnull=value + frontporttemplates__isnull=value, + rearporttemplates__isnull=value ) def _device_bays(self, queryset, name, value): - return queryset.exclude(device_bay_templates__isnull=value) + return queryset.exclude(devicebaytemplates__isnull=value) class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet): @@ -656,7 +656,7 @@ class DeviceFilterSet( return queryset.filter( Q(name__icontains=value) | Q(serial__icontains=value.strip()) | - Q(inventory_items__serial__icontains=value.strip()) | + Q(inventoryitems__serial__icontains=value.strip()) | Q(asset_tag__icontains=value.strip()) | Q(comments__icontains=value) ).distinct() @@ -698,7 +698,7 @@ class DeviceFilterSet( ) def _device_bays(self, queryset, name, value): - return queryset.exclude(device_bays__isnull=value) + return queryset.exclude(devicebays__isnull=value) class DeviceComponentFilterSet(django_filters.FilterSet): @@ -747,6 +747,7 @@ class DeviceComponentFilterSet(django_filters.FilterSet): return queryset return queryset.filter( Q(name__icontains=value) | + Q(label__icontains=value) | Q(description__icontains=value) ) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 281818895..dcc1f6418 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -59,7 +59,6 @@ def get_device_by_name_or_pk(name): class DeviceComponentFilterForm(BootstrapMixin, forms.Form): - field_order = [ 'q', 'region', 'site' ] @@ -127,7 +126,11 @@ class InterfaceCommonForm: }) -class LabeledComponentForm(BootstrapMixin, forms.Form): +class ComponentForm(BootstrapMixin, forms.Form): + """ + Subclass this form when facilitating the creation of one or more device component or component templates based on + a name pattern. + """ name_pattern = ExpandableNameField( label='Name' ) @@ -1033,7 +1036,7 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm): # Device component templates # -class ComponentTemplateCreateForm(LabeledComponentForm): +class ComponentTemplateCreateForm(ComponentForm): """ Base form for the creation of device component templates. """ @@ -1350,7 +1353,7 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm): class Meta: model = FrontPortTemplate fields = [ - 'device_type', 'name', 'type', 'rear_port', 'rear_port_position', 'description', + 'device_type', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', ] widgets = { 'device_type': forms.HiddenInput(), @@ -1389,7 +1392,7 @@ class FrontPortTemplateCreateForm(ComponentTemplateCreateForm): # Determine which rear port positions are occupied. These will be excluded from the list of available mappings. occupied_port_positions = [ (front_port.rear_port_id, front_port.rear_port_position) - for front_port in device_type.frontport_templates.all() + for front_port in device_type.frontporttemplates.all() ] # Populate rear port choices @@ -1430,6 +1433,10 @@ class FrontPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm): queryset=FrontPortTemplate.objects.all(), widget=forms.MultipleHiddenInput() ) + label = forms.CharField( + max_length=64, + required=False + ) type = forms.ChoiceField( choices=add_blank_choice(PortTypeChoices), required=False, @@ -1448,7 +1455,7 @@ class RearPortTemplateForm(BootstrapMixin, forms.ModelForm): class Meta: model = RearPortTemplate fields = [ - 'device_type', 'name', 'type', 'positions', 'description', + 'device_type', 'name', 'label', 'type', 'positions', 'description', ] widgets = { 'device_type': forms.HiddenInput(), @@ -1474,6 +1481,10 @@ class RearPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm): queryset=RearPortTemplate.objects.all(), widget=forms.MultipleHiddenInput() ) + label = forms.CharField( + max_length=64, + required=False + ) type = forms.ChoiceField( choices=add_blank_choice(PortTypeChoices), required=False, @@ -2248,7 +2259,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt # Device components # -class ComponentCreateForm(LabeledComponentForm): +class ComponentCreateForm(ComponentForm): """ Base form for the creation of device components. """ @@ -2261,7 +2272,7 @@ class ComponentCreateForm(LabeledComponentForm): ) -class DeviceBulkAddComponentForm(LabeledComponentForm): +class DeviceBulkAddComponentForm(ComponentForm): pk = forms.ModelMultipleChoiceField( queryset=Device.objects.all(), widget=forms.MultipleHiddenInput() @@ -2967,13 +2978,12 @@ class InterfaceCSVForm(CSVModelForm): super().__init__(*args, **kwargs) # Limit LAG choices to interfaces belonging to this device (or VC master) + device = None if self.is_bound and 'device' in self.data: try: device = self.fields['device'].to_python(self.data['device']) except forms.ValidationError: - device = None - else: - device = self.instance.device + pass if device: self.fields['lag'].queryset = Interface.objects.filter( @@ -3013,7 +3023,7 @@ class FrontPortForm(BootstrapMixin, forms.ModelForm): class Meta: model = FrontPort fields = [ - 'device', 'name', 'type', 'rear_port', 'rear_port_position', 'description', 'tags', + 'device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'tags', ] widgets = { 'device': forms.HiddenInput(), @@ -3094,14 +3104,14 @@ class FrontPortCreateForm(ComponentCreateForm): # class FrontPortBulkCreateForm( -# form_from_model(FrontPort, ['type', 'description', 'tags']), +# form_from_model(FrontPort, ['label', 'type', 'description', 'tags']), # DeviceBulkAddComponentForm # ): # pass class FrontPortBulkEditForm( - form_from_model(FrontPort, ['type', 'description']), + form_from_model(FrontPort, ['label', 'type', 'description']), BootstrapMixin, AddRemoveTagsForm, BulkEditForm @@ -3112,9 +3122,7 @@ class FrontPortBulkEditForm( ) class Meta: - nullable_fields = [ - 'description', - ] + nullable_fields = ('label', 'description') class FrontPortCSVForm(CSVModelForm): @@ -3185,7 +3193,7 @@ class RearPortForm(BootstrapMixin, forms.ModelForm): class Meta: model = RearPort fields = [ - 'device', 'name', 'type', 'positions', 'description', 'tags', + 'device', 'name', 'label', 'type', 'positions', 'description', 'tags', ] widgets = { 'device': forms.HiddenInput(), @@ -3210,14 +3218,14 @@ class RearPortCreateForm(ComponentCreateForm): class RearPortBulkCreateForm( - form_from_model(RearPort, ['type', 'positions', 'description', 'tags']), + form_from_model(RearPort, ['label', 'type', 'positions', 'description', 'tags']), DeviceBulkAddComponentForm ): pass class RearPortBulkEditForm( - form_from_model(RearPort, ['type', 'description']), + form_from_model(RearPort, ['label', 'type', 'description']), BootstrapMixin, AddRemoveTagsForm, BulkEditForm @@ -3228,9 +3236,7 @@ class RearPortBulkEditForm( ) class Meta: - nullable_fields = [ - 'description', - ] + nullable_fields = ('label', 'description') class RearPortCSVForm(CSVModelForm): @@ -3392,17 +3398,11 @@ class InventoryItemForm(BootstrapMixin, forms.ModelForm): class Meta: model = InventoryItem fields = [ - 'name', 'device', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'tags', + 'name', 'label', 'device', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'tags', ] -class InventoryItemCreateForm(BootstrapMixin, forms.Form): - device = DynamicModelChoiceField( - queryset=Device.objects.prefetch_related('device_type__manufacturer') - ) - name_pattern = ExpandableNameField( - label='Name' - ) +class InventoryItemCreateForm(ComponentCreateForm): manufacturer = DynamicModelChoiceField( queryset=Manufacturer.objects.all(), required=False @@ -3443,7 +3443,7 @@ class InventoryItemCSVForm(CSVModelForm): class InventoryItemBulkCreateForm( - form_from_model(InventoryItem, ['manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'tags']), + form_from_model(InventoryItem, ['label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'tags']), DeviceBulkAddComponentForm ): tags = DynamicModelMultipleChoiceField( @@ -3452,68 +3452,27 @@ class InventoryItemBulkCreateForm( ) -class InventoryItemBulkEditForm(BootstrapMixin, BulkEditForm): +class InventoryItemBulkEditForm( + form_from_model(InventoryItem, ['label', 'manufacturer', 'part_id', 'description']), + BootstrapMixin, + AddRemoveTagsForm, + BulkEditForm +): pk = forms.ModelMultipleChoiceField( queryset=InventoryItem.objects.all(), widget=forms.MultipleHiddenInput() ) - device = DynamicModelChoiceField( - queryset=Device.objects.all(), - required=False - ) manufacturer = DynamicModelChoiceField( queryset=Manufacturer.objects.all(), required=False ) - part_id = forms.CharField( - max_length=50, - required=False, - label='Part ID' - ) - description = forms.CharField( - max_length=100, - required=False - ) class Meta: - nullable_fields = [ - 'manufacturer', 'part_id', 'description', - ] + nullable_fields = ('label', 'manufacturer', 'part_id', 'description') -class InventoryItemFilterForm(BootstrapMixin, forms.Form): +class InventoryItemFilterForm(DeviceComponentFilterForm): model = InventoryItem - q = forms.CharField( - required=False, - label='Search' - ) - region = DynamicModelMultipleChoiceField( - queryset=Region.objects.all(), - to_field_name='slug', - required=False, - widget=APISelectMultiple( - value_field="slug", - filter_for={ - 'site': 'region' - } - ) - ) - site = DynamicModelMultipleChoiceField( - queryset=Site.objects.all(), - to_field_name='slug', - required=False, - widget=APISelectMultiple( - value_field="slug", - filter_for={ - 'device_id': 'site' - } - ) - ) - device_id = DynamicModelMultipleChoiceField( - queryset=Device.objects.all(), - required=False, - label='Device' - ) manufacturer = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), to_field_name='slug', @@ -3522,6 +3481,12 @@ class InventoryItemFilterForm(BootstrapMixin, forms.Form): value_field="slug", ) ) + serial = forms.CharField( + required=False + ) + asset_tag = forms.CharField( + required=False + ) discovered = forms.NullBooleanField( required=False, widget=StaticSelect2( diff --git a/netbox/dcim/migrations/0107_component_labels.py b/netbox/dcim/migrations/0107_component_labels.py index 8e5ab8156..c89bfc0b6 100644 --- a/netbox/dcim/migrations/0107_component_labels.py +++ b/netbox/dcim/migrations/0107_component_labels.py @@ -1,5 +1,3 @@ -# Generated by Django 3.0.7 on 2020-06-04 20:37 - from django.db import migrations, models @@ -10,16 +8,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='interface', - name='label', - field=models.CharField(blank=True, max_length=64), - ), - migrations.AddField( - model_name='interfacetemplate', - name='label', - field=models.CharField(blank=True, max_length=64), - ), migrations.AddField( model_name='consoleport', name='label', @@ -40,6 +28,41 @@ class Migration(migrations.Migration): name='label', field=models.CharField(blank=True, max_length=64), ), + migrations.AddField( + model_name='devicebay', + name='label', + field=models.CharField(blank=True, max_length=64), + ), + migrations.AddField( + model_name='devicebaytemplate', + name='label', + field=models.CharField(blank=True, max_length=64), + ), + migrations.AddField( + model_name='frontport', + name='label', + field=models.CharField(blank=True, max_length=64), + ), + migrations.AddField( + model_name='frontporttemplate', + name='label', + field=models.CharField(blank=True, max_length=64), + ), + migrations.AddField( + model_name='interface', + name='label', + field=models.CharField(blank=True, max_length=64), + ), + migrations.AddField( + model_name='interfacetemplate', + name='label', + field=models.CharField(blank=True, max_length=64), + ), + migrations.AddField( + model_name='inventoryitem', + name='label', + field=models.CharField(blank=True, max_length=64), + ), migrations.AddField( model_name='poweroutlet', name='label', @@ -61,12 +84,12 @@ class Migration(migrations.Migration): field=models.CharField(blank=True, max_length=64), ), migrations.AddField( - model_name='devicebay', + model_name='rearport', name='label', field=models.CharField(blank=True, max_length=64), ), migrations.AddField( - model_name='devicebaytemplate', + model_name='rearporttemplate', name='label', field=models.CharField(blank=True, max_length=64), ), diff --git a/netbox/dcim/migrations/0109_interface_remove_vm.py b/netbox/dcim/migrations/0109_interface_remove_vm.py index 97a84a43e..6e1d727b0 100644 --- a/netbox/dcim/migrations/0109_interface_remove_vm.py +++ b/netbox/dcim/migrations/0109_interface_remove_vm.py @@ -1,6 +1,5 @@ -# Generated by Django 3.0.6 on 2020-06-22 16:03 - -from django.db import migrations +from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -15,4 +14,11 @@ class Migration(migrations.Migration): model_name='interface', name='virtual_machine', ), + # device is now a required field + migrations.AlterField( + model_name='interface', + name='device', + field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='dcim.Device'), + preserve_default=False, + ), ] diff --git a/netbox/dcim/migrations/0112_standardize_components.py b/netbox/dcim/migrations/0112_standardize_components.py new file mode 100644 index 000000000..1a3465e02 --- /dev/null +++ b/netbox/dcim/migrations/0112_standardize_components.py @@ -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'), + ), + ] diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index 1aeedf1e5..db8fd8cf3 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -678,7 +678,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel): 'device_type__manufacturer', 'device_role' ).annotate( - devicebay_count=Count('device_bays') + devicebay_count=Count('devicebays') ).exclude( pk=exclude ).filter( @@ -1049,23 +1049,23 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): )) # Component templates - if self.consoleport_templates.exists(): + if self.consoleporttemplates.exists(): data['console-ports'] = [ { 'name': c.name, 'type': c.type, } - for c in self.consoleport_templates.all() + for c in self.consoleporttemplates.all() ] - if self.consoleserverport_templates.exists(): + if self.consoleserverporttemplates.exists(): data['console-server-ports'] = [ { 'name': c.name, 'type': c.type, } - for c in self.consoleserverport_templates.all() + for c in self.consoleserverporttemplates.all() ] - if self.powerport_templates.exists(): + if self.powerporttemplates.exists(): data['power-ports'] = [ { 'name': c.name, @@ -1073,9 +1073,9 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): 'maximum_draw': c.maximum_draw, 'allocated_draw': c.allocated_draw, } - for c in self.powerport_templates.all() + for c in self.powerporttemplates.all() ] - if self.poweroutlet_templates.exists(): + if self.poweroutlettemplates.exists(): data['power-outlets'] = [ { 'name': c.name, @@ -1083,18 +1083,18 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): 'power_port': c.power_port.name if c.power_port else None, 'feed_leg': c.feed_leg, } - for c in self.poweroutlet_templates.all() + for c in self.poweroutlettemplates.all() ] - if self.interface_templates.exists(): + if self.interfacetemplates.exists(): data['interfaces'] = [ { 'name': c.name, 'type': c.type, 'mgmt_only': c.mgmt_only, } - for c in self.interface_templates.all() + for c in self.interfacetemplates.all() ] - if self.frontport_templates.exists(): + if self.frontporttemplates.exists(): data['front-ports'] = [ { 'name': c.name, @@ -1102,23 +1102,23 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): 'rear_port': c.rear_port.name, 'rear_port_position': c.rear_port_position, } - for c in self.frontport_templates.all() + for c in self.frontporttemplates.all() ] - if self.rearport_templates.exists(): + if self.rearporttemplates.exists(): data['rear-ports'] = [ { 'name': c.name, 'type': c.type, 'positions': c.positions, } - for c in self.rearport_templates.all() + for c in self.rearporttemplates.all() ] - if self.device_bay_templates.exists(): + if self.devicebaytemplates.exists(): data['device-bays'] = [ { 'name': c.name, } - for c in self.device_bay_templates.all() + for c in self.devicebaytemplates.all() ] return yaml.dump(dict(data), sort_keys=False) @@ -1159,7 +1159,7 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): if ( self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT - ) and self.device_bay_templates.count(): + ) and self.devicebaytemplates.count(): raise ValidationError({ 'subdevice_role': "Must delete all device bay templates associated with this device before " "declassifying it as a parent device." @@ -1634,28 +1634,28 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): # If this is a new Device, instantiate all of the related components per the DeviceType definition if is_new: ConsolePort.objects.bulk_create( - [x.instantiate(self) for x in self.device_type.consoleport_templates.unrestricted()] + [x.instantiate(self) for x in self.device_type.consoleporttemplates.unrestricted()] ) ConsoleServerPort.objects.bulk_create( - [x.instantiate(self) for x in self.device_type.consoleserverport_templates.unrestricted()] + [x.instantiate(self) for x in self.device_type.consoleserverporttemplates.unrestricted()] ) PowerPort.objects.bulk_create( - [x.instantiate(self) for x in self.device_type.powerport_templates.unrestricted()] + [x.instantiate(self) for x in self.device_type.powerporttemplates.unrestricted()] ) PowerOutlet.objects.bulk_create( - [x.instantiate(self) for x in self.device_type.poweroutlet_templates.unrestricted()] + [x.instantiate(self) for x in self.device_type.poweroutlettemplates.unrestricted()] ) Interface.objects.bulk_create( - [x.instantiate(self) for x in self.device_type.interface_templates.unrestricted()] + [x.instantiate(self) for x in self.device_type.interfacetemplates.unrestricted()] ) RearPort.objects.bulk_create( - [x.instantiate(self) for x in self.device_type.rearport_templates.unrestricted()] + [x.instantiate(self) for x in self.device_type.rearporttemplates.unrestricted()] ) FrontPort.objects.bulk_create( - [x.instantiate(self) for x in self.device_type.frontport_templates.unrestricted()] + [x.instantiate(self) for x in self.device_type.frontporttemplates.unrestricted()] ) DeviceBay.objects.bulk_create( - [x.instantiate(self) for x in self.device_type.device_bay_templates.unrestricted()] + [x.instantiate(self) for x in self.device_type.devicebaytemplates.unrestricted()] ) # Update Site and Rack assignment for any child Devices diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 1c2be0e5d..7e96a8016 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -27,6 +27,24 @@ __all__ = ( class ComponentTemplateModel(models.Model): + device_type = models.ForeignKey( + to='dcim.DeviceType', + on_delete=models.CASCADE, + related_name='%(class)ss' + ) + name = models.CharField( + max_length=64 + ) + _name = NaturalOrderingField( + target_field='name', + max_length=100, + blank=True + ) + label = models.CharField( + max_length=64, + blank=True, + help_text="Physical label" + ) description = models.CharField( max_length=200, blank=True @@ -68,24 +86,6 @@ class ConsolePortTemplate(ComponentTemplateModel): """ A template for a ConsolePort to be created for a new Device. """ - device_type = models.ForeignKey( - to='dcim.DeviceType', - on_delete=models.CASCADE, - related_name='consoleport_templates' - ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -108,24 +108,6 @@ class ConsoleServerPortTemplate(ComponentTemplateModel): """ A template for a ConsoleServerPort to be created for a new Device. """ - device_type = models.ForeignKey( - to='dcim.DeviceType', - on_delete=models.CASCADE, - related_name='consoleserverport_templates' - ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -148,24 +130,6 @@ class PowerPortTemplate(ComponentTemplateModel): """ A template for a PowerPort to be created for a new Device. """ - device_type = models.ForeignKey( - to='dcim.DeviceType', - on_delete=models.CASCADE, - related_name='powerport_templates' - ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) type = models.CharField( max_length=50, choices=PowerPortTypeChoices, @@ -202,24 +166,6 @@ class PowerOutletTemplate(ComponentTemplateModel): """ A template for a PowerOutlet to be created for a new Device. """ - device_type = models.ForeignKey( - to='dcim.DeviceType', - on_delete=models.CASCADE, - related_name='poweroutlet_templates' - ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) type = models.CharField( max_length=50, choices=PowerOutletTypeChoices, @@ -269,25 +215,13 @@ class InterfaceTemplate(ComponentTemplateModel): """ A template for a physical data interface on a new Device. """ - device_type = models.ForeignKey( - to='dcim.DeviceType', - on_delete=models.CASCADE, - related_name='interface_templates' - ) - name = models.CharField( - max_length=64 - ) + # Override ComponentTemplateModel._name to specify naturalize_interface function _name = NaturalOrderingField( target_field='name', naturalize_function=naturalize_interface, max_length=100, blank=True ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) type = models.CharField( max_length=50, choices=InterfaceTypeChoices @@ -314,19 +248,6 @@ class FrontPortTemplate(ComponentTemplateModel): """ Template for a pass-through port on the front of a new Device. """ - device_type = models.ForeignKey( - to='dcim.DeviceType', - on_delete=models.CASCADE, - related_name='frontport_templates' - ) - name = models.CharField( - max_length=64 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=PortTypeChoices @@ -348,9 +269,6 @@ class FrontPortTemplate(ComponentTemplateModel): ('rear_port', 'rear_port_position'), ) - def __str__(self): - return self.name - def clean(self): # Validate rear port assignment @@ -385,19 +303,6 @@ class RearPortTemplate(ComponentTemplateModel): """ Template for a pass-through port on the rear of a new Device. """ - device_type = models.ForeignKey( - to='dcim.DeviceType', - on_delete=models.CASCADE, - related_name='rearport_templates' - ) - name = models.CharField( - max_length=64 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=PortTypeChoices @@ -411,9 +316,6 @@ class RearPortTemplate(ComponentTemplateModel): ordering = ('device_type', '_name') unique_together = ('device_type', 'name') - def __str__(self): - return self.name - def instantiate(self, device): return RearPort( device=device, @@ -427,25 +329,6 @@ class DeviceBayTemplate(ComponentTemplateModel): """ A template for a DeviceBay to be created for a new parent Device. """ - device_type = models.ForeignKey( - to='dcim.DeviceType', - on_delete=models.CASCADE, - related_name='device_bay_templates' - ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) - class Meta: ordering = ('device_type', '_name') unique_together = ('device_type', 'name') diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index aecf57544..1ebb0c4d5 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -36,6 +36,24 @@ __all__ = ( class ComponentModel(models.Model): + device = models.ForeignKey( + to='dcim.Device', + on_delete=models.CASCADE, + related_name='%(class)ss' + ) + name = models.CharField( + max_length=64 + ) + _name = NaturalOrderingField( + target_field='name', + max_length=100, + blank=True + ) + label = models.CharField( + max_length=64, + blank=True, + help_text="Physical label" + ) description = models.CharField( max_length=200, blank=True @@ -233,24 +251,6 @@ class ConsolePort(CableTermination, ComponentModel): """ A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts. """ - device = models.ForeignKey( - to='dcim.Device', - on_delete=models.CASCADE, - related_name='consoleports' - ) - name = models.CharField( - max_length=50 - ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -270,7 +270,7 @@ class ConsolePort(CableTermination, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'type', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'description'] class Meta: ordering = ('device', '_name') @@ -283,6 +283,7 @@ class ConsolePort(CableTermination, ComponentModel): return ( self.device.identifier, self.name, + self.label, self.type, self.description, ) @@ -297,24 +298,6 @@ class ConsoleServerPort(CableTermination, ComponentModel): """ A physical port within a Device (typically a designated console server) which provides access to ConsolePorts. """ - device = models.ForeignKey( - to='dcim.Device', - on_delete=models.CASCADE, - related_name='consoleserverports' - ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -327,7 +310,7 @@ class ConsoleServerPort(CableTermination, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'type', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'description'] class Meta: ordering = ('device', '_name') @@ -340,6 +323,7 @@ class ConsoleServerPort(CableTermination, ComponentModel): return ( self.device.identifier, self.name, + self.label, self.type, self.description, ) @@ -354,24 +338,6 @@ class PowerPort(CableTermination, ComponentModel): """ A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets. """ - device = models.ForeignKey( - to='dcim.Device', - on_delete=models.CASCADE, - related_name='powerports' - ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) type = models.CharField( max_length=50, choices=PowerPortTypeChoices, @@ -410,7 +376,7 @@ class PowerPort(CableTermination, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'type', 'maximum_draw', 'allocated_draw', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description'] class Meta: ordering = ('device', '_name') @@ -423,6 +389,7 @@ class PowerPort(CableTermination, ComponentModel): return ( self.device.identifier, self.name, + self.label, self.get_type_display(), self.maximum_draw, self.allocated_draw, @@ -519,24 +486,6 @@ class PowerOutlet(CableTermination, ComponentModel): """ A physical power outlet (output) within a Device which provides power to a PowerPort. """ - device = models.ForeignKey( - to='dcim.Device', - on_delete=models.CASCADE, - related_name='poweroutlets' - ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) type = models.CharField( max_length=50, choices=PowerOutletTypeChoices, @@ -562,7 +511,7 @@ class PowerOutlet(CableTermination, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'type', 'power_port', 'feed_leg', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description'] class Meta: ordering = ('device', '_name') @@ -575,6 +524,7 @@ class PowerOutlet(CableTermination, ComponentModel): return ( self.device.identifier, self.name, + self.label, self.get_type_display(), self.power_port.name if self.power_port else None, self.get_feed_leg_display(), @@ -595,15 +545,9 @@ class PowerOutlet(CableTermination, ComponentModel): # class BaseInterface(models.Model): - name = models.CharField( - max_length=64 - ) - _name = NaturalOrderingField( - target_field='name', - naturalize_function=naturalize_interface, - max_length=100, - blank=True - ) + """ + Abstract base class for fields shared by dcim.Interface and virtualization.VMInterface. + """ enabled = models.BooleanField( default=True ) @@ -633,18 +577,13 @@ class Interface(CableTermination, ComponentModel, BaseInterface): """ A network interface within a Device. A physical Interface can connect to exactly one other Interface. """ - device = models.ForeignKey( - to='Device', - on_delete=models.CASCADE, - related_name='interfaces', - null=True, + # Override ComponentModel._name to specify naturalize_interface function + _name = NaturalOrderingField( + target_field='name', + naturalize_function=naturalize_interface, + max_length=100, blank=True ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) _connected_interface = models.OneToOneField( to='self', on_delete=models.SET_NULL, @@ -703,7 +642,7 @@ class Interface(CableTermination, ComponentModel, BaseInterface): tags = TaggableManager(through=TaggedItem) csv_headers = [ - 'device', 'name', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only', 'description', 'mode', + 'device', 'name', 'label', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only', 'description', 'mode', ] class Meta: @@ -717,6 +656,7 @@ class Interface(CableTermination, ComponentModel, BaseInterface): return ( self.device.identifier if self.device else None, self.name, + self.label, self.lag.name if self.lag else None, self.get_type_display(), self.enabled, @@ -849,19 +789,6 @@ class FrontPort(CableTermination, ComponentModel): """ A pass-through port on the front of a Device. """ - device = models.ForeignKey( - to='dcim.Device', - on_delete=models.CASCADE, - related_name='frontports' - ) - name = models.CharField( - max_length=64 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=PortTypeChoices @@ -877,7 +804,7 @@ class FrontPort(CableTermination, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'type', 'rear_port', 'rear_port_position', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description'] class Meta: ordering = ('device', '_name') @@ -886,9 +813,6 @@ class FrontPort(CableTermination, ComponentModel): ('rear_port', 'rear_port_position'), ) - def __str__(self): - return self.name - def get_absolute_url(self): return reverse('dcim:frontport', kwargs={'pk': self.pk}) @@ -896,6 +820,7 @@ class FrontPort(CableTermination, ComponentModel): return ( self.device.identifier, self.name, + self.label, self.get_type_display(), self.rear_port.name, self.rear_port_position, @@ -924,19 +849,6 @@ class RearPort(CableTermination, ComponentModel): """ A pass-through port on the rear of a Device. """ - device = models.ForeignKey( - to='dcim.Device', - on_delete=models.CASCADE, - related_name='rearports' - ) - name = models.CharField( - max_length=64 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=PortTypeChoices @@ -947,15 +859,12 @@ class RearPort(CableTermination, ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'type', 'positions', 'description'] + csv_headers = ['device', 'name', 'label', 'type', 'positions', 'description'] class Meta: ordering = ('device', '_name') unique_together = ('device', 'name') - def __str__(self): - return self.name - def get_absolute_url(self): return reverse('dcim:rearport', kwargs={'pk': self.pk}) @@ -963,6 +872,7 @@ class RearPort(CableTermination, ComponentModel): return ( self.device.identifier, self.name, + self.label, self.get_type_display(), self.positions, self.description, @@ -978,25 +888,6 @@ class DeviceBay(ComponentModel): """ An empty space within a Device which can house a child device """ - device = models.ForeignKey( - to='dcim.Device', - on_delete=models.CASCADE, - related_name='device_bays' - ) - name = models.CharField( - max_length=50, - verbose_name='Name' - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) installed_device = models.OneToOneField( to='dcim.Device', on_delete=models.SET_NULL, @@ -1006,17 +897,12 @@ class DeviceBay(ComponentModel): ) tags = TaggableManager(through=TaggedItem) - csv_headers = ['device', 'name', 'installed_device', 'description'] + csv_headers = ['device', 'name', 'label', 'installed_device', 'description'] class Meta: ordering = ('device', '_name') unique_together = ('device', 'name') - def __str__(self): - if self.label: - return '{} - {} ({})'.format(self.device.name, self.name, self.label) - return '{} - {}'.format(self.device.name, self.name) - def get_absolute_url(self): return reverse('dcim:devicebay', kwargs={'pk': self.pk}) @@ -1024,6 +910,7 @@ class DeviceBay(ComponentModel): return ( self.device.identifier, self.name, + self.label, self.installed_device.identifier if self.installed_device else None, self.description, ) @@ -1061,11 +948,6 @@ class InventoryItem(ComponentModel): An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply. InventoryItems are used only for inventory purposes. """ - device = models.ForeignKey( - to='dcim.Device', - on_delete=models.CASCADE, - related_name='inventory_items' - ) parent = models.ForeignKey( to='self', on_delete=models.CASCADE, @@ -1073,15 +955,6 @@ class InventoryItem(ComponentModel): blank=True, null=True ) - name = models.CharField( - max_length=50, - verbose_name='Name' - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) manufacturer = models.ForeignKey( to='dcim.Manufacturer', on_delete=models.PROTECT, @@ -1116,16 +989,13 @@ class InventoryItem(ComponentModel): tags = TaggableManager(through=TaggedItem) csv_headers = [ - 'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', + 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', ] class Meta: ordering = ('device__id', 'parent__id', '_name') unique_together = ('device', 'parent', 'name') - def __str__(self): - return self.name - def get_absolute_url(self): return reverse('dcim:inventoryitem', kwargs={'pk': self.pk}) @@ -1133,6 +1003,7 @@ class InventoryItem(ComponentModel): return ( self.device.name or '{{{}}}'.format(self.device.pk), self.name, + self.label, self.manufacturer.name if self.manufacturer else None, self.part_id, self.serial, diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index f258df221..442c0cf47 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -110,21 +110,6 @@ POWERPANEL_POWERFEED_COUNT = """ """ -def get_component_template_actions(model_name): - return """ - {{% if perms.dcim.change_{model_name} %}} - - - - {{% endif %}} - {{% if perms.dcim.delete_{model_name} %}} - - - - {{% endif %}} - """.format(model_name=model_name).strip() - - # # Regions # @@ -401,10 +386,9 @@ class ComponentTemplateTable(BaseTable): class ConsolePortTemplateTable(ComponentTemplateTable): - actions = tables.TemplateColumn( - template_code=get_component_template_actions('consoleporttemplate'), - attrs={'td': {'class': 'text-right noprint'}}, - verbose_name='' + actions = ButtonsColumn( + model=ConsolePortTemplate, + buttons=('edit', 'delete') ) class Meta(BaseTable.Meta): @@ -414,10 +398,9 @@ class ConsolePortTemplateTable(ComponentTemplateTable): class ConsoleServerPortTemplateTable(ComponentTemplateTable): - actions = tables.TemplateColumn( - template_code=get_component_template_actions('consoleserverporttemplate'), - attrs={'td': {'class': 'text-right noprint'}}, - verbose_name='' + actions = ButtonsColumn( + model=ConsoleServerPortTemplate, + buttons=('edit', 'delete') ) class Meta(BaseTable.Meta): @@ -427,10 +410,9 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable): class PowerPortTemplateTable(ComponentTemplateTable): - actions = tables.TemplateColumn( - template_code=get_component_template_actions('powerporttemplate'), - attrs={'td': {'class': 'text-right noprint'}}, - verbose_name='' + actions = ButtonsColumn( + model=PowerPortTemplate, + buttons=('edit', 'delete') ) class Meta(BaseTable.Meta): @@ -440,10 +422,9 @@ class PowerPortTemplateTable(ComponentTemplateTable): class PowerOutletTemplateTable(ComponentTemplateTable): - actions = tables.TemplateColumn( - template_code=get_component_template_actions('poweroutlettemplate'), - attrs={'td': {'class': 'text-right noprint'}}, - verbose_name='' + actions = ButtonsColumn( + model=PowerOutletTemplate, + buttons=('edit', 'delete') ) class Meta(BaseTable.Meta): @@ -456,10 +437,9 @@ class InterfaceTemplateTable(ComponentTemplateTable): mgmt_only = BooleanColumn( verbose_name='Management Only' ) - actions = tables.TemplateColumn( - template_code=get_component_template_actions('interfacetemplate'), - attrs={'td': {'class': 'text-right noprint'}}, - verbose_name='' + actions = ButtonsColumn( + model=InterfaceTemplate, + buttons=('edit', 'delete') ) class Meta(BaseTable.Meta): @@ -472,10 +452,9 @@ class FrontPortTemplateTable(ComponentTemplateTable): rear_port_position = tables.Column( verbose_name='Position' ) - actions = tables.TemplateColumn( - template_code=get_component_template_actions('frontporttemplate'), - attrs={'td': {'class': 'text-right noprint'}}, - verbose_name='' + actions = ButtonsColumn( + model=FrontPortTemplate, + buttons=('edit', 'delete') ) class Meta(BaseTable.Meta): @@ -485,10 +464,9 @@ class FrontPortTemplateTable(ComponentTemplateTable): class RearPortTemplateTable(ComponentTemplateTable): - actions = tables.TemplateColumn( - template_code=get_component_template_actions('rearporttemplate'), - attrs={'td': {'class': 'text-right noprint'}}, - verbose_name='' + actions = ButtonsColumn( + model=RearPortTemplate, + buttons=('edit', 'delete') ) class Meta(BaseTable.Meta): @@ -498,10 +476,9 @@ class RearPortTemplateTable(ComponentTemplateTable): class DeviceBayTemplateTable(ComponentTemplateTable): - actions = tables.TemplateColumn( - template_code=get_component_template_actions('devicebaytemplate'), - attrs={'td': {'class': 'text-right noprint'}}, - verbose_name='' + actions = ButtonsColumn( + model=DeviceBayTemplate, + buttons=('edit', 'delete') ) class Meta(BaseTable.Meta): @@ -784,9 +761,10 @@ class InventoryItemTable(DeviceComponentTable): class Meta(DeviceComponentTable.Meta): model = InventoryItem fields = ( - 'pk', 'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered' + 'pk', 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', + 'discovered', ) - default_columns = ('pk', 'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag') + default_columns = ('pk', 'device', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag') # diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 451d9d9a9..17ac672d9 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -1,5 +1,6 @@ from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType +from django.test import override_settings from django.urls import reverse from rest_framework import status @@ -131,6 +132,7 @@ class SiteTest(APIViewTestCases.APIViewTestCase): }, ] + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) def test_get_site_graphs(self): """ Test retrieval of Graphs assigned to Sites. @@ -900,6 +902,7 @@ class DeviceTest(APIViewTestCases.APIViewTestCase): }, ] + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) def test_get_device_graphs(self): """ Test retrieval of Graphs assigned to Devices. @@ -1156,6 +1159,7 @@ class InterfaceTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase }, ] + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) def test_get_interface_graphs(self): """ Test retrieval of Graphs assigned to Devices. diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 799d186b2..ef7ff993f 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -4,6 +4,7 @@ import pytz import yaml from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType +from django.test import override_settings from django.urls import reverse from netaddr import EUI @@ -376,6 +377,7 @@ class DeviceTypeTestCase( 'is_full_depth': False, } + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) def test_import_objects(self): """ Custom import test for YAML-based imports (versus CSV) @@ -479,45 +481,45 @@ device-bays: self.assertEqual(dt.comments, 'test comment') # Verify all of the components were created - self.assertEqual(dt.consoleport_templates.count(), 3) + self.assertEqual(dt.consoleporttemplates.count(), 3) cp1 = ConsolePortTemplate.objects.first() self.assertEqual(cp1.name, 'Console Port 1') self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9) - self.assertEqual(dt.consoleserverport_templates.count(), 3) + self.assertEqual(dt.consoleserverporttemplates.count(), 3) csp1 = ConsoleServerPortTemplate.objects.first() self.assertEqual(csp1.name, 'Console Server Port 1') self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45) - self.assertEqual(dt.powerport_templates.count(), 3) + self.assertEqual(dt.powerporttemplates.count(), 3) pp1 = PowerPortTemplate.objects.first() self.assertEqual(pp1.name, 'Power Port 1') self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14) - self.assertEqual(dt.poweroutlet_templates.count(), 3) + self.assertEqual(dt.poweroutlettemplates.count(), 3) po1 = PowerOutletTemplate.objects.first() self.assertEqual(po1.name, 'Power Outlet 1') self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13) self.assertEqual(po1.power_port, pp1) self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A) - self.assertEqual(dt.interface_templates.count(), 3) + self.assertEqual(dt.interfacetemplates.count(), 3) iface1 = InterfaceTemplate.objects.first() self.assertEqual(iface1.name, 'Interface 1') self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED) self.assertTrue(iface1.mgmt_only) - self.assertEqual(dt.rearport_templates.count(), 3) + self.assertEqual(dt.rearporttemplates.count(), 3) rp1 = RearPortTemplate.objects.first() self.assertEqual(rp1.name, 'Rear Port 1') - self.assertEqual(dt.frontport_templates.count(), 3) + self.assertEqual(dt.frontporttemplates.count(), 3) fp1 = FrontPortTemplate.objects.first() self.assertEqual(fp1.name, 'Front Port 1') self.assertEqual(fp1.rear_port, rp1) self.assertEqual(fp1.rear_port_position, 1) - self.assertEqual(dt.device_bay_templates.count(), 3) + self.assertEqual(dt.devicebaytemplates.count(), 3) db1 = DeviceBayTemplate.objects.first() self.assertEqual(db1.name, 'Device Bay 1') diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 7af91f0ae..63ae5d2a4 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -98,6 +98,7 @@ urlpatterns = [ # Console port templates path('console-port-templates/add/', views.ConsolePortTemplateCreateView.as_view(), name='consoleporttemplate_add'), path('console-port-templates/edit/', views.ConsolePortTemplateBulkEditView.as_view(), name='consoleporttemplate_bulk_edit'), + path('console-port-templates/rename/', views.ConsolePortTemplateBulkRenameView.as_view(), name='consoleporttemplate_bulk_rename'), path('console-port-templates/delete/', views.ConsolePortTemplateBulkDeleteView.as_view(), name='consoleporttemplate_bulk_delete'), path('console-port-templates//edit/', views.ConsolePortTemplateEditView.as_view(), name='consoleporttemplate_edit'), path('console-port-templates//delete/', views.ConsolePortTemplateDeleteView.as_view(), name='consoleporttemplate_delete'), @@ -105,6 +106,7 @@ urlpatterns = [ # Console server port templates path('console-server-port-templates/add/', views.ConsoleServerPortTemplateCreateView.as_view(), name='consoleserverporttemplate_add'), path('console-server-port-templates/edit/', views.ConsoleServerPortTemplateBulkEditView.as_view(), name='consoleserverporttemplate_bulk_edit'), + path('console-server-port-templates/rename/', views.ConsoleServerPortTemplateBulkRenameView.as_view(), name='consoleserverporttemplate_bulk_rename'), path('console-server-port-templates/delete/', views.ConsoleServerPortTemplateBulkDeleteView.as_view(), name='consoleserverporttemplate_bulk_delete'), path('console-server-port-templates//edit/', views.ConsoleServerPortTemplateEditView.as_view(), name='consoleserverporttemplate_edit'), path('console-server-port-templates//delete/', views.ConsoleServerPortTemplateDeleteView.as_view(), name='consoleserverporttemplate_delete'), @@ -112,6 +114,7 @@ urlpatterns = [ # Power port templates path('power-port-templates/add/', views.PowerPortTemplateCreateView.as_view(), name='powerporttemplate_add'), path('power-port-templates/edit/', views.PowerPortTemplateBulkEditView.as_view(), name='powerporttemplate_bulk_edit'), + path('power-port-templates/rename/', views.PowerPortTemplateBulkRenameView.as_view(), name='powerporttemplate_bulk_rename'), path('power-port-templates/delete/', views.PowerPortTemplateBulkDeleteView.as_view(), name='powerporttemplate_bulk_delete'), path('power-port-templates//edit/', views.PowerPortTemplateEditView.as_view(), name='powerporttemplate_edit'), path('power-port-templates//delete/', views.PowerPortTemplateDeleteView.as_view(), name='powerporttemplate_delete'), @@ -119,6 +122,7 @@ urlpatterns = [ # Power outlet templates path('power-outlet-templates/add/', views.PowerOutletTemplateCreateView.as_view(), name='poweroutlettemplate_add'), path('power-outlet-templates/edit/', views.PowerOutletTemplateBulkEditView.as_view(), name='poweroutlettemplate_bulk_edit'), + path('power-outlet-templates/rename/', views.PowerOutletTemplateBulkRenameView.as_view(), name='poweroutlettemplate_bulk_rename'), path('power-outlet-templates/delete/', views.PowerOutletTemplateBulkDeleteView.as_view(), name='poweroutlettemplate_bulk_delete'), path('power-outlet-templates//edit/', views.PowerOutletTemplateEditView.as_view(), name='poweroutlettemplate_edit'), path('power-outlet-templates//delete/', views.PowerOutletTemplateDeleteView.as_view(), name='poweroutlettemplate_delete'), @@ -126,6 +130,7 @@ urlpatterns = [ # Interface templates path('interface-templates/add/', views.InterfaceTemplateCreateView.as_view(), name='interfacetemplate_add'), path('interface-templates/edit/', views.InterfaceTemplateBulkEditView.as_view(), name='interfacetemplate_bulk_edit'), + path('interface-templates/rename/', views.InterfaceTemplateBulkRenameView.as_view(), name='interfacetemplate_bulk_rename'), path('interface-templates/delete/', views.InterfaceTemplateBulkDeleteView.as_view(), name='interfacetemplate_bulk_delete'), path('interface-templates//edit/', views.InterfaceTemplateEditView.as_view(), name='interfacetemplate_edit'), path('interface-templates//delete/', views.InterfaceTemplateDeleteView.as_view(), name='interfacetemplate_delete'), @@ -133,6 +138,7 @@ urlpatterns = [ # Front port templates path('front-port-templates/add/', views.FrontPortTemplateCreateView.as_view(), name='frontporttemplate_add'), path('front-port-templates/edit/', views.FrontPortTemplateBulkEditView.as_view(), name='frontporttemplate_bulk_edit'), + path('front-port-templates/rename/', views.FrontPortTemplateBulkRenameView.as_view(), name='frontporttemplate_bulk_rename'), path('front-port-templates/delete/', views.FrontPortTemplateBulkDeleteView.as_view(), name='frontporttemplate_bulk_delete'), path('front-port-templates//edit/', views.FrontPortTemplateEditView.as_view(), name='frontporttemplate_edit'), path('front-port-templates//delete/', views.FrontPortTemplateDeleteView.as_view(), name='frontporttemplate_delete'), @@ -140,6 +146,7 @@ urlpatterns = [ # Rear port templates path('rear-port-templates/add/', views.RearPortTemplateCreateView.as_view(), name='rearporttemplate_add'), path('rear-port-templates/edit/', views.RearPortTemplateBulkEditView.as_view(), name='rearporttemplate_bulk_edit'), + path('rear-port-templates/rename/', views.RearPortTemplateBulkRenameView.as_view(), name='rearporttemplate_bulk_rename'), path('rear-port-templates/delete/', views.RearPortTemplateBulkDeleteView.as_view(), name='rearporttemplate_bulk_delete'), path('rear-port-templates//edit/', views.RearPortTemplateEditView.as_view(), name='rearporttemplate_edit'), path('rear-port-templates//delete/', views.RearPortTemplateDeleteView.as_view(), name='rearporttemplate_delete'), @@ -147,6 +154,7 @@ urlpatterns = [ # Device bay templates path('device-bay-templates/add/', views.DeviceBayTemplateCreateView.as_view(), name='devicebaytemplate_add'), path('device-bay-templates/edit/', views.DeviceBayTemplateBulkEditView.as_view(), name='devicebaytemplate_bulk_edit'), + path('device-bay-templates/rename/', views.DeviceBayTemplateBulkRenameView.as_view(), name='devicebaytemplate_bulk_rename'), path('device-bay-templates/delete/', views.DeviceBayTemplateBulkDeleteView.as_view(), name='devicebaytemplate_bulk_delete'), path('device-bay-templates//edit/', views.DeviceBayTemplateEditView.as_view(), name='devicebaytemplate_edit'), path('device-bay-templates//delete/', views.DeviceBayTemplateDeleteView.as_view(), name='devicebaytemplate_delete'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 0c373cb24..889b4d94e 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -640,6 +640,10 @@ class ConsolePortTemplateBulkEditView(BulkEditView): form = forms.ConsolePortTemplateBulkEditForm +class ConsolePortTemplateBulkRenameView(BulkRenameView): + queryset = ConsolePortTemplate.objects.all() + + class ConsolePortTemplateBulkDeleteView(BulkDeleteView): queryset = ConsolePortTemplate.objects.all() table = tables.ConsolePortTemplateTable @@ -671,6 +675,10 @@ class ConsoleServerPortTemplateBulkEditView(BulkEditView): form = forms.ConsoleServerPortTemplateBulkEditForm +class ConsoleServerPortTemplateBulkRenameView(BulkRenameView): + queryset = ConsoleServerPortTemplate.objects.all() + + class ConsoleServerPortTemplateBulkDeleteView(BulkDeleteView): queryset = ConsoleServerPortTemplate.objects.all() table = tables.ConsoleServerPortTemplateTable @@ -702,6 +710,10 @@ class PowerPortTemplateBulkEditView(BulkEditView): form = forms.PowerPortTemplateBulkEditForm +class PowerPortTemplateBulkRenameView(BulkRenameView): + queryset = PowerPortTemplate.objects.all() + + class PowerPortTemplateBulkDeleteView(BulkDeleteView): queryset = PowerPortTemplate.objects.all() table = tables.PowerPortTemplateTable @@ -733,6 +745,10 @@ class PowerOutletTemplateBulkEditView(BulkEditView): form = forms.PowerOutletTemplateBulkEditForm +class PowerOutletTemplateBulkRenameView(BulkRenameView): + queryset = PowerOutletTemplate.objects.all() + + class PowerOutletTemplateBulkDeleteView(BulkDeleteView): queryset = PowerOutletTemplate.objects.all() table = tables.PowerOutletTemplateTable @@ -764,6 +780,10 @@ class InterfaceTemplateBulkEditView(BulkEditView): form = forms.InterfaceTemplateBulkEditForm +class InterfaceTemplateBulkRenameView(BulkRenameView): + queryset = InterfaceTemplate.objects.all() + + class InterfaceTemplateBulkDeleteView(BulkDeleteView): queryset = InterfaceTemplate.objects.all() table = tables.InterfaceTemplateTable @@ -795,6 +815,10 @@ class FrontPortTemplateBulkEditView(BulkEditView): form = forms.FrontPortTemplateBulkEditForm +class FrontPortTemplateBulkRenameView(BulkRenameView): + queryset = FrontPortTemplate.objects.all() + + class FrontPortTemplateBulkDeleteView(BulkDeleteView): queryset = FrontPortTemplate.objects.all() table = tables.FrontPortTemplateTable @@ -826,6 +850,10 @@ class RearPortTemplateBulkEditView(BulkEditView): form = forms.RearPortTemplateBulkEditForm +class RearPortTemplateBulkRenameView(BulkRenameView): + queryset = RearPortTemplate.objects.all() + + class RearPortTemplateBulkDeleteView(BulkDeleteView): queryset = RearPortTemplate.objects.all() table = tables.RearPortTemplateTable @@ -857,6 +885,10 @@ class DeviceBayTemplateBulkEditView(BulkEditView): form = forms.DeviceBayTemplateBulkEditForm +class DeviceBayTemplateBulkRenameView(BulkRenameView): + queryset = DeviceBayTemplate.objects.all() + + class DeviceBayTemplateBulkDeleteView(BulkDeleteView): queryset = DeviceBayTemplate.objects.all() table = tables.DeviceBayTemplateTable @@ -952,7 +984,7 @@ class DeviceView(ObjectView): vc_members = [] # Console ports - console_ports = ConsolePort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related( + consoleports = ConsolePort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related( 'connected_endpoint__device', 'cable', ) @@ -964,7 +996,7 @@ class DeviceView(ObjectView): ) # Power ports - power_ports = PowerPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related( + powerports = PowerPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related( '_connected_poweroutlet__device', 'cable', ) @@ -982,15 +1014,15 @@ class DeviceView(ObjectView): ) # Front ports - front_ports = FrontPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related( + frontports = FrontPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related( 'rear_port', 'cable', ) # Rear ports - rear_ports = RearPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related('cable') + rearports = RearPort.objects.restrict(request.user, 'view').filter(device=device).prefetch_related('cable') # Device bays - device_bays = DeviceBay.objects.restrict(request.user, 'view').filter(device=device).prefetch_related( + devicebays = DeviceBay.objects.restrict(request.user, 'view').filter(device=device).prefetch_related( 'installed_device__device_type__manufacturer', ) @@ -1011,14 +1043,14 @@ class DeviceView(ObjectView): return render(request, 'dcim/device.html', { 'device': device, - 'console_ports': console_ports, + 'consoleports': consoleports, 'consoleserverports': consoleserverports, - 'power_ports': power_ports, + 'powerports': powerports, 'poweroutlets': poweroutlets, 'interfaces': interfaces, - 'device_bays': device_bays, - 'front_ports': front_ports, - 'rear_ports': rear_ports, + 'devicebays': devicebays, + 'frontports': frontports, + 'rearports': rearports, 'services': services, 'secrets': secrets, 'vc_members': vc_members, diff --git a/netbox/secrets/tests/test_views.py b/netbox/secrets/tests/test_views.py index 577ba4ef4..d88c02e15 100644 --- a/netbox/secrets/tests/test_views.py +++ b/netbox/secrets/tests/test_views.py @@ -1,5 +1,6 @@ import base64 +from django.test import override_settings from django.urls import reverse from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site @@ -96,6 +97,7 @@ class SecretTestCase( self.session_key = SessionKey(userkey=userkey) self.session_key.save(master_key) + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) def test_import_objects(self): self.add_permissions('secrets.add_secret') diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 01f125db4..6bdf26071 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -101,7 +101,7 @@ {% if perms.dcim.napalm_read_device %} @@ -329,86 +329,6 @@ {% plugin_left_page device %}
- {% if console_ports %} -
- {% csrf_token %} -
-
- Console Ports -
- - {% for cp in console_ports %} - {% include 'dcim/inc/consoleport.html' %} - {% endfor %} -
- -
-
- {% endif %} - {% if power_ports %} -
- {% csrf_token %} -
-
- Power Ports -
- - {% for pp in power_ports %} - {% include 'dcim/inc/powerport.html' %} - {% endfor %} -
- -
-
- {% endif %} {% if power_ports and poweroutlets %}
@@ -554,355 +474,490 @@
- {% if device_bays or device.device_type.is_parent_device %} -
- {% csrf_token %} -
-
- Device Bays -
- - - - {% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %} - - {% endif %} - - - - - - - - - {% for devicebay in device_bays %} - {% include 'dcim/inc/devicebay.html' %} - {% empty %} + +
+
+ + {% csrf_token %} +
+
+ Interfaces +
+ +
+
+ +
+
+
NameStatusDescriptionInstalled Device
+ - + {% if perms.dcim.change_interface or perms.dcim.delete_interface %} + + {% endif %} + + + + + + + + - {% endfor %} - -
— No device bays defined —NameLAGDescriptionMTUModeCableConnection
- -
-
- {% endif %} - {% if interfaces %} -
- {% csrf_token %} -
-
- Interfaces -
- + + + {% for iface in interfaces %} + {% include 'dcim/inc/interface.html' %} + {% endfor %} + + + +
+ +
+
+
+ {% csrf_token %} +
+
+ Front Ports
-
- + + + + {% if perms.dcim.change_frontport or perms.dcim.delete_frontport %} + + {% endif %} + + + + + + + + + + + + {% for frontport in frontports %} + {% include 'dcim/inc/frontport.html' %} + {% endfor %} + +
NameTypeRear PortPositionDescriptionCableConnection
+
- - - - {% if perms.dcim.change_interface or perms.dcim.delete_interface %} - - {% endif %} - - - - - - - - - - - - {% for iface in interfaces %} - {% include 'dcim/inc/interface.html' %} - {% endfor %} - -
NameLAGDescriptionMTUModeCableConnection
- -
-
- {% endif %} - {% if consoleserverports %} -
- {% csrf_token %} -
-
- Console Server Ports + +
+
+
+ {% csrf_token %} +
+
+ Rear Ports +
+ + + + {% if perms.dcim.change_rearport or perms.dcim.delete_rearport %} + + {% endif %} + + + + + + + + + + + {% for rearport in rearports %} + {% include 'dcim/inc/rearport.html' %} + {% endfor %} + +
NameTypePositionsDescriptionCableConnection
+
- - - - {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %} - - {% endif %} - + + +
+
+ {% csrf_token %} +
+
+ Console Ports +
+
Name
+ + + {% if perms.dcim.change_consoleport or perms.dcim.delete_consoleport %} + + {% endif %} + + + + + + + + + {% for cp in consoleports %} + {% include 'dcim/inc/consoleport.html' %} + {% endfor %} +
NameTypeDescriptionCableConnection
+ +
+ +
+
+
+ {% csrf_token %} +
+
+ Console Server Ports +
+ + + + {% if perms.dcim.change_consoleserverport or perms.dcim.delete_consoleserverport %} + + {% endif %} + + + + + + + + + + {% for csp in consoleserverports %} + {% include 'dcim/inc/consoleserverport.html' %} + {% endfor %} + +
NameTypeDescriptionCableConnection
+ +
+
+
+
+
+ {% csrf_token %} +
+
+ Power Ports +
+ + + + {% if perms.dcim.change_consoleport or perms.dcim.delete_consoleport %} + + {% endif %} + + + + + + + + + + {% for pp in powerports %} + {% include 'dcim/inc/powerport.html' %} + {% endfor %} +
NameTypeDrawDescriptionCableConnection
+ +
+
+
+
+
+ {% csrf_token %} +
+
+ Power Outlets +
+ + + + {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %} + + {% endif %} + - - - - - - - - {% for csp in consoleserverports %} - {% include 'dcim/inc/consoleserverport.html' %} - {% endfor %} - -
Name TypeDescriptionCableConnection
- -
-
- {% endif %} - {% if poweroutlets %} -
- {% csrf_token %} -
-
- Power Outlets + +
+
+
+ {% csrf_token %} +
+
+ Device Bays +
+ + + + {% if perms.dcim.change_devicebay or perms.dcim.delete_devicebay %} + + {% endif %} + + + + + + + + + {% for devicebay in devicebays %} + {% include 'dcim/inc/devicebay.html' %} + {% empty %} + + + + {% endfor %} + +
NameStatusDescriptionInstalled Device
— No device bays defined —
+
- - - - {% if perms.dcim.change_poweroutlet or perms.dcim.delete_poweroutlet %} - - {% endif %} - - - - - - - - - - - {% for po in poweroutlets %} - {% include 'dcim/inc/poweroutlet.html' %} - {% endfor %} - -
NameTypeInput/LegDescriptionCableConnection
- -
- - {% endif %} - {% if front_ports %} -
- {% csrf_token %} -
-
- Front Ports -
- - - - {% if perms.dcim.change_frontport or perms.dcim.delete_frontport %} - - {% endif %} - - - - - - - - - - - - {% for frontport in front_ports %} - {% include 'dcim/inc/frontport.html' %} - {% endfor %} - -
NameTypeRear PortPositionDescriptionCableConnection
- -
-
- {% endif %} - {% if rear_ports %} -
- {% csrf_token %} -
-
- Rear Ports -
- - - - {% if perms.dcim.change_rearport or perms.dcim.delete_rearport %} - - {% endif %} - - - - - - - - - - - {% for rearport in rear_ports %} - {% include 'dcim/inc/rearport.html' %} - {% endfor %} - -
NameTypePositionsDescriptionCableConnection
- -
-
- {% endif %} + +
+
{% include 'inc/modal.html' with name='graphs' title='Graphs' %} diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index 7ca29a1f7..b9db8db6f 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -63,149 +63,157 @@ {% endblock %} {% block content %} -
-
-
-
- Chassis +
+
+
+
+ Chassis +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Manufacturer{{ devicetype.manufacturer }}
Model Name + {{ devicetype.model }}
+ {{ devicetype.slug }} +
Part Number{{ devicetype.part_number|placeholder }}
Height (U){{ devicetype.u_height }}
Full Depth + {% if devicetype.is_full_depth %} + + {% else %} + + {% endif %} +
Parent/Child + {{ devicetype.get_subdevice_role_display|placeholder }} +
Front Image + {% if devicetype.front_image %} + + {{ devicetype.front_image.name }} + + {% else %} + + {% endif %} +
Rear Image + {% if devicetype.rear_image %} + + {{ devicetype.rear_image.name }} + + {% else %} + + {% endif %} +
Instances{{ instance_count }}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Manufacturer{{ devicetype.manufacturer }}
Model Name - {{ devicetype.model }}
- {{ devicetype.slug }} -
Part Number{{ devicetype.part_number|placeholder }}
Height (U){{ devicetype.u_height }}
Full Depth - {% if devicetype.is_full_depth %} - - {% else %} - - {% endif %} -
Parent/Child - {{ devicetype.get_subdevice_role_display|placeholder }} -
Front Image - {% if devicetype.front_image %} - - {{ devicetype.front_image.name }} - - {% else %} - - {% endif %} -
Rear Image - {% if devicetype.rear_image %} - - {{ devicetype.rear_image.name }} - - {% else %} - - {% endif %} -
Instances{{ instance_count }}
+ {% plugin_left_page devicetype %}
- {% plugin_left_page devicetype %} -
-
- {% include 'inc/custom_fields_panel.html' with obj=devicetype %} - {% include 'extras/inc/tags_panel.html' with tags=devicetype.tags.all url='dcim:devicetype_list' %} -
-
- Comments +
+ {% include 'inc/custom_fields_panel.html' with obj=devicetype %} + {% include 'extras/inc/tags_panel.html' with tags=devicetype.tags.all url='dcim:devicetype_list' %} +
+
+ Comments +
+
+ {% if devicetype.comments %} + {{ devicetype.comments|render_markdown }} + {% else %} + None + {% endif %} +
-
- {% if devicetype.comments %} - {{ devicetype.comments|render_markdown }} - {% else %} - None - {% endif %} + {% plugin_right_page devicetype %} +
+
+
+
+ {% plugin_full_width_page devicetype %} +
+
+
+
+ +
+
+ {% include 'dcim/inc/devicetype_component_table.html' with table=interface_table title='Interfaces' %} +
+
+ {% include 'dcim/inc/devicetype_component_table.html' with table=front_port_table title='Front Ports' %} +
+
+ {% include 'dcim/inc/devicetype_component_table.html' with table=rear_port_table title='Rear Ports' %} +
+
+ {% include 'dcim/inc/devicetype_component_table.html' with table=consoleport_table title='Console Ports' %} +
+
+ {% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' %} +
+
+ {% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' %} +
+
+ {% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' %} +
+
+ {% include 'dcim/inc/devicetype_component_table.html' with table=devicebay_table title='Device Bays' %} +
- {% plugin_right_page devicetype %}
-
-{% if devicetype.consoleport_templates.exists or devicetype.powerport_templates.exists %} -
-
- {% 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' %} -
-
- {% 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' %} -
-
-{% endif %} -
-
- {% plugin_full_width_page devicetype %} -
-
-{% if devicetype.is_parent_device or devicebay_table.rows %} -
-
- {% 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' %} -
-
-{% endif %} -{% if devicetype.consoleserverport_templates.exists %} -
-
- {% 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' %} -
-
-{% endif %} -{% if devicetype.poweroutlet_templates.exists %} -
-
- {% 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' %} -
-
-{% endif %} -{% if devicetype.interface_templates.exists %} -
-
- {% 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' %} -
-
-{% endif %} -{% if devicetype.frontport_templates.exists or devicetype.rearport_templates.exists %} -
-
- {% 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' %} -
-
- {% 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' %} -
-
-{% endif %} {% endblock %} diff --git a/netbox/templates/dcim/inc/consoleport.html b/netbox/templates/dcim/inc/consoleport.html index 61b4fe045..dc2111b8a 100644 --- a/netbox/templates/dcim/inc/consoleport.html +++ b/netbox/templates/dcim/inc/consoleport.html @@ -18,8 +18,6 @@ {% if cp.type %}{{ cp.get_type_display }}{% else %}—{% endif %} - - {# Description #} {{ cp.description }} diff --git a/netbox/templates/dcim/inc/device_component_table.html b/netbox/templates/dcim/inc/device_component_table.html new file mode 100644 index 000000000..a0bb8f82b --- /dev/null +++ b/netbox/templates/dcim/inc/device_component_table.html @@ -0,0 +1,40 @@ +{% load helpers %} +{% load perms %} +
+ {% csrf_token %} +
+
+ {{ title }} +
+ + {% for obj in components %} + {% include component_template %} + {% endfor %} +
+ +
+
diff --git a/netbox/templates/dcim/inc/devicetype_component_table.html b/netbox/templates/dcim/inc/devicetype_component_table.html index 010749b93..135facc73 100644 --- a/netbox/templates/dcim/inc/devicetype_component_table.html +++ b/netbox/templates/dcim/inc/devicetype_component_table.html @@ -1,3 +1,4 @@ +{% load helpers %} {% if perms.dcim.change_devicetype %}
{% csrf_token %} @@ -8,19 +9,18 @@ {% include 'responsive_table.html' %}