7537 add serial number to virtual machines (#16407)

* 7537 add serial number to virtual machines

* 7537 add migration

* 7537 add sn to search

* 7537 add to model documentation

* 8984 move serializer field

* 8984 add to detail view and search index

* 7537 serial_number -> serial

* 7537 fix migration

* Add missing serial field

* Give serial field higher precedence for search

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
Arthur Hanson 2024-06-12 07:15:16 -07:00 committed by GitHub
parent c1d7696b2a
commit c6553c45dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 65 additions and 17 deletions

View File

@ -51,3 +51,8 @@ The amount of running memory provisioned, in megabytes.
### Disk ### Disk
The amount of disk storage provisioned, in gigabytes. The amount of disk storage provisioned, in gigabytes.
### Serial Number
Optional serial number assigned to this VM.

View File

@ -31,6 +31,10 @@
<th scope="row">{% trans "Description" %}</th> <th scope="row">{% trans "Description" %}</th>
<td>{{ object.description|placeholder }}</td> <td>{{ object.description|placeholder }}</td>
</tr> </tr>
<tr>
<th scope="row">{% trans "Serial Number" %}</th>
<td>{{ object.serial|placeholder }}</td>
</tr>
<tr> <tr>
<th scope="row">{% trans "Tenant" %}</th> <th scope="row">{% trans "Tenant" %}</th>
<td> <td>

View File

@ -49,9 +49,9 @@ class VirtualMachineSerializer(NetBoxModelSerializer):
class Meta: class Meta:
model = VirtualMachine model = VirtualMachine
fields = [ fields = [
'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'device', 'serial', 'role', 'tenant',
'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'comments', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description',
'config_template', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated', 'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
'interface_count', 'virtual_disk_count', 'interface_count', 'virtual_disk_count',
] ]
brief_fields = ('id', 'url', 'display', 'name', 'description') brief_fields = ('id', 'url', 'display', 'name', 'description')
@ -62,9 +62,9 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
class Meta(VirtualMachineSerializer.Meta): class Meta(VirtualMachineSerializer.Meta):
fields = [ fields = [
'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'device', 'serial', 'role', 'tenant',
'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'comments', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description',
'config_template', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created',
'last_updated', 'interface_count', 'virtual_disk_count', 'last_updated', 'interface_count', 'virtual_disk_count',
] ]

View File

@ -240,7 +240,10 @@ class VirtualMachineFilterSet(
class Meta: class Meta:
model = VirtualMachine model = VirtualMachine
fields = ('id', 'cluster', 'vcpus', 'memory', 'disk', 'description', 'interface_count', 'virtual_disk_count') fields = (
'id', 'cluster', 'vcpus', 'memory', 'disk', 'description', 'interface_count', 'virtual_disk_count',
'serial'
)
def search(self, queryset, name, value): def search(self, queryset, name, value):
if not value.strip(): if not value.strip():
@ -250,7 +253,8 @@ class VirtualMachineFilterSet(
Q(description__icontains=value) | Q(description__icontains=value) |
Q(comments__icontains=value) | Q(comments__icontains=value) |
Q(primary_ip4__address__startswith=value) | Q(primary_ip4__address__startswith=value) |
Q(primary_ip6__address__startswith=value) Q(primary_ip6__address__startswith=value) |
Q(serial__icontains=value)
) )
def _has_primary_ip(self, queryset, name, value): def _has_primary_ip(self, queryset, name, value):

View File

@ -137,7 +137,7 @@ class VirtualMachineImportForm(NetBoxModelImportForm):
model = VirtualMachine model = VirtualMachine
fields = ( fields = (
'name', 'status', 'role', 'site', 'cluster', 'device', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'name', 'status', 'role', 'site', 'cluster', 'device', 'tenant', 'platform', 'vcpus', 'memory', 'disk',
'description', 'config_template', 'comments', 'tags', 'description', 'serial', 'config_template', 'comments', 'tags',
) )

View File

@ -100,7 +100,7 @@ class VirtualMachineFilterForm(
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')), FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
FieldSet( FieldSet(
'status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'config_template_id', 'status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'config_template_id',
'local_context_data', name=_('Attributes') 'local_context_data', 'serial', name=_('Attributes')
), ),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')), FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')), FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
@ -178,6 +178,10 @@ class VirtualMachineFilterForm(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )
) )
serial = forms.CharField(
required=False,
label=_('Serial number')
)
config_template_id = DynamicModelMultipleChoiceField( config_template_id = DynamicModelMultipleChoiceField(
queryset=ConfigTemplate.objects.all(), queryset=ConfigTemplate.objects.all(),
required=False, required=False,

View File

@ -217,7 +217,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
comments = CommentField() comments = CommentField()
fieldsets = ( fieldsets = (
FieldSet('name', 'role', 'status', 'description', 'tags', name=_('Virtual Machine')), FieldSet('name', 'role', 'status', 'description', 'serial', 'tags', name=_('Virtual Machine')),
FieldSet('site', 'cluster', 'device', name=_('Site/Cluster')), FieldSet('site', 'cluster', 'device', name=_('Site/Cluster')),
FieldSet('tenant_group', 'tenant', name=_('Tenancy')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
FieldSet('platform', 'primary_ip4', 'primary_ip6', 'config_template', name=_('Management')), FieldSet('platform', 'primary_ip4', 'primary_ip6', 'config_template', name=_('Management')),
@ -229,8 +229,8 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
model = VirtualMachine model = VirtualMachine
fields = [ fields = [
'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4',
'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'comments', 'tags', 'local_context_data', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'serial', 'comments', 'tags',
'config_template', 'local_context_data', 'config_template',
] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-06-04 17:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('virtualization', '0039_convert_disk_size'),
]
operations = [
migrations.AddField(
model_name='virtualmachine',
name='serial',
field=models.CharField(blank=True, max_length=50),
),
]

View File

@ -127,6 +127,11 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co
null=True, null=True,
verbose_name=_('disk (MB)') verbose_name=_('disk (MB)')
) )
serial = models.CharField(
verbose_name=_('serial number'),
blank=True,
max_length=50
)
# Counter fields # Counter fields
interface_count = CounterCacheField( interface_count = CounterCacheField(

View File

@ -39,11 +39,12 @@ class ClusterTypeIndex(SearchIndex):
class VirtualMachineIndex(SearchIndex): class VirtualMachineIndex(SearchIndex):
model = models.VirtualMachine model = models.VirtualMachine
fields = ( fields = (
('serial', 60),
('name', 100), ('name', 100),
('description', 500), ('description', 500),
('comments', 5000), ('comments', 5000),
) )
display_attrs = ('site', 'cluster', 'device', 'tenant', 'platform', 'status', 'role', 'description') display_attrs = ('site', 'cluster', 'device', 'tenant', 'platform', 'status', 'serial', 'role', 'description')
@register_search @register_search

View File

@ -116,7 +116,7 @@ class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable)
fields = ( fields = (
'pk', 'id', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'tenant_group', 'vcpus', 'pk', 'id', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'tenant_group', 'vcpus',
'memory', 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'description', 'comments', 'config_template', 'memory', 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'description', 'comments', 'config_template',
'contacts', 'tags', 'created', 'last_updated', 'serial', 'contacts', 'tags', 'created', 'last_updated',
) )
default_columns = ( default_columns = (
'pk', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip', 'pk', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip',

View File

@ -327,7 +327,8 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
memory=1, memory=1,
disk=1, disk=1,
description='foobar1', description='foobar1',
local_context_data={"foo": 123} local_context_data={"foo": 123},
serial='111-aaa'
), ),
VirtualMachine( VirtualMachine(
name='Virtual Machine 2', name='Virtual Machine 2',
@ -341,7 +342,8 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
vcpus=2, vcpus=2,
memory=2, memory=2,
disk=2, disk=2,
description='foobar2' description='foobar2',
serial='222-bbb'
), ),
VirtualMachine( VirtualMachine(
name='Virtual Machine 3', name='Virtual Machine 3',
@ -518,6 +520,10 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'primary_ip6_id': [addresses[2].pk]} params = {'primary_ip6_id': [addresses[2].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
def test_serial_number(self):
params = {'serial': ['111-aaa', '222-bbb']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests): class VMInterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = VMInterface.objects.all() queryset = VMInterface.objects.all()

View File

@ -234,6 +234,7 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
'vcpus': 4, 'vcpus': 4,
'memory': 32768, 'memory': 32768,
'disk': 4000, 'disk': 4000,
'serial': 'aaa-111',
'comments': 'Some comments', 'comments': 'Some comments',
'tags': [t.pk for t in tags], 'tags': [t.pk for t in tags],
'local_context_data': None, 'local_context_data': None,