From f28bde179e09c1bc0ae10346c393e5f106905d57 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 2 Jul 2020 10:46:02 -0400 Subject: [PATCH 01/12] Extend label field to all device components --- netbox/dcim/api/serializers.py | 12 +- netbox/dcim/filters.py | 1 + netbox/dcim/forms.py | 120 +++++++----------- .../dcim/migrations/0107_component_labels.py | 51 ++++++-- .../dcim/models/device_component_templates.py | 41 +----- netbox/dcim/models/device_components.py | 76 ++++------- netbox/dcim/tables.py | 5 +- 7 files changed, 119 insertions(+), 187 deletions(-) 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..449a96dcc 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -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..65cce8850 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(), @@ -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() @@ -3013,7 +3024,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 +3105,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 +3123,7 @@ class FrontPortBulkEditForm( ) class Meta: - nullable_fields = [ - 'description', - ] + nullable_fields = ('label', 'description') class FrontPortCSVForm(CSVModelForm): @@ -3185,7 +3194,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 +3219,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 +3237,7 @@ class RearPortBulkEditForm( ) class Meta: - nullable_fields = [ - 'description', - ] + nullable_fields = ('label', 'description') class RearPortCSVForm(CSVModelForm): @@ -3392,17 +3399,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 +3444,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 +3453,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 +3482,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/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 1c2be0e5d..626363da1 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -27,6 +27,11 @@ __all__ = ( class ComponentTemplateModel(models.Model): + label = models.CharField( + max_length=64, + blank=True, + help_text="Physical label" + ) description = models.CharField( max_length=200, blank=True @@ -81,11 +86,6 @@ class ConsolePortTemplate(ComponentTemplateModel): 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, @@ -121,11 +121,6 @@ class ConsoleServerPortTemplate(ComponentTemplateModel): 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, @@ -161,11 +156,6 @@ class PowerPortTemplate(ComponentTemplateModel): 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, @@ -215,11 +205,6 @@ class PowerOutletTemplate(ComponentTemplateModel): 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, @@ -283,11 +268,6 @@ class InterfaceTemplate(ComponentTemplateModel): 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 @@ -348,9 +328,6 @@ class FrontPortTemplate(ComponentTemplateModel): ('rear_port', 'rear_port_position'), ) - def __str__(self): - return self.name - def clean(self): # Validate rear port assignment @@ -411,9 +388,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, @@ -440,11 +414,6 @@ class DeviceBayTemplate(ComponentTemplateModel): max_length=100, blank=True ) - label = models.CharField( - max_length=64, - blank=True, - help_text="Physical label" - ) class Meta: ordering = ('device_type', '_name') diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index aecf57544..aea34e73e 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -36,6 +36,11 @@ __all__ = ( class ComponentModel(models.Model): + label = models.CharField( + max_length=64, + blank=True, + help_text="Physical label" + ) description = models.CharField( max_length=200, blank=True @@ -241,11 +246,6 @@ class ConsolePort(CableTermination, ComponentModel): 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, @@ -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, ) @@ -310,11 +311,6 @@ class ConsoleServerPort(CableTermination, ComponentModel): 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 +323,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 +336,7 @@ class ConsoleServerPort(CableTermination, ComponentModel): return ( self.device.identifier, self.name, + self.label, self.type, self.description, ) @@ -367,11 +364,6 @@ class PowerPort(CableTermination, ComponentModel): 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 +402,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 +415,7 @@ class PowerPort(CableTermination, ComponentModel): return ( self.device.identifier, self.name, + self.label, self.get_type_display(), self.maximum_draw, self.allocated_draw, @@ -532,11 +525,6 @@ class PowerOutlet(CableTermination, ComponentModel): 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 +550,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 +563,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(), @@ -640,11 +629,6 @@ class Interface(CableTermination, ComponentModel, BaseInterface): null=True, 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 +687,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 +701,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, @@ -877,7 +862,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 +871,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 +878,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, @@ -947,15 +930,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 +943,7 @@ class RearPort(CableTermination, ComponentModel): return ( self.device.identifier, self.name, + self.label, self.get_type_display(), self.positions, self.description, @@ -992,11 +973,6 @@ class DeviceBay(ComponentModel): 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 +982,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 +995,7 @@ class DeviceBay(ComponentModel): return ( self.device.identifier, self.name, + self.label, self.installed_device.identifier if self.installed_device else None, self.description, ) @@ -1116,16 +1088,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 +1102,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..587749277 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -784,9 +784,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') # From c5362f59319c315c783b97231a01453f4714d7d0 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 2 Jul 2020 11:43:03 -0400 Subject: [PATCH 02/12] Fix permissions evaluation for nonstandard tests --- netbox/circuits/tests/test_api.py | 2 ++ netbox/dcim/tests/test_api.py | 4 ++++ netbox/dcim/tests/test_views.py | 2 ++ netbox/secrets/tests/test_views.py | 2 ++ netbox/virtualization/tests/test_api.py | 4 +++- 5 files changed, 13 insertions(+), 1 deletion(-) 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/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..e1baadcc1 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) 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/virtualization/tests/test_api.py b/netbox/virtualization/tests/test_api.py index e108100a9..4d79602f5 100644 --- a/netbox/virtualization/tests/test_api.py +++ b/netbox/virtualization/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 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. """ From d03d302eef3819db64cad8ae74dc5255647045f6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 2 Jul 2020 12:08:19 -0400 Subject: [PATCH 03/12] Closes #4817: Standardize device/VM component name field to 64 characters --- docs/release-notes/version-2.9.md | 1 + .../0112_standardize_component_name.py | 68 ++++++++++++++ .../dcim/models/device_component_templates.py | 67 ++------------ netbox/dcim/models/device_components.py | 89 +++---------------- netbox/virtualization/models.py | 11 +++ 5 files changed, 102 insertions(+), 134 deletions(-) create mode 100644 netbox/dcim/migrations/0112_standardize_component_name.py 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/dcim/migrations/0112_standardize_component_name.py b/netbox/dcim/migrations/0112_standardize_component_name.py new file mode 100644 index 000000000..1e12b1bf2 --- /dev/null +++ b/netbox/dcim/migrations/0112_standardize_component_name.py @@ -0,0 +1,68 @@ +# Generated by Django 3.0.6 on 2020-07-02 16:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0111_component_template_description'), + ] + + operations = [ + 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), + ), + ] diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 626363da1..a7b76fdde 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -27,6 +27,14 @@ __all__ = ( class ComponentTemplateModel(models.Model): + name = models.CharField( + max_length=64 + ) + _name = NaturalOrderingField( + target_field='name', + max_length=100, + blank=True + ) label = models.CharField( max_length=64, blank=True, @@ -78,14 +86,6 @@ class ConsolePortTemplate(ComponentTemplateModel): on_delete=models.CASCADE, related_name='consoleport_templates' ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -113,14 +113,6 @@ class ConsoleServerPortTemplate(ComponentTemplateModel): on_delete=models.CASCADE, related_name='consoleserverport_templates' ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -148,14 +140,6 @@ class PowerPortTemplate(ComponentTemplateModel): on_delete=models.CASCADE, related_name='powerport_templates' ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=PowerPortTypeChoices, @@ -197,14 +181,6 @@ class PowerOutletTemplate(ComponentTemplateModel): on_delete=models.CASCADE, related_name='poweroutlet_templates' ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=PowerOutletTypeChoices, @@ -259,9 +235,6 @@ class InterfaceTemplate(ComponentTemplateModel): on_delete=models.CASCADE, related_name='interface_templates' ) - name = models.CharField( - max_length=64 - ) _name = NaturalOrderingField( target_field='name', naturalize_function=naturalize_interface, @@ -299,14 +272,6 @@ class FrontPortTemplate(ComponentTemplateModel): 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 @@ -367,14 +332,6 @@ class RearPortTemplate(ComponentTemplateModel): 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 @@ -406,14 +363,6 @@ class DeviceBayTemplate(ComponentTemplateModel): 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 - ) class Meta: ordering = ('device_type', '_name') diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index aea34e73e..6a2591916 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -36,6 +36,14 @@ __all__ = ( class ComponentModel(models.Model): + name = models.CharField( + max_length=64 + ) + _name = NaturalOrderingField( + target_field='name', + max_length=100, + blank=True + ) label = models.CharField( max_length=64, blank=True, @@ -243,14 +251,6 @@ class ConsolePort(CableTermination, ComponentModel): on_delete=models.CASCADE, related_name='consoleports' ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -303,14 +303,6 @@ class ConsoleServerPort(CableTermination, ComponentModel): on_delete=models.CASCADE, related_name='consoleserverports' ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -356,14 +348,6 @@ class PowerPort(CableTermination, ComponentModel): on_delete=models.CASCADE, related_name='powerports' ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=PowerPortTypeChoices, @@ -517,14 +501,6 @@ class PowerOutlet(CableTermination, ComponentModel): on_delete=models.CASCADE, related_name='poweroutlets' ) - name = models.CharField( - max_length=50 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) type = models.CharField( max_length=50, choices=PowerOutletTypeChoices, @@ -584,15 +560,6 @@ 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 - ) enabled = models.BooleanField( default=True ) @@ -629,6 +596,12 @@ class Interface(CableTermination, ComponentModel, BaseInterface): null=True, blank=True ) + _name = NaturalOrderingField( + target_field='name', + naturalize_function=naturalize_interface, + max_length=100, + blank=True + ) _connected_interface = models.OneToOneField( to='self', on_delete=models.SET_NULL, @@ -839,14 +812,6 @@ class FrontPort(CableTermination, ComponentModel): 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 @@ -912,14 +877,6 @@ class RearPort(CableTermination, ComponentModel): 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 @@ -964,15 +921,6 @@ class DeviceBay(ComponentModel): 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 - ) installed_device = models.OneToOneField( to='dcim.Device', on_delete=models.SET_NULL, @@ -1045,15 +993,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, diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index 5d74f8468..258183bff 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -9,7 +9,9 @@ from dcim.choices import InterfaceModeChoices from dcim.models import BaseInterface, Device from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange, TaggedItem from extras.utils import extras_features +from utilities.fields import NaturalOrderingField from utilities.models import ChangeLoggedModel +from utilities.ordering import naturalize_interface from utilities.query_functions import CollateAsChar from utilities.querysets import RestrictedQuerySet from utilities.utils import serialize_object @@ -387,6 +389,15 @@ class VMInterface(BaseInterface): on_delete=models.CASCADE, related_name='interfaces' ) + name = models.CharField( + max_length=64 + ) + _name = NaturalOrderingField( + target_field='name', + naturalize_function=naturalize_interface, + max_length=100, + blank=True + ) description = models.CharField( max_length=200, blank=True From 1f9cdc71d4c15b18989d1d3081c06a8a9d916b6d Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 2 Jul 2020 13:07:32 -0400 Subject: [PATCH 04/12] Move device and device_type ForeignKeys to abstract component models --- netbox/dcim/filters.py | 20 +-- netbox/dcim/forms.py | 2 +- .../0112_standardize_component_name.py | 68 ---------- .../migrations/0112_standardize_components.py | 120 ++++++++++++++++++ netbox/dcim/models/__init__.py | 52 ++++---- .../dcim/models/device_component_templates.py | 47 +------ netbox/dcim/models/device_components.py | 56 ++------ netbox/dcim/tests/test_views.py | 16 +-- netbox/templates/dcim/device.html | 2 +- netbox/templates/dcim/devicetype.html | 10 +- 10 files changed, 186 insertions(+), 207 deletions(-) delete mode 100644 netbox/dcim/migrations/0112_standardize_component_name.py create mode 100644 netbox/dcim/migrations/0112_standardize_components.py diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 449a96dcc..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): diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 65cce8850..a35835732 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -1392,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 diff --git a/netbox/dcim/migrations/0112_standardize_component_name.py b/netbox/dcim/migrations/0112_standardize_component_name.py deleted file mode 100644 index 1e12b1bf2..000000000 --- a/netbox/dcim/migrations/0112_standardize_component_name.py +++ /dev/null @@ -1,68 +0,0 @@ -# Generated by Django 3.0.6 on 2020-07-02 16:02 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0111_component_template_description'), - ] - - operations = [ - 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), - ), - ] 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 a7b76fdde..7e96a8016 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -27,6 +27,11 @@ __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 ) @@ -81,11 +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' - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -108,11 +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' - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -135,11 +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' - ) type = models.CharField( max_length=50, choices=PowerPortTypeChoices, @@ -176,11 +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' - ) type = models.CharField( max_length=50, choices=PowerOutletTypeChoices, @@ -230,11 +215,7 @@ 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' - ) + # Override ComponentTemplateModel._name to specify naturalize_interface function _name = NaturalOrderingField( target_field='name', naturalize_function=naturalize_interface, @@ -267,11 +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' - ) type = models.CharField( max_length=50, choices=PortTypeChoices @@ -327,11 +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' - ) type = models.CharField( max_length=50, choices=PortTypeChoices @@ -358,12 +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' - ) - 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 6a2591916..1ebb0c4d5 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -36,6 +36,11 @@ __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 ) @@ -246,11 +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' - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -298,11 +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' - ) type = models.CharField( max_length=50, choices=ConsolePortTypeChoices, @@ -343,11 +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' - ) type = models.CharField( max_length=50, choices=PowerPortTypeChoices, @@ -496,11 +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' - ) type = models.CharField( max_length=50, choices=PowerOutletTypeChoices, @@ -560,6 +545,9 @@ class PowerOutlet(CableTermination, ComponentModel): # class BaseInterface(models.Model): + """ + Abstract base class for fields shared by dcim.Interface and virtualization.VMInterface. + """ enabled = models.BooleanField( default=True ) @@ -589,13 +577,7 @@ 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, - blank=True - ) + # Override ComponentModel._name to specify naturalize_interface function _name = NaturalOrderingField( target_field='name', naturalize_function=naturalize_interface, @@ -807,11 +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' - ) type = models.CharField( max_length=50, choices=PortTypeChoices @@ -872,11 +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' - ) type = models.CharField( max_length=50, choices=PortTypeChoices @@ -916,11 +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' - ) installed_device = models.OneToOneField( to='dcim.Device', on_delete=models.SET_NULL, @@ -981,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, diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index e1baadcc1..ef7ff993f 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -481,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/templates/dcim/device.html b/netbox/templates/dcim/device.html index 01f125db4..408d0d833 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -101,7 +101,7 @@ {% if perms.dcim.napalm_read_device %} diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index 7ca29a1f7..ad0e070e9 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -155,7 +155,7 @@ {% plugin_right_page devicetype %} -{% if devicetype.consoleport_templates.exists or devicetype.powerport_templates.exists %} +{% if devicetype.consoleporttemplates.exists or devicetype.powerporttemplates.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' %} @@ -177,28 +177,28 @@
{% endif %} -{% if devicetype.consoleserverport_templates.exists %} +{% if devicetype.consoleserverporttemplates.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 %} +{% if devicetype.poweroutlettemplates.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 %} +{% if devicetype.interfacetemplates.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 %} +{% if devicetype.frontporttemplates.exists or devicetype.rearporttemplates.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' %} From 6abb7e8f4dbaf2e7a14daa7fda302bb400431c19 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 2 Jul 2020 13:12:37 -0400 Subject: [PATCH 05/12] #4721: Tweak migrations to ensure Interface.device cannot be null --- netbox/dcim/migrations/0109_interface_remove_vm.py | 12 +++++++++--- .../migrations/0016_replicate_interfaces.py | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) 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/virtualization/migrations/0016_replicate_interfaces.py b/netbox/virtualization/migrations/0016_replicate_interfaces.py index d6c0b0217..e141dff04 100644 --- a/netbox/virtualization/migrations/0016_replicate_interfaces.py +++ b/netbox/virtualization/migrations/0016_replicate_interfaces.py @@ -51,8 +51,8 @@ def replicate_interfaces(apps, schema_editor): # Verify that all interfaces have been replicated assert replicated_count == original_interfaces.count(), "Replicated interfaces count does not match original count!" - # Delete original VM interfaces - original_interfaces.delete() + # Delete all interfaces not assigned to a Device + Interface.objects.filter(device__isnull=True).delete() class Migration(migrations.Migration): From eb2da300b0f78dfd909812fb52c2401356f9586a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 2 Jul 2020 13:27:54 -0400 Subject: [PATCH 06/12] Fix form initialization for interface import --- netbox/dcim/forms.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index a35835732..dcc1f6418 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -2978,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( From 92c889ef9ec14a43616dd09bc09d89740d3808d7 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 2 Jul 2020 13:58:53 -0400 Subject: [PATCH 07/12] #4416: Provide bulk rename ability for device component templates --- netbox/dcim/urls.py | 8 +++++ netbox/dcim/views.py | 32 +++++++++++++++++++ netbox/templates/dcim/devicetype.html | 16 +++++----- .../dcim/inc/devicetype_component_table.html | 22 ++++++------- 4 files changed, 59 insertions(+), 19 deletions(-) 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..2376e5430 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 diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index ad0e070e9..03d314aa6 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -158,10 +158,10 @@ {% if devicetype.consoleporttemplates.exists or devicetype.powerporttemplates.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=consoleport_table title='Console Ports' %}
- {% 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' %} + {% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' %}
{% endif %} @@ -173,38 +173,38 @@ {% 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' %} + {% include 'dcim/inc/devicetype_component_table.html' with table=devicebay_table title='Device Bays' %}
{% endif %} {% if devicetype.consoleserverporttemplates.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' %} + {% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' %}
{% endif %} {% if devicetype.poweroutlettemplates.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' %} + {% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' %}
{% endif %} {% if devicetype.interfacetemplates.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' %} + {% include 'dcim/inc/devicetype_component_table.html' with table=interface_table title='Interfaces' %}
{% endif %} {% if devicetype.frontporttemplates.exists or devicetype.rearporttemplates.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=front_port_table title='Front Ports' %}
- {% 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' %} + {% include 'dcim/inc/devicetype_component_table.html' with table=rear_port_table title='Rear Ports' %}
{% endif %} 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' %}