From 9e0d6bf0673891116a24bd1797a0ad7d2fbe9ad5 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 17 Nov 2023 13:42:42 -0500 Subject: [PATCH] Introduce abstract ComponentModel for VM components --- .../templates/virtualization/virtualdisk.html | 74 ++++++------ netbox/virtualization/api/serializers.py | 3 +- netbox/virtualization/filtersets.py | 2 +- netbox/virtualization/forms/bulk_create.py | 2 +- netbox/virtualization/forms/bulk_edit.py | 9 +- netbox/virtualization/forms/bulk_import.py | 2 +- netbox/virtualization/forms/model_forms.py | 4 +- .../migrations/0038_virtualdisk.py | 50 +++++++++ ...virtual_disk_count_virtualdisk_and_more.py | 70 ------------ .../virtualization/models/virtualmachines.py | 106 ++++++++---------- .../virtualization/tables/virtualmachines.py | 8 +- 11 files changed, 155 insertions(+), 175 deletions(-) create mode 100644 netbox/virtualization/migrations/0038_virtualdisk.py delete mode 100644 netbox/virtualization/migrations/0038_virtualmachine_virtual_disk_count_virtualdisk_and_more.py diff --git a/netbox/templates/virtualization/virtualdisk.html b/netbox/templates/virtualization/virtualdisk.html index 413491085..821e58796 100644 --- a/netbox/templates/virtualization/virtualdisk.html +++ b/netbox/templates/virtualization/virtualdisk.html @@ -12,46 +12,48 @@ {% endblock %} {% block content %} -
-
-
-
- {% trans "Virtual Disk" %} -
-
- - - - - - - - - - - - - -
{% trans "Virtual Machine" %}{{ object.virtual_machine|linkify }}
{% trans "Name" %}{{ object.name }}
{% trans "Disk Space" %} - {% if object.size %} - {{ object.size }} {% trans "GB" context "Abbreviation for gigabyte" %} - {% else %} - {{ ''|placeholder }} - {% endif %} -
-
+
+
+
+
{% trans "Virtual Disk" %}
+
+ + + + + + + + + + + + + + + + + +
{% trans "Virtual Machine" %}{{ object.virtual_machine|linkify }}
{% trans "Name" %}{{ object.name }}
{% trans "Size" %} + {% if object.size %} + {{ object.size }} {% trans "GB" context "Abbreviation for gigabyte" %} + {% else %} + {{ ''|placeholder }} + {% endif %} +
{% trans "Description" %}{{ object.description|placeholder }}
- {% include 'inc/panels/tags.html' %} - {% plugin_left_page object %} +
+ {% include 'inc/panels/tags.html' %} + {% plugin_left_page object %}
- {% include 'inc/panels/custom_fields.html' %} - {% plugin_right_page object %} + {% include 'inc/panels/custom_fields.html' %} + {% plugin_right_page object %}
-
-
+
+
- {% plugin_full_width_page object %} + {% plugin_full_width_page object %}
-
+
{% endblock %} diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index 0b36d37f0..95b2152a5 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -173,5 +173,6 @@ class VirtualDiskSerializer(NetBoxModelSerializer): class Meta: model = VirtualDisk fields = [ - 'id', 'url', 'virtual_machine', 'name', 'size', 'tags', 'custom_fields', 'created', 'last_updated', + 'id', 'url', 'virtual_machine', 'name', 'description', 'size', 'tags', 'custom_fields', 'created', + 'last_updated', ] diff --git a/netbox/virtualization/filtersets.py b/netbox/virtualization/filtersets.py index 29415ba66..1f7ddabe1 100644 --- a/netbox/virtualization/filtersets.py +++ b/netbox/virtualization/filtersets.py @@ -321,7 +321,7 @@ class VirtualDiskFilterSet(NetBoxModelFilterSet): class Meta: model = VirtualDisk - fields = ['id', 'name', 'size'] + fields = ['id', 'name', 'size', 'description'] def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/virtualization/forms/bulk_create.py b/netbox/virtualization/forms/bulk_create.py index 56f1c9f94..a4ad867d4 100644 --- a/netbox/virtualization/forms/bulk_create.py +++ b/netbox/virtualization/forms/bulk_create.py @@ -34,7 +34,7 @@ class VMInterfaceBulkCreateForm( class VirtualDiskBulkCreateForm( - form_from_model(VirtualDisk, ['size', 'tags']), + form_from_model(VirtualDisk, ['size', 'description', 'tags']), VirtualMachineBulkAddComponentForm ): replication_fields = ('name',) diff --git a/netbox/virtualization/forms/bulk_edit.py b/netbox/virtualization/forms/bulk_edit.py index 9a352bd8d..72990ec76 100644 --- a/netbox/virtualization/forms/bulk_edit.py +++ b/netbox/virtualization/forms/bulk_edit.py @@ -331,12 +331,17 @@ class VirtualDiskBulkEditForm(NetBoxModelBulkEditForm): required=False, label=_('Size (GB)') ) + description = forms.CharField( + label=_('Description'), + max_length=100, + required=False + ) model = VirtualDisk fieldsets = ( - (None, ('size',)), + (None, ('size', 'description')), ) - nullable_fields = () + nullable_fields = ('description',) class VirtualDiskBulkRenameForm(BulkRenameForm): diff --git a/netbox/virtualization/forms/bulk_import.py b/netbox/virtualization/forms/bulk_import.py index 00f386af2..5d44ddceb 100644 --- a/netbox/virtualization/forms/bulk_import.py +++ b/netbox/virtualization/forms/bulk_import.py @@ -212,5 +212,5 @@ class VirtualDiskImportForm(NetBoxModelImportForm): class Meta: model = VirtualDisk fields = ( - 'virtual_machine', 'name', 'size', 'tags' + 'virtual_machine', 'name', 'size', 'description', 'tags' ) diff --git a/netbox/virtualization/forms/model_forms.py b/netbox/virtualization/forms/model_forms.py index 80794281b..615b223cf 100644 --- a/netbox/virtualization/forms/model_forms.py +++ b/netbox/virtualization/forms/model_forms.py @@ -369,13 +369,13 @@ class VirtualDiskForm(NetBoxModelForm): ) fieldsets = ( - (None, ('virtual_machine', 'name', 'size', 'tags')), + (None, ('virtual_machine', 'name', 'size', 'description', 'tags')), ) class Meta: model = VirtualDisk fields = [ - 'virtual_machine', 'name', 'size', 'tags', + 'virtual_machine', 'name', 'size', 'description', 'tags', ] def __init__(self, *args, **kwargs): diff --git a/netbox/virtualization/migrations/0038_virtualdisk.py b/netbox/virtualization/migrations/0038_virtualdisk.py new file mode 100644 index 000000000..59d45c975 --- /dev/null +++ b/netbox/virtualization/migrations/0038_virtualdisk.py @@ -0,0 +1,50 @@ +from django.db import migrations, models +import django.db.models.deletion +import taggit.managers +import utilities.fields +import utilities.json +import utilities.ordering +import utilities.query_functions +import utilities.tracking + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0099_cachedvalue_ordering'), + ('virtualization', '0037_protect_child_interfaces'), + ] + + operations = [ + migrations.AddField( + model_name='virtualmachine', + name='virtual_disk_count', + field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='virtual_machine', to_model='virtualization.VirtualDisk'), + ), + migrations.CreateModel( + name='VirtualDisk', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), + ('created', models.DateTimeField(auto_now_add=True, null=True)), + ('last_updated', models.DateTimeField(auto_now=True, null=True)), + ('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)), + ('name', models.CharField(max_length=64)), + ('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize_interface)), + ('description', models.CharField(blank=True, max_length=200)), + ('size', models.PositiveIntegerField()), + ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), + ('virtual_machine', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='virtualization.virtualmachine')), + ], + options={ + 'verbose_name': 'virtual disk', + 'verbose_name_plural': 'virtual disks', + 'ordering': ('virtual_machine', utilities.query_functions.CollateAsChar('_name')), + 'abstract': False, + }, + bases=(models.Model, utilities.tracking.TrackingModelMixin), + ), + migrations.AddConstraint( + model_name='virtualdisk', + constraint=models.UniqueConstraint(fields=('virtual_machine', 'name'), name='virtualization_virtualdisk_unique_virtual_machine_name'), + ), + ] diff --git a/netbox/virtualization/migrations/0038_virtualmachine_virtual_disk_count_virtualdisk_and_more.py b/netbox/virtualization/migrations/0038_virtualmachine_virtual_disk_count_virtualdisk_and_more.py deleted file mode 100644 index 1af5c98cf..000000000 --- a/netbox/virtualization/migrations/0038_virtualmachine_virtual_disk_count_virtualdisk_and_more.py +++ /dev/null @@ -1,70 +0,0 @@ -# Generated by Django 4.2.5 on 2023-10-23 15:13 - -from django.db import migrations, models -import django.db.models.deletion -import django.db.models.functions.text -import taggit.managers -import utilities.fields -import utilities.json -import utilities.ordering -import utilities.tracking - - -class Migration(migrations.Migration): - dependencies = [ - ('extras', '0098_webhook_custom_field_data_webhook_tags'), - ('virtualization', '0037_protect_child_interfaces'), - ] - - operations = [ - migrations.AddField( - model_name='virtualmachine', - name='virtual_disk_count', - field=utilities.fields.CounterCacheField( - default=0, editable=False, to_field='virtual_machine', to_model='virtualization.VirtualDisk' - ), - ), - migrations.CreateModel( - name='VirtualDisk', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), - ('created', models.DateTimeField(auto_now_add=True, null=True)), - ('last_updated', models.DateTimeField(auto_now=True, null=True)), - ( - 'custom_field_data', - models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder), - ), - ('name', models.CharField(max_length=64)), - ( - '_name', - utilities.fields.NaturalOrderingField( - 'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize - ), - ), - ('size', models.PositiveIntegerField()), - ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')), - ( - 'virtual_machine', - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name='%(class)ss', - to='virtualization.virtualmachine', - ), - ), - ], - options={ - 'verbose_name': 'virtual disk', - 'verbose_name_plural': 'virtual disks', - 'ordering': ('virtual_machine', '_name'), - }, - bases=(models.Model, utilities.tracking.TrackingModelMixin), - ), - migrations.AddConstraint( - model_name='virtualdisk', - constraint=models.UniqueConstraint( - django.db.models.functions.text.Lower('name'), - models.F('virtual_machine'), - name='virtualization_virtualdisk_unique_name_virtual_machine', - ), - ), - ] diff --git a/netbox/virtualization/models/virtualmachines.py b/netbox/virtualization/models/virtualmachines.py index 634c59c9e..705419186 100644 --- a/netbox/virtualization/models/virtualmachines.py +++ b/netbox/virtualization/models/virtualmachines.py @@ -252,11 +252,19 @@ class VirtualMachine(ContactsMixin, RenderConfigMixin, ConfigContextModel, Prima return None -class VMInterface(NetBoxModel, BaseInterface, TrackingModelMixin): +# +# VM components +# + + +class ComponentModel(NetBoxModel): + """ + An abstract model inherited by any model which has a parent VirtualMachine. + """ virtual_machine = models.ForeignKey( to='virtualization.VirtualMachine', on_delete=models.CASCADE, - related_name='interfaces' + related_name='%(class)ss' ) name = models.CharField( verbose_name=_('name'), @@ -273,6 +281,42 @@ class VMInterface(NetBoxModel, BaseInterface, TrackingModelMixin): max_length=200, blank=True ) + + class Meta: + abstract = True + ordering = ('virtual_machine', CollateAsChar('_name')) + constraints = ( + models.UniqueConstraint( + fields=('virtual_machine', 'name'), + name='%(app_label)s_%(class)s_unique_virtual_machine_name' + ), + ) + + def __str__(self): + return self.name + + def to_objectchange(self, action): + objectchange = super().to_objectchange(action) + objectchange.related_object = self.virtual_machine + return objectchange + + @property + def parent_object(self): + return self.virtual_machine + + +class VMInterface(ComponentModel, BaseInterface, TrackingModelMixin): + virtual_machine = models.ForeignKey( + to='virtualization.VirtualMachine', + on_delete=models.CASCADE, + related_name='interfaces' # Override ComponentModel + ) + _name = NaturalOrderingField( + target_field='name', + naturalize_function=naturalize_interface, + max_length=100, + blank=True + ) untagged_vlan = models.ForeignKey( to='ipam.VLAN', on_delete=models.SET_NULL, @@ -314,20 +358,10 @@ class VMInterface(NetBoxModel, BaseInterface, TrackingModelMixin): related_query_name='vminterface', ) - class Meta: - ordering = ('virtual_machine', CollateAsChar('_name')) - constraints = ( - models.UniqueConstraint( - fields=('virtual_machine', 'name'), - name='%(app_label)s_%(class)s_unique_virtual_machine_name' - ), - ) + class Meta(ComponentModel.Meta): verbose_name = _('interface') verbose_name_plural = _('interfaces') - def __str__(self): - return self.name - def get_absolute_url(self): return reverse('virtualization:vminterface', kwargs={'pk': self.pk}) @@ -375,61 +409,19 @@ class VMInterface(NetBoxModel, BaseInterface, TrackingModelMixin): ).format(untagged_vlan=self.untagged_vlan) }) - def to_objectchange(self, action): - objectchange = super().to_objectchange(action) - objectchange.related_object = self.virtual_machine - return objectchange - - @property - def parent_object(self): - return self.virtual_machine - @property def l2vpn_termination(self): return self.l2vpn_terminations.first() -class VirtualDisk(NetBoxModel, TrackingModelMixin): - virtual_machine = models.ForeignKey( - to=VirtualMachine, - on_delete=models.CASCADE, - related_name='%(class)ss' - ) - name = models.CharField( - verbose_name=_('name'), - max_length=64 - ) - _name = NaturalOrderingField( - target_field='name', - max_length=100, - blank=True - ) +class VirtualDisk(ComponentModel, TrackingModelMixin): size = models.PositiveIntegerField( verbose_name=_('size (GB)'), ) - class Meta: - ordering = ('virtual_machine', '_name') - constraints = ( - models.UniqueConstraint( - Lower('name'), 'virtual_machine', - name='%(app_label)s_%(class)s_unique_name_virtual_machine' - ), - ) + class Meta(ComponentModel.Meta): verbose_name = _('virtual disk') verbose_name_plural = _('virtual disks') - def __str__(self): - return self.name - def get_absolute_url(self): return reverse('virtualization:virtualdisk', args=[self.pk]) - - def to_objectchange(self, action): - objectchange = super().to_objectchange(action) - objectchange.related_object = self.virtual_machine - return objectchange - - @property - def parent_object(self): - return self.virtual_machine diff --git a/netbox/virtualization/tables/virtualmachines.py b/netbox/virtualization/tables/virtualmachines.py index 132cc77d7..9eb171e01 100644 --- a/netbox/virtualization/tables/virtualmachines.py +++ b/netbox/virtualization/tables/virtualmachines.py @@ -178,9 +178,9 @@ class VirtualDiskTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = VirtualDisk fields = ( - 'pk', 'id', 'virtual_machine', 'name', 'size', 'tags', + 'pk', 'id', 'virtual_machine', 'name', 'size', 'description', 'tags', ) - default_columns = ('pk', 'name', 'virtual_machine', 'size') + default_columns = ('pk', 'name', 'virtual_machine', 'size', 'description') row_attrs = { 'data-name': lambda record: record.name, } @@ -193,6 +193,6 @@ class VirtualMachineVirtualDiskTable(VirtualDiskTable): class Meta(VirtualDiskTable.Meta): fields = ( - 'pk', 'id', 'name', 'size', 'tags', 'actions', + 'pk', 'id', 'name', 'size', 'description', 'tags', 'actions', ) - default_columns = ('pk', 'name', 'size') + default_columns = ('pk', 'name', 'size', 'description')