mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
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:
parent
c1d7696b2a
commit
c6553c45dd
@ -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.
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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):
|
||||||
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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',
|
||||||
|
@ -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()
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user