-
-
-
-
-
- {% 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 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')