mirror of
https://github.com/netbox-community/netbox.git
synced 2026-03-29 05:42:09 +02:00
Compare commits
2 Commits
5795-add-v
...
20163-chan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32137117c9 | ||
|
|
296b89ae02 |
@@ -74,7 +74,6 @@ These are considered the "core" application models which are used to model netwo
|
||||
* [tenancy.Tenant](../models/tenancy/tenant.md)
|
||||
* [virtualization.Cluster](../models/virtualization/cluster.md)
|
||||
* [virtualization.VirtualMachine](../models/virtualization/virtualmachine.md)
|
||||
* [virtualization.VirtualMachineType](../models/virtualization/virtualmachinetype.md)
|
||||
* [vpn.IKEPolicy](../models/vpn/ikepolicy.md)
|
||||
* [vpn.IKEProposal](../models/vpn/ikeproposal.md)
|
||||
* [vpn.IPSecPolicy](../models/vpn/ipsecpolicy.md)
|
||||
@@ -94,7 +93,6 @@ Organization models are used to organize and classify primary models.
|
||||
* [dcim.DeviceRole](../models/dcim/devicerole.md)
|
||||
* [dcim.Manufacturer](../models/dcim/manufacturer.md)
|
||||
* [dcim.Platform](../models/dcim/platform.md)
|
||||
* [dcim.RackGroup](../models/dcim/rackgroup.md)
|
||||
* [dcim.RackRole](../models/dcim/rackrole.md)
|
||||
* [ipam.ASNRange](../models/ipam/asnrange.md)
|
||||
* [ipam.RIR](../models/ipam/rir.md)
|
||||
|
||||
@@ -5,37 +5,27 @@ Virtual machines, clusters, and standalone hypervisors can be modeled in NetBox
|
||||
```mermaid
|
||||
flowchart TD
|
||||
ClusterGroup & ClusterType --> Cluster
|
||||
VirtualMachineType --> VirtualMachine
|
||||
Device --> VirtualMachine
|
||||
Cluster --> VirtualMachine
|
||||
Device --> VirtualMachine
|
||||
Platform --> VirtualMachine
|
||||
VirtualMachine --> VMInterface
|
||||
|
||||
click Cluster "../../models/virtualization/cluster/"
|
||||
click ClusterGroup "../../models/virtualization/clustergroup/"
|
||||
click ClusterType "../../models/virtualization/clustertype/"
|
||||
click VirtualMachineType "../../models/virtualization/virtualmachinetype/"
|
||||
click Device "../../models/dcim/device/"
|
||||
click Platform "../../models/dcim/platform/"
|
||||
click VirtualMachine "../../models/virtualization/virtualmachine/"
|
||||
click VMInterface "../../models/virtualization/vminterface/"
|
||||
click Cluster "../../models/virtualization/cluster/"
|
||||
click ClusterGroup "../../models/virtualization/clustergroup/"
|
||||
click ClusterType "../../models/virtualization/clustertype/"
|
||||
click Device "../../models/dcim/device/"
|
||||
click Platform "../../models/dcim/platform/"
|
||||
click VirtualMachine "../../models/virtualization/virtualmachine/"
|
||||
click VMInterface "../../models/virtualization/vminterface/"
|
||||
```
|
||||
|
||||
## Clusters
|
||||
|
||||
A cluster is one or more physical host devices on which virtual machines can run.
|
||||
|
||||
Each cluster must have a type and operational status, and may be assigned to a group. (Both types and groups are user-defined.) Each cluster may designate one or more devices as hosts, however this is optional.
|
||||
|
||||
## Virtual Machine Types
|
||||
|
||||
A virtual machine type provides reusable classification for virtual machines and can define create-time defaults for platform, vCPUs, and memory. This is useful when multiple virtual machines share a common sizing or profile while still allowing per-instance overrides after creation.
|
||||
A cluster is one or more physical host devices on which virtual machines can run. Each cluster must have a type and operational status, and may be assigned to a group. (Both types and groups are user-defined.) Each cluster may designate one or more devices as hosts, however this is optional.
|
||||
|
||||
## Virtual Machines
|
||||
|
||||
A virtual machine is a virtualized compute instance. These behave in NetBox very similarly to device objects, but without any physical attributes.
|
||||
|
||||
For example, a VM may have interfaces assigned to it with IP addresses and VLANs, however its interfaces cannot be connected via cables (because they are virtual). Each VM may define its compute, memory, and storage resources as well. A VM can optionally be assigned a [virtual machine type](../models/virtualization/virtualmachinetype.md) to classify it and provide default values for selected attributes at creation time.
|
||||
A virtual machine is a virtualized compute instance. These behave in NetBox very similarly to device objects, but without any physical attributes. For example, a VM may have interfaces assigned to it with IP addresses and VLANs, however its interfaces cannot be connected via cables (because they are virtual). Each VM may define its compute, memory, and storage resources as well.
|
||||
|
||||
A VM can be placed in one of three ways:
|
||||
|
||||
|
||||
@@ -13,12 +13,6 @@ The virtual machine's configured name. Must be unique within its scoping context
|
||||
- If assigned to a **cluster**: unique within the cluster and tenant.
|
||||
- If assigned to a **device** (no cluster): unique within the device and tenant.
|
||||
|
||||
### Type
|
||||
|
||||
The [virtual machine type](./virtualmachinetype.md) assigned to the VM. A type classifies a virtual machine and can provide default values for platform, vCPUs, and memory when the VM is created.
|
||||
|
||||
Changes made to a virtual machine type do **not** apply retroactively to existing virtual machines.
|
||||
|
||||
### Role
|
||||
|
||||
The functional role assigned to the VM.
|
||||
@@ -51,7 +45,7 @@ The location or host for this VM. At least one must be specified:
|
||||
|
||||
### Platform
|
||||
|
||||
A VM may be associated with a particular [platform](../dcim/platform.md) to indicate its operating system. If a virtual machine type defines a default platform, it will be applied when the VM is created unless an explicit platform is specified.
|
||||
A VM may be associated with a particular [platform](../dcim/platform.md) to indicate its operating system.
|
||||
|
||||
### Primary IPv4 & IPv6 Addresses
|
||||
|
||||
@@ -62,11 +56,11 @@ Each VM may designate one primary IPv4 address and/or one primary IPv6 address f
|
||||
|
||||
### vCPUs
|
||||
|
||||
The number of virtual CPUs provisioned. A VM may be allocated a partial vCPU count (e.g. 1.5 vCPU). If a virtual machine type defines a default vCPU allocation, it will be applied when the VM is created unless an explicit value is specified.
|
||||
The number of virtual CPUs provisioned. A VM may be allocated a partial vCPU count (e.g. 1.5 vCPU).
|
||||
|
||||
### Memory
|
||||
|
||||
The amount of running memory provisioned, in megabytes. If a virtual machine type defines a default memory allocation, it will be applied when the VM is created unless an explicit value is specified.
|
||||
The amount of running memory provisioned, in megabytes.
|
||||
|
||||
### Disk
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# Virtual Machine Types
|
||||
|
||||
A virtual machine type defines a reusable classification and default configuration for [virtual machines](./virtualmachine.md).
|
||||
|
||||
A type can optionally provide default values for a VM's [platform](../dcim/platform.md), vCPU allocation, and memory allocation. When a virtual machine is created with an assigned type, any unset values among these fields will inherit their defaults from the type. Changes made to a virtual machine type do **not** apply retroactively to existing virtual machines.
|
||||
|
||||
## Fields
|
||||
|
||||
### Name
|
||||
|
||||
A unique human-friendly name.
|
||||
|
||||
### Slug
|
||||
|
||||
A unique URL-friendly identifier. (This value can be used for filtering.)
|
||||
|
||||
### Default Platform
|
||||
|
||||
If defined, virtual machines instantiated with this type will automatically inherit the selected platform when no explicit platform is provided.
|
||||
|
||||
### Default vCPUs
|
||||
|
||||
The default number of vCPUs to assign when creating a virtual machine from this type.
|
||||
|
||||
### Default Memory
|
||||
|
||||
The default amount of memory, in megabytes, to assign when creating a virtual machine from this type.
|
||||
@@ -285,10 +285,9 @@ nav:
|
||||
- Cluster: 'models/virtualization/cluster.md'
|
||||
- ClusterGroup: 'models/virtualization/clustergroup.md'
|
||||
- ClusterType: 'models/virtualization/clustertype.md'
|
||||
- VMInterface: 'models/virtualization/vminterface.md'
|
||||
- VirtualDisk: 'models/virtualization/virtualdisk.md'
|
||||
- VirtualMachine: 'models/virtualization/virtualmachine.md'
|
||||
- VirtualMachineType: 'models/virtualization/virtualmachinetype.md'
|
||||
- VMInterface: 'models/virtualization/vminterface.md'
|
||||
- VPN:
|
||||
- IKEPolicy: 'models/vpn/ikepolicy.md'
|
||||
- IKEProposal: 'models/vpn/ikeproposal.md'
|
||||
|
||||
@@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from dcim.models import *
|
||||
from extras.models import Tag
|
||||
from netbox.forms.mixins import CustomFieldsMixin
|
||||
from netbox.forms.mixins import ChangelogMessageMixin, CustomFieldsMixin
|
||||
from utilities.forms import form_from_model
|
||||
from utilities.forms.fields import DynamicModelMultipleChoiceField, ExpandableNameField
|
||||
|
||||
@@ -27,7 +27,8 @@ __all__ = (
|
||||
# Device components
|
||||
#
|
||||
|
||||
class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm):
|
||||
|
||||
class DeviceBulkAddComponentForm(ChangelogMessageMixin, CustomFieldsMixin, ComponentCreateForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
|
||||
@@ -3,11 +3,13 @@ from decimal import Decimal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import yaml
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import override_settings, tag
|
||||
from django.urls import reverse
|
||||
from netaddr import EUI
|
||||
|
||||
from core.models import ObjectType
|
||||
from core.choices import ObjectChangeActionChoices
|
||||
from core.models import ObjectChange, ObjectType
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import *
|
||||
@@ -2741,6 +2743,50 @@ class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
f"{console_ports[2].pk},Console Port 9,New description9",
|
||||
)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
|
||||
def test_bulk_add_components_with_changelog_message(self):
|
||||
device1 = Device.objects.get(name='Device 1')
|
||||
device2 = create_test_device('Device 2')
|
||||
changelog_message = 'Bulk-created console ports'
|
||||
|
||||
obj_perm = ObjectPermission(
|
||||
name='Test permission',
|
||||
actions=['add'],
|
||||
)
|
||||
obj_perm.save()
|
||||
obj_perm.users.add(self.user)
|
||||
obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
|
||||
|
||||
request = {
|
||||
'path': reverse('dcim:device_bulk_add_consoleport'),
|
||||
'data': post_data({
|
||||
'pk': [device1.pk, device2.pk],
|
||||
'name': 'Console Port Bulk',
|
||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||
'description': 'Bulk-created console port',
|
||||
'changelog_message': changelog_message,
|
||||
'_create': True,
|
||||
}),
|
||||
}
|
||||
|
||||
initial_count = self._get_queryset().count()
|
||||
response = self.client.post(**request)
|
||||
self.assertHttpStatus(response, 302)
|
||||
self.assertEqual(initial_count + 2, self._get_queryset().count())
|
||||
|
||||
created_ports = list(ConsolePort.objects.filter(name='Console Port Bulk').order_by('device_id'))
|
||||
self.assertEqual(len(created_ports), 2)
|
||||
self.assertEqual([port.device_id for port in created_ports], [device1.pk, device2.pk])
|
||||
|
||||
objectchanges = ObjectChange.objects.filter(
|
||||
action=ObjectChangeActionChoices.ACTION_CREATE,
|
||||
changed_object_type=ContentType.objects.get_for_model(ConsolePort),
|
||||
changed_object_id__in=[port.pk for port in created_ports],
|
||||
)
|
||||
self.assertEqual(objectchanges.count(), 2)
|
||||
for objectchange in objectchanges:
|
||||
self.assertEqual(objectchange.message, changelog_message)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_trace(self):
|
||||
consoleport = ConsolePort.objects.first()
|
||||
|
||||
@@ -1305,7 +1305,6 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
'virtualdevicecontext',
|
||||
'virtualdisk',
|
||||
'virtualmachine',
|
||||
'virtualmachinetype',
|
||||
'vlan',
|
||||
'vlangroup',
|
||||
'vlantranslationpolicy',
|
||||
|
||||
@@ -270,12 +270,6 @@ VIRTUALIZATION_MENU = Menu(
|
||||
get_model_item('virtualization', 'virtualdisk', _('Virtual Disks')),
|
||||
),
|
||||
),
|
||||
MenuGroup(
|
||||
label=_('Virtual Machine Types'),
|
||||
items=(
|
||||
get_model_item('virtualization', 'virtualmachinetype', _('Virtual Machine Types')),
|
||||
),
|
||||
),
|
||||
MenuGroup(
|
||||
label=_('Clusters'),
|
||||
items=(
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
|
||||
import netaddr
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import ProgrammingError
|
||||
from django.db.models import F, Q, Window, prefetch_related_objects
|
||||
from django.db.models.fields.related import ForeignKey
|
||||
from django.db.models.functions import window
|
||||
@@ -24,6 +26,8 @@ from . import FieldTypes, LookupTypes, get_indexer
|
||||
DEFAULT_LOOKUP_TYPE = LookupTypes.PARTIAL
|
||||
MAX_RESULTS = 1000
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SearchBackend:
|
||||
"""
|
||||
@@ -63,7 +67,12 @@ class SearchBackend:
|
||||
"""
|
||||
Receiver for the post_save signal, responsible for caching object creation/changes.
|
||||
"""
|
||||
self.cache(instance, remove_existing=not created)
|
||||
try:
|
||||
self.cache(instance, remove_existing=not created)
|
||||
except ProgrammingError as e:
|
||||
# The schema may be incomplete during migrations; skip caching.
|
||||
logger.warning(f"Skipping search cache update due to schema error: {e}")
|
||||
pass
|
||||
|
||||
def removal_handler(self, sender, instance, **kwargs):
|
||||
"""
|
||||
|
||||
@@ -1139,6 +1139,7 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
|
||||
new_components = []
|
||||
data = deepcopy(form.cleaned_data)
|
||||
changelog_message = data.pop('changelog_message', '')
|
||||
replication_data = {
|
||||
field: data.pop(field) for field in form.replication_fields
|
||||
}
|
||||
@@ -1160,6 +1161,8 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
|
||||
component_form = self.model_form(component_data)
|
||||
if component_form.is_valid():
|
||||
if changelog_message:
|
||||
component_form.instance._changelog_message = changelog_message
|
||||
instance = component_form.save()
|
||||
logger.debug(f"Created {instance} on {instance.parent_object}")
|
||||
new_components.append(instance)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{% extends 'generic/object.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block extra_controls %}
|
||||
{% if perms.virtualization.add_virtualmachine %}
|
||||
<a href="{% url 'virtualization:virtualmachine_add' %}?virtual_machine_type={{ object.pk }}" class="btn btn-primary">
|
||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> {% trans "Add Virtual Machine" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock extra_controls %}
|
||||
@@ -16,10 +16,10 @@ from netbox.api.fields import ChoiceField, SerializedPKRelatedField
|
||||
from netbox.api.serializers import NetBoxModelSerializer, PrimaryModelSerializer
|
||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||
from users.api.serializers_.mixins import OwnerMixin
|
||||
from virtualization.choices import *
|
||||
from virtualization.models import VirtualDisk, VirtualMachine, VMInterface
|
||||
from vpn.api.serializers_.l2vpn import L2VPNTerminationSerializer
|
||||
|
||||
from ...choices import *
|
||||
from ...models import VirtualDisk, VirtualMachine, VirtualMachineType, VMInterface
|
||||
from .clusters import ClusterSerializer
|
||||
from .nested import NestedVMInterfaceSerializer
|
||||
|
||||
@@ -27,29 +27,11 @@ __all__ = (
|
||||
'VMInterfaceSerializer',
|
||||
'VirtualDiskSerializer',
|
||||
'VirtualMachineSerializer',
|
||||
'VirtualMachineTypeSerializer',
|
||||
'VirtualMachineWithConfigContextSerializer',
|
||||
)
|
||||
|
||||
|
||||
class VirtualMachineTypeSerializer(PrimaryModelSerializer):
|
||||
default_platform = PlatformSerializer(nested=True, required=False, allow_null=True)
|
||||
|
||||
# Counter fields
|
||||
virtual_machine_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = VirtualMachineType
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'default_platform', 'default_vcpus',
|
||||
'default_memory', 'description', 'owner', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'virtual_machine_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description')
|
||||
|
||||
|
||||
class VirtualMachineSerializer(PrimaryModelSerializer):
|
||||
virtual_machine_type = VirtualMachineTypeSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
status = ChoiceField(choices=VirtualMachineStatusChoices, required=False)
|
||||
start_on_boot = ChoiceField(choices=VirtualMachineStartOnBootChoices, required=False)
|
||||
site = SiteSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
@@ -70,10 +52,10 @@ class VirtualMachineSerializer(PrimaryModelSerializer):
|
||||
class Meta:
|
||||
model = VirtualMachine
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'virtual_machine_type', 'role', 'status', 'start_on_boot',
|
||||
'site', 'cluster', 'device', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory',
|
||||
'disk', 'description', 'serial', 'tenant', 'owner', 'comments', 'tags', 'local_context_data',
|
||||
'config_template', 'custom_fields', 'created', 'last_updated', 'interface_count', 'virtual_disk_count',
|
||||
'id', 'url', 'display_url', 'display', 'name', 'status', 'start_on_boot', 'site', 'cluster', 'device',
|
||||
'serial', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory',
|
||||
'disk', 'description', 'owner', 'comments', 'config_template', 'local_context_data', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'interface_count', 'virtual_disk_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
@@ -83,11 +65,10 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
|
||||
|
||||
class Meta(VirtualMachineSerializer.Meta):
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'virtual_machine_type', 'role', 'status', 'start_on_boot',
|
||||
'site', 'cluster', 'device', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory',
|
||||
'disk', 'description', 'serial', 'tenant', 'owner', 'comments', 'tags', 'local_context_data',
|
||||
'config_template', 'custom_fields', 'created', 'last_updated', 'interface_count', 'virtual_disk_count',
|
||||
'config_context',
|
||||
'id', 'url', 'display_url', 'display', 'name', 'status', 'start_on_boot', 'site', 'cluster', 'device',
|
||||
'serial', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory',
|
||||
'disk', 'description', 'owner', 'comments', 'config_template', 'local_context_data', 'tags',
|
||||
'custom_fields', 'config_context', 'created', 'last_updated', 'interface_count', 'virtual_disk_count',
|
||||
]
|
||||
|
||||
@extend_schema_field(serializers.JSONField(allow_null=True))
|
||||
|
||||
@@ -10,9 +10,6 @@ router.register('cluster-types', views.ClusterTypeViewSet)
|
||||
router.register('cluster-groups', views.ClusterGroupViewSet)
|
||||
router.register('clusters', views.ClusterViewSet)
|
||||
|
||||
# Virtual machine types
|
||||
router.register('virtual-machine-types', views.VirtualMachineTypeViewSet)
|
||||
|
||||
# VirtualMachines
|
||||
router.register('virtual-machines', views.VirtualMachineViewSet)
|
||||
router.register('interfaces', views.VMInterfaceViewSet)
|
||||
|
||||
@@ -14,7 +14,6 @@ class VirtualizationRootView(APIRootView):
|
||||
"""
|
||||
Virtualization API root view
|
||||
"""
|
||||
|
||||
def get_view_name(self):
|
||||
return 'Virtualization'
|
||||
|
||||
@@ -23,7 +22,6 @@ class VirtualizationRootView(APIRootView):
|
||||
# Clusters
|
||||
#
|
||||
|
||||
|
||||
class ClusterTypeViewSet(NetBoxModelViewSet):
|
||||
queryset = ClusterType.objects.all()
|
||||
serializer_class = serializers.ClusterTypeSerializer
|
||||
@@ -46,22 +44,10 @@ class ClusterViewSet(NetBoxModelViewSet):
|
||||
filterset_class = filtersets.ClusterFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Virtual machine types
|
||||
#
|
||||
|
||||
|
||||
class VirtualMachineTypeViewSet(NetBoxModelViewSet):
|
||||
queryset = VirtualMachineType.objects.all()
|
||||
serializer_class = serializers.VirtualMachineTypeSerializer
|
||||
filterset_class = filtersets.VirtualMachineTypeFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Virtual machines
|
||||
#
|
||||
|
||||
|
||||
class VirtualMachineViewSet(ConfigContextQuerySetMixin, RenderConfigMixin, NetBoxModelViewSet):
|
||||
queryset = VirtualMachine.objects.all()
|
||||
filterset_class = filtersets.VirtualMachineFilterSet
|
||||
|
||||
@@ -9,10 +9,10 @@ class VirtualizationConfig(AppConfig):
|
||||
from utilities.counters import connect_counters
|
||||
|
||||
from . import search, signals # noqa: F401
|
||||
from .models import VirtualMachine, VirtualMachineType
|
||||
from .models import VirtualMachine
|
||||
|
||||
# Register models
|
||||
register_models(*self.get_models())
|
||||
|
||||
# Register counters
|
||||
connect_counters(VirtualMachine, VirtualMachineType)
|
||||
connect_counters(VirtualMachine)
|
||||
|
||||
@@ -26,7 +26,6 @@ __all__ = (
|
||||
'VMInterfaceFilterSet',
|
||||
'VirtualDiskFilterSet',
|
||||
'VirtualMachineFilterSet',
|
||||
'VirtualMachineTypeFilterSet',
|
||||
)
|
||||
|
||||
|
||||
@@ -92,45 +91,6 @@ class ClusterFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ScopedFilterSet,
|
||||
)
|
||||
|
||||
|
||||
@register_filterset
|
||||
class VirtualMachineTypeFilterSet(PrimaryModelFilterSet):
|
||||
default_platform_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Platform.objects.all(),
|
||||
field_name='default_platform',
|
||||
lookup_expr='in',
|
||||
label=_('Default platform (ID)'),
|
||||
)
|
||||
default_platform = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Platform.objects.all(),
|
||||
field_name='default_platform',
|
||||
to_field_name='slug',
|
||||
lookup_expr='in',
|
||||
label=_('Default platform (slug)'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualMachineType
|
||||
fields = (
|
||||
'id',
|
||||
'name',
|
||||
'slug',
|
||||
'default_vcpus',
|
||||
'default_memory',
|
||||
'description',
|
||||
'virtual_machine_count',
|
||||
)
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
|
||||
return queryset.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(description__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
)
|
||||
|
||||
|
||||
@register_filterset
|
||||
class VirtualMachineFilterSet(
|
||||
PrimaryModelFilterSet,
|
||||
@@ -139,18 +99,6 @@ class VirtualMachineFilterSet(
|
||||
LocalConfigContextFilterSet,
|
||||
PrimaryIPFilterSet,
|
||||
):
|
||||
virtual_machine_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=VirtualMachineType.objects.all(),
|
||||
distinct=False,
|
||||
label=_('Virtual machine type (ID)'),
|
||||
)
|
||||
virtual_machine_type = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='virtual_machine_type__slug',
|
||||
queryset=VirtualMachineType.objects.all(),
|
||||
distinct=False,
|
||||
to_field_name='slug',
|
||||
label=_('Virtual machine type (slug)'),
|
||||
)
|
||||
status = django_filters.MultipleChoiceFilter(
|
||||
choices=VirtualMachineStatusChoices,
|
||||
distinct=False,
|
||||
|
||||
@@ -14,9 +14,8 @@ from utilities.forms import BulkRenameForm, add_blank_choice
|
||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||
|
||||
from ..choices import *
|
||||
from ..models import *
|
||||
from virtualization.choices import *
|
||||
from virtualization.models import *
|
||||
|
||||
__all__ = (
|
||||
'ClusterBulkEditForm',
|
||||
@@ -27,7 +26,6 @@ __all__ = (
|
||||
'VirtualDiskBulkEditForm',
|
||||
'VirtualDiskBulkRenameForm',
|
||||
'VirtualMachineBulkEditForm',
|
||||
'VirtualMachineTypeBulkEditForm',
|
||||
)
|
||||
|
||||
|
||||
@@ -80,37 +78,7 @@ class ClusterBulkEditForm(ScopedBulkEditForm, PrimaryModelBulkEditForm):
|
||||
)
|
||||
|
||||
|
||||
class VirtualMachineTypeBulkEditForm(PrimaryModelBulkEditForm):
|
||||
default_platform = DynamicModelChoiceField(
|
||||
label=_('Default platform'),
|
||||
queryset=Platform.objects.all(),
|
||||
required=False
|
||||
)
|
||||
default_vcpus = forms.IntegerField(
|
||||
label=_('Default vCPUs'),
|
||||
required=False,
|
||||
)
|
||||
default_memory = forms.IntegerField(
|
||||
label=_('Default Memory (MB)'),
|
||||
required=False,
|
||||
)
|
||||
|
||||
model = VirtualMachineType
|
||||
fieldsets = (
|
||||
FieldSet('description', name=_('Virtual Machine Type')),
|
||||
FieldSet('default_platform', 'default_vcpus', 'default_memory', name=_('Defaults')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'default_platform', 'default_vcpus', 'default_memory', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
class VirtualMachineBulkEditForm(PrimaryModelBulkEditForm):
|
||||
virtual_machine_type = DynamicModelChoiceField(
|
||||
label=_('Virtual machine type'),
|
||||
queryset=VirtualMachineType.objects.all(),
|
||||
required=False
|
||||
)
|
||||
status = forms.ChoiceField(
|
||||
label=_('Status'),
|
||||
choices=add_blank_choice(VirtualMachineStatusChoices),
|
||||
@@ -184,14 +152,13 @@ class VirtualMachineBulkEditForm(PrimaryModelBulkEditForm):
|
||||
|
||||
model = VirtualMachine
|
||||
fieldsets = (
|
||||
FieldSet('virtual_machine_type', 'status', 'start_on_boot', 'role', 'tenant', 'platform', 'description'),
|
||||
FieldSet('status', 'start_on_boot', 'role', 'tenant', 'platform', 'description'),
|
||||
FieldSet('site', 'cluster', 'device', name=_('Placement')),
|
||||
FieldSet('vcpus', 'memory', 'disk', name=_('Resources')),
|
||||
FieldSet('config_template', name=_('Configuration')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'virtual_machine_type', 'role', 'site', 'cluster', 'device', 'platform', 'vcpus', 'memory', 'disk', 'tenant',
|
||||
'description', 'comments',
|
||||
'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -10,9 +10,8 @@ from ipam.models import VLAN, VRF, VLANGroup
|
||||
from netbox.forms import NetBoxModelImportForm, OrganizationalModelImportForm, OwnerCSVMixin, PrimaryModelImportForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, CSVModelMultipleChoiceField
|
||||
|
||||
from ..choices import *
|
||||
from ..models import *
|
||||
from virtualization.choices import *
|
||||
from virtualization.models import *
|
||||
|
||||
__all__ = (
|
||||
'ClusterGroupImportForm',
|
||||
@@ -21,7 +20,6 @@ __all__ = (
|
||||
'VMInterfaceImportForm',
|
||||
'VirtualDiskImportForm',
|
||||
'VirtualMachineImportForm',
|
||||
'VirtualMachineTypeImportForm',
|
||||
)
|
||||
|
||||
|
||||
@@ -84,31 +82,7 @@ class ClusterImportForm(ScopedImportForm, PrimaryModelImportForm):
|
||||
}
|
||||
|
||||
|
||||
class VirtualMachineTypeImportForm(PrimaryModelImportForm):
|
||||
default_platform = CSVModelChoiceField(
|
||||
label=_('Default platform'),
|
||||
queryset=Platform.objects.all(),
|
||||
required=False,
|
||||
to_field_name='name',
|
||||
help_text=_('Assigned default platform'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualMachineType
|
||||
fields = (
|
||||
'name', 'slug', 'default_platform', 'default_vcpus', 'default_memory', 'description',
|
||||
'owner', 'comments', 'tags',
|
||||
)
|
||||
|
||||
|
||||
class VirtualMachineImportForm(PrimaryModelImportForm):
|
||||
virtual_machine_type = CSVModelChoiceField(
|
||||
label=_('Virtual machine type'),
|
||||
queryset=VirtualMachineType.objects.all(),
|
||||
to_field_name='name',
|
||||
required=False,
|
||||
help_text=_('Optional virtual machine type'),
|
||||
)
|
||||
status = CSVChoiceField(
|
||||
label=_('Status'),
|
||||
choices=VirtualMachineStatusChoices,
|
||||
@@ -175,9 +149,8 @@ class VirtualMachineImportForm(PrimaryModelImportForm):
|
||||
class Meta:
|
||||
model = VirtualMachine
|
||||
fields = (
|
||||
'name', 'virtual_machine_type', 'role', 'status', 'start_on_boot', 'site', 'cluster', 'device',
|
||||
'platform', 'vcpus', 'memory', 'disk', 'description', 'serial',
|
||||
'tenant', 'owner', 'comments', 'tags', 'config_template',
|
||||
'name', 'status', 'start_on_boot', 'role', 'site', 'cluster', 'device', 'tenant', 'platform', 'vcpus',
|
||||
'memory', 'disk', 'description', 'serial', 'config_template', 'comments', 'owner', 'tags',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -12,11 +12,10 @@ from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
|
||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
|
||||
from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from virtualization.choices import *
|
||||
from virtualization.models import *
|
||||
from vpn.models import L2VPN
|
||||
|
||||
from ..choices import *
|
||||
from ..models import *
|
||||
|
||||
__all__ = (
|
||||
'ClusterFilterForm',
|
||||
'ClusterGroupFilterForm',
|
||||
@@ -24,7 +23,6 @@ __all__ = (
|
||||
'VMInterfaceFilterForm',
|
||||
'VirtualDiskFilterForm',
|
||||
'VirtualMachineFilterForm',
|
||||
'VirtualMachineTypeFilterForm',
|
||||
)
|
||||
|
||||
|
||||
@@ -102,43 +100,6 @@ class ClusterFilterForm(TenancyFilterForm, ContactModelFilterForm, PrimaryModelF
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VirtualMachineTypeFilterForm(PrimaryModelFilterSetForm):
|
||||
model = VirtualMachineType
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet(
|
||||
'default_platform_id', 'default_vcpus', 'default_memory', 'virtual_machine_count',
|
||||
name=_('Attributes'),
|
||||
),
|
||||
FieldSet('owner_group_id', 'owner_id', name=_('Ownership')),
|
||||
)
|
||||
|
||||
selector_fields = ('filter_id', 'q')
|
||||
|
||||
default_platform_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Platform.objects.all(),
|
||||
required=False,
|
||||
label=_('Default platform'),
|
||||
)
|
||||
default_vcpus = forms.DecimalField(
|
||||
label=_('Default vCPUs'),
|
||||
required=False,
|
||||
)
|
||||
default_memory = forms.IntegerField(
|
||||
label=_('Default memory (MB)'),
|
||||
required=False,
|
||||
min_value=0,
|
||||
)
|
||||
virtual_machine_count = forms.IntegerField(
|
||||
label=_('Virtual machine count'),
|
||||
required=False,
|
||||
min_value=0,
|
||||
)
|
||||
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VirtualMachineFilterForm(
|
||||
LocalConfigContextFilterForm,
|
||||
TenancyFilterForm,
|
||||
@@ -151,20 +112,13 @@ class VirtualMachineFilterForm(
|
||||
FieldSet('cluster_group_id', 'cluster_type_id', 'cluster_id', 'device_id', name=_('Cluster')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||
FieldSet(
|
||||
'virtual_machine_type_id', 'status', 'start_on_boot', 'role_id', 'platform_id', 'mac_address',
|
||||
'has_primary_ip', 'config_template_id', 'local_context_data', 'serial',
|
||||
name=_('Attributes')
|
||||
'status', 'start_on_boot', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'config_template_id',
|
||||
'local_context_data', 'serial', name=_('Attributes')
|
||||
),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('owner_group_id', 'owner_id', name=_('Ownership')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
)
|
||||
virtual_machine_type_id = DynamicModelMultipleChoiceField(
|
||||
queryset=VirtualMachineType.objects.all(),
|
||||
required=False,
|
||||
null_option='None',
|
||||
label=_('Virtual machine type'),
|
||||
)
|
||||
cluster_group_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
required=False,
|
||||
|
||||
@@ -14,11 +14,10 @@ from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelF
|
||||
from netbox.forms.mixins import OwnerMixin
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField
|
||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import HTMXSelect
|
||||
|
||||
from ..models import *
|
||||
from virtualization.models import *
|
||||
|
||||
__all__ = (
|
||||
'ClusterAddDevicesForm',
|
||||
@@ -29,7 +28,6 @@ __all__ = (
|
||||
'VMInterfaceForm',
|
||||
'VirtualDiskForm',
|
||||
'VirtualMachineForm',
|
||||
'VirtualMachineTypeForm',
|
||||
)
|
||||
|
||||
|
||||
@@ -169,35 +167,7 @@ class ClusterRemoveDevicesForm(ConfirmationForm):
|
||||
)
|
||||
|
||||
|
||||
class VirtualMachineTypeForm(PrimaryModelForm):
|
||||
slug = SlugField()
|
||||
default_platform = DynamicModelChoiceField(
|
||||
label=_('Default platform'),
|
||||
queryset=Platform.objects.all(),
|
||||
required=False,
|
||||
selector=True,
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('name', 'slug', 'description', 'tags', name=_('Virtual Machine Type')),
|
||||
FieldSet('default_platform', 'default_vcpus', 'default_memory', name=_('Defaults')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualMachineType
|
||||
fields = (
|
||||
'name', 'slug', 'default_platform', 'default_vcpus', 'default_memory', 'description',
|
||||
'owner', 'comments', 'tags',
|
||||
)
|
||||
|
||||
|
||||
class VirtualMachineForm(TenancyForm, PrimaryModelForm):
|
||||
virtual_machine_type = DynamicModelChoiceField(
|
||||
label=_('Type'),
|
||||
queryset=VirtualMachineType.objects.all(),
|
||||
required=False,
|
||||
selector=True,
|
||||
)
|
||||
site = DynamicModelChoiceField(
|
||||
label=_('Site'),
|
||||
queryset=Site.objects.all(),
|
||||
@@ -256,10 +226,7 @@ class VirtualMachineForm(TenancyForm, PrimaryModelForm):
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
'name', 'virtual_machine_type', 'role', 'status', 'start_on_boot', 'description', 'serial', 'tags',
|
||||
name=_('Virtual Machine')
|
||||
),
|
||||
FieldSet('name', 'role', 'status', 'start_on_boot', 'description', 'serial', 'tags', name=_('Virtual Machine')),
|
||||
FieldSet('site', 'cluster', 'device', name=_('Placement')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
FieldSet('platform', 'primary_ip4', 'primary_ip6', 'config_template', name=_('Management')),
|
||||
@@ -270,9 +237,9 @@ class VirtualMachineForm(TenancyForm, PrimaryModelForm):
|
||||
class Meta:
|
||||
model = VirtualMachine
|
||||
fields = [
|
||||
'name', 'virtual_machine_type', 'role', 'status', 'start_on_boot', 'site', 'cluster', 'device',
|
||||
'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'serial',
|
||||
'tenant_group', 'tenant', 'owner', 'comments', 'tags', 'local_context_data', 'config_template',
|
||||
'name', 'status', 'start_on_boot', 'site', 'cluster', 'device', 'role', 'tenant_group', 'tenant',
|
||||
'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'serial', 'owner',
|
||||
'comments', 'tags', 'local_context_data', 'config_template',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Annotated
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from strawberry.scalars import ID
|
||||
from strawberry_django import BaseFilterLookup, ComparisonFilterLookup, StrFilterLookup
|
||||
from strawberry_django import BaseFilterLookup, FilterLookup, StrFilterLookup
|
||||
|
||||
from dcim.graphql.filter_mixins import InterfaceBaseFilterMixin, RenderConfigFilterMixin, ScopedFilterMixin
|
||||
from extras.graphql.filter_mixins import ConfigContextFilterMixin
|
||||
@@ -34,7 +34,6 @@ __all__ = (
|
||||
'VMInterfaceFilter',
|
||||
'VirtualDiskFilter',
|
||||
'VirtualMachineFilter',
|
||||
'VirtualMachineTypeFilter',
|
||||
)
|
||||
|
||||
|
||||
@@ -69,24 +68,6 @@ class ClusterTypeFilter(OrganizationalModelFilter):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter_type(models.VirtualMachineType, lookups=True)
|
||||
class VirtualMachineTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilter):
|
||||
default_platform: Annotated['PlatformFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
default_platform_id: ID | None = strawberry_django.filter_field()
|
||||
default_vcpus: Annotated['FloatLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
default_memory: Annotated['IntegerLookup', strawberry.lazy('netbox.graphql.filter_lookups')] | None = (
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
instances: Annotated['VirtualMachineFilter', strawberry.lazy('virtualization.graphql.filters')] | None = (
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
virtual_machine_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
|
||||
|
||||
|
||||
@strawberry_django.filter_type(models.VirtualMachine, lookups=True)
|
||||
class VirtualMachineFilter(
|
||||
ContactFilterMixin,
|
||||
@@ -97,9 +78,6 @@ class VirtualMachineFilter(
|
||||
PrimaryModelFilter,
|
||||
):
|
||||
name: StrFilterLookup[str] | None = strawberry_django.filter_field()
|
||||
virtual_machine_type: (
|
||||
Annotated['VirtualMachineTypeFilter', strawberry.lazy('virtualization.graphql.filters')] | None
|
||||
) = strawberry_django.filter_field()
|
||||
site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field()
|
||||
site_id: ID | None = strawberry_django.filter_field()
|
||||
cluster: Annotated['ClusterFilter', strawberry.lazy('virtualization.graphql.filters')] | None = (
|
||||
@@ -114,7 +92,9 @@ class VirtualMachineFilter(
|
||||
platform_id: ID | None = strawberry_django.filter_field()
|
||||
status: (
|
||||
BaseFilterLookup[Annotated['VirtualMachineStatusEnum', strawberry.lazy('virtualization.graphql.enums')]] | None
|
||||
) = strawberry_django.filter_field()
|
||||
) = (
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
role: Annotated['DeviceRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
@@ -137,6 +117,8 @@ class VirtualMachineFilter(
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
serial: StrFilterLookup[str] | None = strawberry_django.filter_field()
|
||||
interface_count: FilterLookup[int] | None = strawberry_django.filter_field()
|
||||
virtual_disk_count: FilterLookup[int] | None = strawberry_django.filter_field()
|
||||
interfaces: Annotated['VMInterfaceFilter', strawberry.lazy('virtualization.graphql.filters')] | None = (
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
@@ -147,11 +129,10 @@ class VirtualMachineFilter(
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
start_on_boot: (
|
||||
BaseFilterLookup[Annotated['VirtualMachineStartOnBootEnum', strawberry.lazy('virtualization.graphql.enums')]]
|
||||
| None
|
||||
) = strawberry_django.filter_field()
|
||||
interface_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
|
||||
virtual_disk_count: ComparisonFilterLookup[int] | None = strawberry_django.filter_field()
|
||||
BaseFilterLookup[Annotated['VirtualMachineStartOnBootEnum', strawberry.lazy('virtualization.graphql.enums')]
|
||||
] | None) = (
|
||||
strawberry_django.filter_field()
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter_type(models.VMInterface, lookups=True)
|
||||
|
||||
@@ -4,7 +4,7 @@ import strawberry_django
|
||||
from .types import *
|
||||
|
||||
|
||||
@strawberry.type(name='Query')
|
||||
@strawberry.type(name="Query")
|
||||
class VirtualizationQuery:
|
||||
cluster: ClusterType = strawberry_django.field()
|
||||
cluster_list: list[ClusterType] = strawberry_django.field()
|
||||
@@ -15,9 +15,6 @@ class VirtualizationQuery:
|
||||
cluster_type: ClusterTypeType = strawberry_django.field()
|
||||
cluster_type_list: list[ClusterTypeType] = strawberry_django.field()
|
||||
|
||||
virtual_machine_type: VirtualMachineTypeType = strawberry_django.field()
|
||||
virtual_machine_type_list: list[VirtualMachineTypeType] = strawberry_django.field()
|
||||
|
||||
virtual_machine: VirtualMachineType = strawberry_django.field()
|
||||
virtual_machine_list: list[VirtualMachineType] = strawberry_django.field()
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ __all__ = (
|
||||
'VMInterfaceType',
|
||||
'VirtualDiskType',
|
||||
'VirtualMachineType',
|
||||
'VirtualMachineTypeType',
|
||||
)
|
||||
|
||||
|
||||
@@ -92,19 +91,6 @@ class ClusterTypeType(OrganizationalObjectType):
|
||||
clusters: list[ClusterType]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VirtualMachineType,
|
||||
fields='__all__',
|
||||
filters=VirtualMachineTypeFilter,
|
||||
pagination=True
|
||||
)
|
||||
class VirtualMachineTypeType(PrimaryObjectType):
|
||||
virtual_machine_count: BigInt
|
||||
default_platform: Annotated['PlatformType', strawberry.lazy('dcim.graphql.types')] | None
|
||||
|
||||
instances: list[Annotated['VirtualMachineType', strawberry.lazy('virtualization.graphql.types')]]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VirtualMachine,
|
||||
fields='__all__',
|
||||
@@ -115,7 +101,6 @@ class VirtualMachineType(ConfigContextMixin, ContactsMixin, PrimaryObjectType):
|
||||
interface_count: BigInt
|
||||
virtual_disk_count: BigInt
|
||||
interface_count: BigInt
|
||||
virtual_machine_type: Annotated['VirtualMachineTypeType', strawberry.lazy('virtualization.graphql.types')] | None
|
||||
config_template: Annotated["ConfigTemplateType", strawberry.lazy('extras.graphql.types')] | None
|
||||
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
cluster: Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')] | None
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
from decimal import Decimal
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import django.db.models.functions.text
|
||||
import taggit.managers
|
||||
from django.db import migrations, models
|
||||
|
||||
import netbox.models.deletion
|
||||
import utilities.fields
|
||||
import utilities.json
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('virtualization', '0053_virtualmachine_standalone_device_assignment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VirtualMachineType',
|
||||
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),
|
||||
),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
('comments', models.TextField(blank=True)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('slug', models.SlugField(max_length=100)),
|
||||
(
|
||||
'default_vcpus',
|
||||
models.DecimalField(
|
||||
blank=True,
|
||||
decimal_places=2,
|
||||
max_digits=6,
|
||||
null=True,
|
||||
validators=[django.core.validators.MinValueValidator(Decimal('0.01'))],
|
||||
),
|
||||
),
|
||||
('default_memory', models.PositiveIntegerField(blank=True, null=True)),
|
||||
(
|
||||
'virtual_machine_count',
|
||||
utilities.fields.CounterCacheField(
|
||||
default=0,
|
||||
editable=False,
|
||||
to_field='virtual_machine_type',
|
||||
to_model='virtualization.VirtualMachine',
|
||||
),
|
||||
),
|
||||
(
|
||||
'default_platform',
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='+',
|
||||
to='dcim.platform',
|
||||
),
|
||||
),
|
||||
(
|
||||
'owner',
|
||||
models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'virtual machine type',
|
||||
'verbose_name_plural': 'virtual machine types',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
bases=(netbox.models.deletion.DeleteMixin, models.Model),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='virtualmachine',
|
||||
name='virtual_machine_type',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name='instances',
|
||||
to='virtualization.virtualmachinetype',
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='virtualmachinetype',
|
||||
constraint=models.UniqueConstraint(
|
||||
django.db.models.functions.text.Lower('name'),
|
||||
name='virtualization_virtualmachinetype_unique_name',
|
||||
violation_error_message='Virtual machine type name must be unique.',
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='virtualmachinetype',
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=('slug',),
|
||||
name='virtualization_virtualmachinetype_unique_slug',
|
||||
violation_error_message='Virtual machine type slug must be unique.',
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -20,88 +20,16 @@ from utilities.fields import CounterCacheField, NaturalOrderingField
|
||||
from utilities.ordering import naturalize_interface
|
||||
from utilities.query_functions import CollateAsChar
|
||||
from utilities.tracking import TrackingModelMixin
|
||||
|
||||
from ..choices import *
|
||||
from virtualization.choices import *
|
||||
|
||||
__all__ = (
|
||||
'VMInterface',
|
||||
'VirtualDisk',
|
||||
'VirtualMachine',
|
||||
'VirtualMachineType',
|
||||
)
|
||||
|
||||
|
||||
class VirtualMachineType(ImageAttachmentsMixin, PrimaryModel):
|
||||
"""
|
||||
A type defining default attributes (platform, vCPUs, memory, etc.) for virtual machines.
|
||||
"""
|
||||
|
||||
name = models.CharField(
|
||||
verbose_name=_('name'),
|
||||
max_length=100,
|
||||
)
|
||||
slug = models.SlugField(
|
||||
verbose_name=_('slug'),
|
||||
max_length=100,
|
||||
)
|
||||
default_platform = models.ForeignKey(
|
||||
to='dcim.Platform',
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='+',
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_('default platform'),
|
||||
)
|
||||
default_vcpus = models.DecimalField(
|
||||
verbose_name=_('default vCPUs'),
|
||||
max_digits=6,
|
||||
decimal_places=2,
|
||||
blank=True,
|
||||
null=True,
|
||||
validators=(MinValueValidator(decimal.Decimal('0.01')),),
|
||||
)
|
||||
default_memory = models.PositiveIntegerField(
|
||||
verbose_name=_('default memory (MB)'),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
# Counter fields
|
||||
virtual_machine_count = CounterCacheField(
|
||||
to_model='virtualization.VirtualMachine',
|
||||
to_field='virtual_machine_type',
|
||||
)
|
||||
|
||||
clone_fields = (
|
||||
'default_platform',
|
||||
'default_vcpus',
|
||||
'default_memory',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('name',)
|
||||
constraints = (
|
||||
models.UniqueConstraint(
|
||||
Lower('name'),
|
||||
name='%(app_label)s_%(class)s_unique_name',
|
||||
violation_error_message=_('Virtual machine type name must be unique.'),
|
||||
),
|
||||
models.UniqueConstraint(
|
||||
fields=('slug',),
|
||||
name='%(app_label)s_%(class)s_unique_slug',
|
||||
violation_error_message=_('Virtual machine type slug must be unique.'),
|
||||
),
|
||||
)
|
||||
verbose_name = _('virtual machine type')
|
||||
verbose_name_plural = _('virtual machine types')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class VirtualMachine(
|
||||
ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, ConfigContextModel, TrackingModelMixin, PrimaryModel
|
||||
):
|
||||
class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, ConfigContextModel, PrimaryModel):
|
||||
"""
|
||||
A virtual machine which runs on a Cluster or a standalone Device.
|
||||
|
||||
@@ -114,15 +42,6 @@ class VirtualMachine(
|
||||
When a Cluster or Device is set, the Site is automatically inherited if not explicitly provided.
|
||||
If a Device belongs to a Cluster, the Cluster must also be specified on the VM.
|
||||
"""
|
||||
|
||||
virtual_machine_type = models.ForeignKey(
|
||||
to='virtualization.VirtualMachineType',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='instances',
|
||||
verbose_name=_('type'),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
site = models.ForeignKey(
|
||||
to='dcim.Site',
|
||||
on_delete=models.PROTECT,
|
||||
@@ -243,8 +162,7 @@ class VirtualMachine(
|
||||
objects = ConfigContextModelQuerySet.as_manager()
|
||||
|
||||
clone_fields = (
|
||||
'virtual_machine_type', 'site', 'cluster', 'device', 'tenant', 'platform', 'status', 'role', 'vcpus', 'memory',
|
||||
'disk',
|
||||
'site', 'cluster', 'device', 'tenant', 'platform', 'status', 'role', 'vcpus', 'memory', 'disk',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -364,29 +282,8 @@ class VirtualMachine(
|
||||
elif self.device and self.device.site:
|
||||
self.site = self.device.site
|
||||
|
||||
if self._state.adding:
|
||||
self.apply_type_defaults()
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def apply_type_defaults(self):
|
||||
"""
|
||||
Populate any empty fields with defaults from the assigned VirtualMachineType.
|
||||
"""
|
||||
if not self.virtual_machine_type_id:
|
||||
return
|
||||
|
||||
defaults = {
|
||||
'platform_id': 'default_platform_id',
|
||||
'vcpus': 'default_vcpus',
|
||||
'memory': 'default_memory',
|
||||
}
|
||||
for field, default_field in defaults.items():
|
||||
if getattr(self, field) is None:
|
||||
default_value = getattr(self.virtual_machine_type, default_field)
|
||||
if default_value is not None:
|
||||
setattr(self, field, default_value)
|
||||
|
||||
def get_status_color(self):
|
||||
return VirtualMachineStatusChoices.colors.get(self.status)
|
||||
|
||||
|
||||
@@ -38,18 +38,6 @@ class ClusterTypeIndex(SearchIndex):
|
||||
display_attrs = ('description',)
|
||||
|
||||
|
||||
@register_search
|
||||
class VirtualMachineTypeIndex(SearchIndex):
|
||||
model = models.VirtualMachineType
|
||||
fields = (
|
||||
('name', 100),
|
||||
('slug', 110),
|
||||
('description', 500),
|
||||
('comments', 5000),
|
||||
)
|
||||
display_attrs = ('default_platform', 'default_vcpus', 'default_memory', 'description')
|
||||
|
||||
|
||||
@register_search
|
||||
class VirtualMachineIndex(SearchIndex):
|
||||
model = models.VirtualMachine
|
||||
|
||||
@@ -5,64 +5,28 @@ from dcim.tables.devices import BaseInterfaceTable
|
||||
from netbox.tables import NetBoxTable, PrimaryModelTable, columns
|
||||
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
|
||||
from utilities.templatetags.helpers import humanize_disk_megabytes
|
||||
from virtualization.models import VirtualDisk, VirtualMachine, VMInterface
|
||||
|
||||
from ..models import VirtualDisk, VirtualMachine, VirtualMachineType, VMInterface
|
||||
from .template_code import *
|
||||
|
||||
__all__ = (
|
||||
'VMInterfaceTable',
|
||||
'VirtualDiskTable',
|
||||
'VirtualMachineTable',
|
||||
'VirtualMachineTypeTable',
|
||||
'VirtualMachineVMInterfaceTable',
|
||||
'VirtualMachineVirtualDiskTable',
|
||||
)
|
||||
|
||||
#
|
||||
# Virtual machine types
|
||||
#
|
||||
|
||||
|
||||
class VirtualMachineTypeTable(PrimaryModelTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True,
|
||||
)
|
||||
default_platform = tables.Column(
|
||||
verbose_name=_('Default platform'),
|
||||
linkify=True,
|
||||
)
|
||||
virtual_machine_count = tables.Column(
|
||||
verbose_name=_('VM count')
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='virtualization:virtualmachinetype_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
model = VirtualMachineType
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'slug', 'default_platform', 'default_vcpus', 'default_memory', 'virtual_machine_count',
|
||||
'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'default_platform', 'default_vcpus', 'default_memory', 'virtual_machine_count', 'description',
|
||||
)
|
||||
|
||||
#
|
||||
# Virtual machines
|
||||
#
|
||||
|
||||
|
||||
class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
)
|
||||
virtual_machine_type = tables.Column(
|
||||
verbose_name=_('Type'),
|
||||
linkify=True,
|
||||
)
|
||||
status = columns.ChoiceFieldColumn(
|
||||
verbose_name=_('Status'),
|
||||
)
|
||||
@@ -121,13 +85,12 @@ class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModel
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
model = VirtualMachine
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'virtual_machine_type', 'role', 'status', 'start_on_boot', 'site', 'cluster', 'device',
|
||||
'platform', 'primary_ip4', 'primary_ip6', 'primary_ip', 'vcpus', 'memory', 'disk', 'description', 'serial',
|
||||
'tenant_group', 'tenant', 'contacts', 'comments', 'tags', 'config_template', 'created', 'last_updated',
|
||||
'pk', 'id', 'name', 'status', 'start_on_boot', 'site', 'cluster', 'device', 'role', 'tenant',
|
||||
'tenant_group', 'vcpus', 'memory', 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'description',
|
||||
'comments', 'config_template', 'serial', 'contacts', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'virtual_machine_type', 'role', 'status', 'site', 'cluster', 'tenant',
|
||||
'vcpus', 'memory', 'disk', 'primary_ip',
|
||||
'pk', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip',
|
||||
)
|
||||
|
||||
def render_disk(self, value):
|
||||
|
||||
@@ -7,7 +7,7 @@ from rest_framework import status
|
||||
|
||||
from core.models import ObjectType
|
||||
from dcim.choices import InterfaceModeChoices
|
||||
from dcim.models import Platform, Site
|
||||
from dcim.models import Site
|
||||
from extras.choices import CustomFieldTypeChoices
|
||||
from extras.models import ConfigTemplate, CustomField
|
||||
from ipam.choices import VLANQinQRoleChoices
|
||||
@@ -167,170 +167,54 @@ class ClusterTest(APIViewTestCases.APIViewTestCase):
|
||||
]
|
||||
|
||||
|
||||
class VirtualMachineTypeTest(APIViewTestCases.APIViewTestCase):
|
||||
model = VirtualMachineType
|
||||
brief_fields = ['description', 'display', 'id', 'name', 'slug', 'url']
|
||||
user_permissions = ('dcim.view_platform', 'virtualization.view_virtualmachine')
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.platforms = (
|
||||
Platform.objects.create(name='Platform 1', slug='platform-1'),
|
||||
Platform.objects.create(name='Platform 2', slug='platform-2'),
|
||||
Platform.objects.create(name='Platform 3', slug='platform-3'),
|
||||
)
|
||||
|
||||
cls.virtual_machine_types = (
|
||||
VirtualMachineType.objects.create(
|
||||
name='Virtual Machine Type 1',
|
||||
slug='virtual-machine-type-1',
|
||||
default_platform=cls.platforms[0],
|
||||
default_vcpus=1,
|
||||
default_memory=1024,
|
||||
),
|
||||
VirtualMachineType.objects.create(
|
||||
name='Virtual Machine Type 2',
|
||||
slug='virtual-machine-type-2',
|
||||
default_platform=cls.platforms[1],
|
||||
default_vcpus=2,
|
||||
default_memory=2048,
|
||||
),
|
||||
VirtualMachineType.objects.create(
|
||||
name='Virtual Machine Type 3',
|
||||
slug='virtual-machine-type-3',
|
||||
default_platform=cls.platforms[2],
|
||||
default_vcpus=4,
|
||||
default_memory=4096,
|
||||
),
|
||||
)
|
||||
|
||||
cls.create_data = [
|
||||
{
|
||||
'name': 'Virtual Machine Type 4',
|
||||
'slug': 'virtual-machine-type-4',
|
||||
'default_platform': cls.platforms[0].pk,
|
||||
'default_vcpus': 1,
|
||||
'default_memory': 1024,
|
||||
},
|
||||
{
|
||||
'name': 'Virtual Machine Type 5',
|
||||
'slug': 'virtual-machine-type-5',
|
||||
'default_platform': cls.platforms[1].pk,
|
||||
'default_vcpus': 2,
|
||||
'default_memory': 2048,
|
||||
},
|
||||
{
|
||||
'name': 'Virtual Machine Type 6',
|
||||
'slug': 'virtual-machine-type-6',
|
||||
'default_platform': cls.platforms[2].pk,
|
||||
'default_vcpus': 4,
|
||||
'default_memory': 4096,
|
||||
},
|
||||
]
|
||||
|
||||
cls.bulk_update_data = {
|
||||
'default_platform': cls.platforms[2].pk,
|
||||
'default_vcpus': 8,
|
||||
'default_memory': 8192,
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
def test_filter_by_default_platform(self):
|
||||
self.add_permissions('virtualization.view_virtualmachinetype')
|
||||
|
||||
response = self.client.get(
|
||||
f'{self._get_list_url()}?default_platform_id={self.platforms[0].pk}',
|
||||
**self.header,
|
||||
)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
|
||||
response = self.client.get(
|
||||
f'{self._get_list_url()}?default_platform={self.platforms[0].slug}',
|
||||
**self.header,
|
||||
)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
|
||||
|
||||
class VirtualMachineTest(APIViewTestCases.APIViewTestCase):
|
||||
model = VirtualMachine
|
||||
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
||||
bulk_update_data = {
|
||||
'status': 'staged',
|
||||
}
|
||||
user_permissions = ('dcim.view_platform', 'virtualization.view_virtualmachinetype')
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
||||
clustergroup = ClusterGroup.objects.create(name='Cluster Group 1', slug='cluster-group-1')
|
||||
|
||||
cls.sites = (
|
||||
sites = (
|
||||
Site(name='Site 1', slug='site-1'),
|
||||
Site(name='Site 2', slug='site-2'),
|
||||
Site(name='Site 3', slug='site-3'),
|
||||
)
|
||||
Site.objects.bulk_create(cls.sites)
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
cls.clusters = (
|
||||
Cluster(name='Cluster 1', type=clustertype, scope=cls.sites[0], group=clustergroup),
|
||||
Cluster(name='Cluster 2', type=clustertype, scope=cls.sites[1], group=clustergroup),
|
||||
clusters = (
|
||||
Cluster(name='Cluster 1', type=clustertype, scope=sites[0], group=clustergroup),
|
||||
Cluster(name='Cluster 2', type=clustertype, scope=sites[1], group=clustergroup),
|
||||
Cluster(name='Cluster 3', type=clustertype),
|
||||
)
|
||||
for cluster in cls.clusters:
|
||||
for cluster in clusters:
|
||||
cluster.save()
|
||||
|
||||
cls.devices = (
|
||||
create_test_device('device1', site=cls.sites[0], cluster=cls.clusters[0]),
|
||||
create_test_device('device2', site=cls.sites[1], cluster=cls.clusters[1]),
|
||||
)
|
||||
|
||||
cls.platforms = (
|
||||
Platform.objects.create(name='Platform 1', slug='platform-1'),
|
||||
Platform.objects.create(name='Platform 2', slug='platform-2'),
|
||||
Platform.objects.create(name='Platform 3', slug='platform-3'),
|
||||
)
|
||||
|
||||
cls.vm_types = (
|
||||
VirtualMachineType.objects.create(
|
||||
name='Virtual Machine Type 1',
|
||||
slug='virtual-machine-type-1',
|
||||
default_platform=cls.platforms[0],
|
||||
default_vcpus=2,
|
||||
default_memory=4096,
|
||||
),
|
||||
VirtualMachineType.objects.create(
|
||||
name='Virtual Machine Type 2',
|
||||
slug='virtual-machine-type-2',
|
||||
default_platform=cls.platforms[1],
|
||||
default_vcpus=4,
|
||||
default_memory=8192,
|
||||
),
|
||||
)
|
||||
device1 = create_test_device('device1', site=sites[0], cluster=clusters[0])
|
||||
device2 = create_test_device('device2', site=sites[1], cluster=clusters[1])
|
||||
|
||||
virtual_machines = (
|
||||
VirtualMachine(
|
||||
name='Virtual Machine 1',
|
||||
virtual_machine_type=cls.vm_types[0],
|
||||
site=cls.sites[0],
|
||||
cluster=cls.clusters[0],
|
||||
device=cls.devices[0],
|
||||
platform=cls.platforms[0],
|
||||
vcpus=2,
|
||||
memory=4096,
|
||||
site=sites[0],
|
||||
cluster=clusters[0],
|
||||
device=device1,
|
||||
local_context_data={'A': 1},
|
||||
),
|
||||
VirtualMachine(
|
||||
name='Virtual Machine 2',
|
||||
site=cls.sites[0],
|
||||
cluster=cls.clusters[0],
|
||||
local_context_data={'B': 2},
|
||||
),
|
||||
site=sites[0],
|
||||
cluster=clusters[0],
|
||||
local_context_data={'B': 2
|
||||
}),
|
||||
VirtualMachine(
|
||||
name='Virtual Machine 3',
|
||||
site=cls.sites[0],
|
||||
cluster=cls.clusters[0],
|
||||
site=sites[0],
|
||||
cluster=clusters[0],
|
||||
local_context_data={'C': 3},
|
||||
start_on_boot=VirtualMachineStartOnBootChoices.STATUS_ON,
|
||||
),
|
||||
@@ -340,106 +224,26 @@ class VirtualMachineTest(APIViewTestCases.APIViewTestCase):
|
||||
cls.create_data = [
|
||||
{
|
||||
'name': 'Virtual Machine 4',
|
||||
'site': cls.sites[1].pk,
|
||||
'cluster': cls.clusters[1].pk,
|
||||
'device': cls.devices[1].pk,
|
||||
'virtual_machine_type': cls.vm_types[0].pk,
|
||||
'site': sites[1].pk,
|
||||
'cluster': clusters[1].pk,
|
||||
'device': device2.pk,
|
||||
},
|
||||
{
|
||||
'name': 'Virtual Machine 5',
|
||||
'site': cls.sites[1].pk,
|
||||
'cluster': cls.clusters[1].pk,
|
||||
'virtual_machine_type': cls.vm_types[1].pk,
|
||||
'site': sites[1].pk,
|
||||
'cluster': clusters[1].pk,
|
||||
},
|
||||
{
|
||||
'name': 'Virtual Machine 6',
|
||||
'site': cls.sites[1].pk,
|
||||
'site': sites[1].pk,
|
||||
},
|
||||
{
|
||||
'name': 'Virtual Machine 7',
|
||||
'cluster': cls.clusters[2].pk,
|
||||
'virtual_machine_type': cls.vm_types[0].pk,
|
||||
'cluster': clusters[2].pk,
|
||||
'start_on_boot': VirtualMachineStartOnBootChoices.STATUS_ON,
|
||||
},
|
||||
]
|
||||
|
||||
def test_filter_by_virtual_machine_type(self):
|
||||
self.add_permissions('virtualization.view_virtualmachine')
|
||||
|
||||
response = self.client.get(
|
||||
f'{self._get_list_url()}?virtual_machine_type_id={self.vm_types[0].pk}',
|
||||
**self.header,
|
||||
)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
|
||||
response = self.client.get(
|
||||
f'{self._get_list_url()}?virtual_machine_type={self.vm_types[0].slug}',
|
||||
**self.header,
|
||||
)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
|
||||
def test_virtual_machine_type_defaults_applied_on_create(self):
|
||||
data = {
|
||||
'name': 'Virtual Machine With Defaults',
|
||||
'site': self.sites[1].pk,
|
||||
'cluster': self.clusters[1].pk,
|
||||
'virtual_machine_type': self.vm_types[0].pk,
|
||||
'platform': None,
|
||||
'vcpus': None,
|
||||
'memory': None,
|
||||
}
|
||||
self.add_permissions('virtualization.add_virtualmachine')
|
||||
|
||||
response = self.client.post(self._get_list_url(), data, format='json', **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
||||
|
||||
vm = VirtualMachine.objects.get(pk=response.data['id'])
|
||||
self.assertEqual(vm.virtual_machine_type, self.vm_types[0])
|
||||
self.assertEqual(vm.platform, self.vm_types[0].default_platform)
|
||||
self.assertEqual(vm.vcpus, self.vm_types[0].default_vcpus)
|
||||
self.assertEqual(vm.memory, self.vm_types[0].default_memory)
|
||||
|
||||
def test_virtual_machine_type_defaults_do_not_override_explicit_values(self):
|
||||
data = {
|
||||
'name': 'Virtual Machine With Explicit Values',
|
||||
'site': self.sites[1].pk,
|
||||
'cluster': self.clusters[1].pk,
|
||||
'virtual_machine_type': self.vm_types[0].pk,
|
||||
'platform': self.platforms[2].pk,
|
||||
'vcpus': 6,
|
||||
'memory': 12288,
|
||||
}
|
||||
self.add_permissions('virtualization.add_virtualmachine')
|
||||
|
||||
response = self.client.post(self._get_list_url(), data, format='json', **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
||||
|
||||
vm = VirtualMachine.objects.get(pk=response.data['id'])
|
||||
self.assertEqual(vm.virtual_machine_type, self.vm_types[0])
|
||||
self.assertEqual(vm.platform, self.platforms[2])
|
||||
self.assertEqual(vm.vcpus, 6)
|
||||
self.assertEqual(vm.memory, 12288)
|
||||
|
||||
def test_setting_virtual_machine_type_on_existing_vm_does_not_backfill_defaults(self):
|
||||
vm = VirtualMachine.objects.get(name='Virtual Machine 2')
|
||||
self.add_permissions('virtualization.change_virtualmachine')
|
||||
|
||||
response = self.client.patch(
|
||||
self._get_detail_url(vm),
|
||||
{'virtual_machine_type': self.vm_types[1].pk},
|
||||
format='json',
|
||||
**self.header,
|
||||
)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
|
||||
vm.refresh_from_db()
|
||||
self.assertEqual(vm.virtual_machine_type, self.vm_types[1])
|
||||
self.assertIsNone(vm.platform)
|
||||
self.assertIsNone(vm.vcpus)
|
||||
self.assertIsNone(vm.memory)
|
||||
|
||||
def test_config_context_included_by_default_in_list_view(self):
|
||||
"""
|
||||
Check that config context data is included by default in the virtual machines list.
|
||||
|
||||
@@ -230,116 +230,6 @@ class ClusterTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class VirtualMachineTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = VirtualMachineType.objects.all()
|
||||
filterset = VirtualMachineTypeFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
platforms = (
|
||||
Platform(name='Platform 1', slug='platform-1'),
|
||||
Platform(name='Platform 2', slug='platform-2'),
|
||||
Platform(name='Platform 3', slug='platform-3'),
|
||||
)
|
||||
for platform in platforms:
|
||||
platform.save()
|
||||
|
||||
cluster_type = ClusterType.objects.create(
|
||||
name='Cluster Type 1',
|
||||
slug='cluster-type-1',
|
||||
)
|
||||
site = Site.objects.create(
|
||||
name='Site 1',
|
||||
slug='site-1',
|
||||
)
|
||||
cluster = Cluster.objects.create(
|
||||
name='Cluster 1',
|
||||
type=cluster_type,
|
||||
scope=site,
|
||||
)
|
||||
|
||||
cls.vm_types = (
|
||||
VirtualMachineType.objects.create(
|
||||
name='Virtual Machine Type 1',
|
||||
slug='virtual-machine-type-1',
|
||||
default_platform=platforms[0],
|
||||
default_vcpus=1,
|
||||
default_memory=1024,
|
||||
description='foobar1',
|
||||
),
|
||||
VirtualMachineType.objects.create(
|
||||
name='Virtual Machine Type 2',
|
||||
slug='virtual-machine-type-2',
|
||||
default_platform=platforms[1],
|
||||
default_vcpus=2,
|
||||
default_memory=2048,
|
||||
description='foobar2',
|
||||
),
|
||||
VirtualMachineType.objects.create(
|
||||
name='Virtual Machine Type 3',
|
||||
slug='virtual-machine-type-3',
|
||||
default_platform=platforms[2],
|
||||
default_vcpus=4,
|
||||
default_memory=4096,
|
||||
description='foobar3',
|
||||
),
|
||||
)
|
||||
|
||||
# Populate virtual_machine_count
|
||||
VirtualMachine.objects.create(
|
||||
name='vm-type-1a',
|
||||
cluster=cluster,
|
||||
virtual_machine_type=cls.vm_types[0],
|
||||
)
|
||||
VirtualMachine.objects.create(
|
||||
name='vm-type-1b',
|
||||
cluster=cluster,
|
||||
virtual_machine_type=cls.vm_types[0],
|
||||
tenant=Tenant.objects.create(name='Tenant 1', slug='tenant-1'),
|
||||
)
|
||||
VirtualMachine.objects.create(
|
||||
name='vm-type-2a',
|
||||
cluster=cluster,
|
||||
virtual_machine_type=cls.vm_types[1],
|
||||
)
|
||||
|
||||
def test_q(self):
|
||||
params = {'q': 'foobar1'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Virtual Machine Type 1', 'Virtual Machine Type 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_slug(self):
|
||||
params = {'slug': ['virtual-machine-type-1', 'virtual-machine-type-2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_description(self):
|
||||
params = {'description': ['foobar1', 'foobar2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_default_platform(self):
|
||||
platforms = Platform.objects.all()[:2]
|
||||
params = {'default_platform_id': [platforms[0].pk, platforms[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'default_platform': [platforms[0].slug, platforms[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_default_vcpus(self):
|
||||
params = {'default_vcpus': [1, 2]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_default_memory(self):
|
||||
params = {'default_memory': [1024, 2048]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_virtual_machine_count(self):
|
||||
params = {'virtual_machine_count': [1, 2]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = VirtualMachine.objects.all()
|
||||
filterset = VirtualMachineFilterSet
|
||||
@@ -400,30 +290,6 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
for platform in platforms:
|
||||
platform.save()
|
||||
|
||||
cls.vm_types = (
|
||||
VirtualMachineType.objects.create(
|
||||
name='Virtual Machine Type 1',
|
||||
slug='virtual-machine-type-1',
|
||||
default_platform=platforms[0],
|
||||
default_vcpus=1,
|
||||
default_memory=1024,
|
||||
),
|
||||
VirtualMachineType.objects.create(
|
||||
name='Virtual Machine Type 2',
|
||||
slug='virtual-machine-type-2',
|
||||
default_platform=platforms[1],
|
||||
default_vcpus=2,
|
||||
default_memory=2048,
|
||||
),
|
||||
VirtualMachineType.objects.create(
|
||||
name='Virtual Machine Type 3',
|
||||
slug='virtual-machine-type-3',
|
||||
default_platform=platforms[2],
|
||||
default_vcpus=4,
|
||||
default_memory=4096,
|
||||
),
|
||||
)
|
||||
|
||||
roles = (
|
||||
DeviceRole(name='Device Role 1', slug='device-role-1'),
|
||||
DeviceRole(name='Device Role 2', slug='device-role-2'),
|
||||
@@ -456,7 +322,6 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
vms = (
|
||||
VirtualMachine(
|
||||
name='Virtual Machine 1',
|
||||
virtual_machine_type=cls.vm_types[0],
|
||||
site=sites[0],
|
||||
cluster=clusters[0],
|
||||
device=devices[0],
|
||||
@@ -468,12 +333,11 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
memory=1,
|
||||
disk=1,
|
||||
description='foobar1',
|
||||
local_context_data={'foo': 123},
|
||||
serial='111-aaa',
|
||||
local_context_data={"foo": 123},
|
||||
serial='111-aaa'
|
||||
),
|
||||
VirtualMachine(
|
||||
name='Virtual Machine 2',
|
||||
virtual_machine_type=cls.vm_types[1],
|
||||
site=sites[1],
|
||||
cluster=clusters[1],
|
||||
device=devices[1],
|
||||
@@ -490,7 +354,6 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
),
|
||||
VirtualMachine(
|
||||
name='Virtual Machine 3',
|
||||
virtual_machine_type=cls.vm_types[2],
|
||||
site=sites[2],
|
||||
cluster=clusters[2],
|
||||
device=devices[2],
|
||||
@@ -636,13 +499,6 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
params = {'platform': [platforms[0].slug, platforms[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_virtual_machine_type(self):
|
||||
vm_types = VirtualMachineType.objects.all()[:2]
|
||||
params = {'virtual_machine_type_id': [vm_types[0].pk, vm_types[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'virtual_machine_type': [vm_types[0].slug, vm_types[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_mac_address(self):
|
||||
params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
@@ -1,175 +1,12 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase
|
||||
|
||||
from dcim.models import Platform, Site
|
||||
from dcim.models import Site
|
||||
from tenancy.models import Tenant
|
||||
from utilities.testing import create_test_device
|
||||
from virtualization.models import *
|
||||
|
||||
|
||||
class VirtualMachineTypeTestCase(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.platform = Platform.objects.create(
|
||||
name='Type Test Ubuntu 24.04',
|
||||
slug='type-test-ubuntu-24-04',
|
||||
)
|
||||
cls.virtual_machine_type = VirtualMachineType.objects.create(
|
||||
name='Small Linux',
|
||||
slug='small-linux',
|
||||
default_platform=cls.platform,
|
||||
default_vcpus=Decimal('2.00'),
|
||||
default_memory=4096,
|
||||
)
|
||||
|
||||
cls.cluster_type = ClusterType.objects.create(
|
||||
name='VM Type Count Cluster Type',
|
||||
slug='vm-type-count-cluster-type',
|
||||
)
|
||||
cls.site = Site.objects.create(
|
||||
name='VM Type Count Site',
|
||||
slug='vm-type-count-site',
|
||||
)
|
||||
cls.cluster = Cluster.objects.create(
|
||||
name='VM Type Count Cluster',
|
||||
type=cls.cluster_type,
|
||||
scope=cls.site,
|
||||
)
|
||||
|
||||
def test_virtual_machine_type_str_and_defaults(self):
|
||||
"""
|
||||
Verify that the string representation of a VirtualMachineType returns
|
||||
its name, and that all default fields (platform, vcpus, memory) are
|
||||
stored correctly after creation.
|
||||
"""
|
||||
self.assertEqual(str(self.virtual_machine_type), 'Small Linux')
|
||||
self.assertEqual(self.virtual_machine_type.default_platform, self.platform)
|
||||
self.assertEqual(self.virtual_machine_type.default_vcpus, Decimal('2.00'))
|
||||
self.assertEqual(self.virtual_machine_type.default_memory, 4096)
|
||||
|
||||
def test_virtual_machine_type_duplicate_name_case_insensitive(self):
|
||||
"""
|
||||
Creating a VirtualMachineType whose name differs from an existing one
|
||||
only by case should fail validation, enforced by the case-insensitive
|
||||
unique constraint on Lower('name').
|
||||
"""
|
||||
virtual_machine_type = VirtualMachineType(
|
||||
name='SMALL LINUX',
|
||||
slug='small-linux-2',
|
||||
)
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
virtual_machine_type.full_clean()
|
||||
|
||||
def test_virtual_machine_type_duplicate_slug(self):
|
||||
"""
|
||||
Creating a VirtualMachineType with a slug that already exists should
|
||||
fail validation, enforced by the unique constraint on the slug field.
|
||||
"""
|
||||
virtual_machine_type = VirtualMachineType(
|
||||
name='Medium Linux',
|
||||
slug='small-linux',
|
||||
)
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
virtual_machine_type.full_clean()
|
||||
|
||||
def test_virtual_machine_type_virtual_machine_count(self):
|
||||
"""
|
||||
The virtual_machine_count counter cache field should accurately track
|
||||
the number of VirtualMachines referencing this type through creation,
|
||||
additional insertions, reassignment, and deletion.
|
||||
"""
|
||||
# Starts at zero
|
||||
self.assertEqual(self.virtual_machine_type.virtual_machine_count, 0)
|
||||
|
||||
# Create the first VM
|
||||
vm1 = VirtualMachine.objects.create(
|
||||
name='vm-count-test-1',
|
||||
cluster=self.cluster,
|
||||
virtual_machine_type=self.virtual_machine_type,
|
||||
)
|
||||
self.virtual_machine_type.refresh_from_db()
|
||||
self.assertEqual(self.virtual_machine_type.virtual_machine_count, 1)
|
||||
|
||||
# Create the second VM
|
||||
vm2 = VirtualMachine.objects.create(
|
||||
name='vm-count-test-2',
|
||||
cluster=self.cluster,
|
||||
virtual_machine_type=self.virtual_machine_type,
|
||||
)
|
||||
self.virtual_machine_type.refresh_from_db()
|
||||
self.assertEqual(self.virtual_machine_type.virtual_machine_count, 2)
|
||||
|
||||
# Delete one VM — count should decrement
|
||||
vm1.delete()
|
||||
self.virtual_machine_type.refresh_from_db()
|
||||
self.assertEqual(self.virtual_machine_type.virtual_machine_count, 1)
|
||||
|
||||
# Reassign the remaining VM to no type — count should drop to zero
|
||||
vm2.virtual_machine_type = None
|
||||
vm2.save()
|
||||
self.virtual_machine_type.refresh_from_db()
|
||||
self.assertEqual(self.virtual_machine_type.virtual_machine_count, 0)
|
||||
|
||||
def test_virtual_machine_type_deletion_protected(self):
|
||||
"""
|
||||
Deleting a VirtualMachineType that is referenced by a VM should be prevented.
|
||||
"""
|
||||
VirtualMachine.objects.create(
|
||||
name='vm-protect-test',
|
||||
cluster=self.cluster,
|
||||
virtual_machine_type=self.virtual_machine_type,
|
||||
)
|
||||
|
||||
from django.db import models
|
||||
|
||||
with self.assertRaises(models.ProtectedError):
|
||||
self.virtual_machine_type.delete()
|
||||
|
||||
def test_virtual_machine_type_deletion_without_vms(self):
|
||||
"""
|
||||
A VirtualMachineType with no associated VMs can be deleted.
|
||||
"""
|
||||
vmt = VirtualMachineType.objects.create(
|
||||
name='Disposable Type',
|
||||
slug='disposable-type',
|
||||
)
|
||||
pk = vmt.pk
|
||||
vmt.delete()
|
||||
self.assertFalse(VirtualMachineType.objects.filter(pk=pk).exists())
|
||||
|
||||
def test_virtual_machine_type_invalid_default_vcpus(self):
|
||||
"""
|
||||
default_vcpus below the minimum should fail validation.
|
||||
"""
|
||||
vmt = VirtualMachineType(
|
||||
name='Zero vCPU Type',
|
||||
slug='zero-vcpu-type',
|
||||
default_vcpus=Decimal('0.00'),
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
vmt.full_clean()
|
||||
|
||||
def test_virtual_machine_type_minimal_fields(self):
|
||||
"""
|
||||
A VirtualMachineType with only name and slug should be valid.
|
||||
"""
|
||||
vmt = VirtualMachineType(
|
||||
name='Bare Minimum',
|
||||
slug='bare-minimum',
|
||||
)
|
||||
vmt.full_clean()
|
||||
vmt.save()
|
||||
|
||||
self.assertIsNone(vmt.default_platform)
|
||||
self.assertIsNone(vmt.default_vcpus)
|
||||
self.assertIsNone(vmt.default_memory)
|
||||
|
||||
|
||||
class VirtualMachineTestCase(TestCase):
|
||||
|
||||
@classmethod
|
||||
@@ -177,12 +14,6 @@ class VirtualMachineTestCase(TestCase):
|
||||
# Create the cluster type
|
||||
cls.cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
||||
|
||||
# Create platforms
|
||||
cls.platforms = (
|
||||
Platform.objects.create(name='VM Default Ubuntu 24.04', slug='vm-default-ubuntu-24-04'),
|
||||
Platform.objects.create(name='VM Default Debian 12', slug='vm-default-debian-12'),
|
||||
)
|
||||
|
||||
# Create sites
|
||||
cls.sites = (
|
||||
Site.objects.create(name='Site 1', slug='site-1'),
|
||||
@@ -228,24 +59,6 @@ class VirtualMachineTestCase(TestCase):
|
||||
Tenant.objects.create(name='Tenant 2', slug='tenant-2'),
|
||||
)
|
||||
|
||||
# Create virtual machine types
|
||||
cls.vm_types = (
|
||||
VirtualMachineType.objects.create(
|
||||
name='General Purpose Small',
|
||||
slug='general-purpose-small',
|
||||
default_platform=cls.platforms[0],
|
||||
default_vcpus=Decimal('2.00'),
|
||||
default_memory=4096,
|
||||
),
|
||||
VirtualMachineType.objects.create(
|
||||
name='General Purpose Large',
|
||||
slug='general-purpose-large',
|
||||
default_platform=cls.platforms[1],
|
||||
default_vcpus=Decimal('8.00'),
|
||||
default_memory=16384,
|
||||
),
|
||||
)
|
||||
|
||||
def test_vm_duplicate_name_per_cluster(self):
|
||||
"""
|
||||
Test that creating two Virtual Machines with the same name in
|
||||
@@ -344,181 +157,6 @@ class VirtualMachineTestCase(TestCase):
|
||||
with self.assertRaises(ValidationError):
|
||||
vm.full_clean()
|
||||
|
||||
#
|
||||
# Virtual Machine Type tests
|
||||
#
|
||||
|
||||
def test_vm_type_defaults_applied_on_create(self):
|
||||
"""
|
||||
When a new VirtualMachine is created with a VirtualMachineType and no
|
||||
explicit platform, vcpus, or memory, the type's defaults should be
|
||||
automatically applied via apply_type_defaults().
|
||||
"""
|
||||
vm = VirtualMachine(
|
||||
name='vm-type-defaults',
|
||||
cluster=self.cluster_with_site,
|
||||
virtual_machine_type=self.vm_types[0],
|
||||
)
|
||||
vm.full_clean()
|
||||
vm.save()
|
||||
vm.refresh_from_db()
|
||||
|
||||
self.assertEqual(vm.platform, self.platforms[0])
|
||||
self.assertEqual(vm.vcpus, Decimal('2.00'))
|
||||
self.assertEqual(vm.memory, 4096)
|
||||
|
||||
def test_vm_type_defaults_do_not_override_explicit_values(self):
|
||||
"""
|
||||
When a new VirtualMachine specifies explicit values for a platform,
|
||||
vcpus, and memory, those values must be preserved even if the
|
||||
assigned VirtualMachineType defines different defaults.
|
||||
"""
|
||||
vm = VirtualMachine(
|
||||
name='vm-type-explicit',
|
||||
cluster=self.cluster_with_site,
|
||||
virtual_machine_type=self.vm_types[0],
|
||||
platform=self.platforms[1],
|
||||
vcpus=Decimal('4.00'),
|
||||
memory=8192,
|
||||
)
|
||||
vm.full_clean()
|
||||
vm.save()
|
||||
vm.refresh_from_db()
|
||||
|
||||
self.assertEqual(vm.platform, self.platforms[1])
|
||||
self.assertEqual(vm.vcpus, Decimal('4.00'))
|
||||
self.assertEqual(vm.memory, 8192)
|
||||
|
||||
def test_vm_type_added_to_existing_vm_does_not_backfill_defaults(self):
|
||||
"""
|
||||
Assigning a VirtualMachineType to an already-saved VirtualMachine
|
||||
(i.e. an update, not a creation) must not retroactively populate
|
||||
the VM's fields with the type's defaults, since apply_type_defaults()
|
||||
only runs on initial creation.
|
||||
"""
|
||||
vm = VirtualMachine(
|
||||
name='vm-type-added-later',
|
||||
cluster=self.cluster_with_site,
|
||||
)
|
||||
vm.full_clean()
|
||||
vm.save()
|
||||
|
||||
vm.virtual_machine_type = self.vm_types[0]
|
||||
vm.full_clean()
|
||||
vm.save()
|
||||
vm.refresh_from_db()
|
||||
|
||||
self.assertEqual(vm.virtual_machine_type, self.vm_types[0])
|
||||
self.assertIsNone(vm.platform)
|
||||
self.assertIsNone(vm.vcpus)
|
||||
self.assertIsNone(vm.memory)
|
||||
|
||||
def test_vm_type_change_does_not_overwrite_existing_values(self):
|
||||
"""
|
||||
Changing the VirtualMachineType on an existing VirtualMachine must
|
||||
not overwrite field values that were previously set — either
|
||||
explicitly or via earlier type defaults.
|
||||
"""
|
||||
vm = VirtualMachine(
|
||||
name='vm-type-change',
|
||||
cluster=self.cluster_with_site,
|
||||
virtual_machine_type=self.vm_types[0],
|
||||
)
|
||||
vm.full_clean()
|
||||
vm.save()
|
||||
vm.refresh_from_db()
|
||||
|
||||
self.assertEqual(vm.platform, self.platforms[0])
|
||||
self.assertEqual(vm.vcpus, Decimal('2.00'))
|
||||
self.assertEqual(vm.memory, 4096)
|
||||
|
||||
vm.platform = self.platforms[1]
|
||||
vm.vcpus = Decimal('6.00')
|
||||
vm.memory = 12288
|
||||
vm.virtual_machine_type = self.vm_types[1]
|
||||
vm.full_clean()
|
||||
vm.save()
|
||||
vm.refresh_from_db()
|
||||
|
||||
self.assertEqual(vm.platform, self.platforms[1])
|
||||
self.assertEqual(vm.vcpus, Decimal('6.00'))
|
||||
self.assertEqual(vm.memory, 12288)
|
||||
self.assertEqual(vm.virtual_machine_type, self.vm_types[1])
|
||||
|
||||
def test_vm_type_partial_defaults(self):
|
||||
"""
|
||||
A VirtualMachineType with only some defaults set should only populate
|
||||
those fields on a new VM, leaving the rest as None.
|
||||
"""
|
||||
partial_type = VirtualMachineType.objects.create(
|
||||
name='Partial Defaults',
|
||||
slug='partial-defaults',
|
||||
default_vcpus=Decimal('4.00'),
|
||||
# default_platform and default_memory intentionally left None
|
||||
)
|
||||
|
||||
vm = VirtualMachine(
|
||||
name='vm-partial-defaults',
|
||||
cluster=self.cluster_with_site,
|
||||
virtual_machine_type=partial_type,
|
||||
)
|
||||
vm.full_clean()
|
||||
vm.save()
|
||||
vm.refresh_from_db()
|
||||
|
||||
self.assertIsNone(vm.platform)
|
||||
self.assertEqual(vm.vcpus, Decimal('4.00'))
|
||||
self.assertIsNone(vm.memory)
|
||||
|
||||
def test_vm_type_no_defaults(self):
|
||||
"""
|
||||
A VirtualMachineType with all default fields as None should not
|
||||
alter any VM fields on creation.
|
||||
"""
|
||||
empty_type = VirtualMachineType.objects.create(
|
||||
name='Empty Type',
|
||||
slug='empty-type',
|
||||
)
|
||||
|
||||
vm = VirtualMachine(
|
||||
name='vm-empty-type',
|
||||
cluster=self.cluster_with_site,
|
||||
virtual_machine_type=empty_type,
|
||||
)
|
||||
vm.full_clean()
|
||||
vm.save()
|
||||
vm.refresh_from_db()
|
||||
|
||||
self.assertEqual(vm.virtual_machine_type, empty_type)
|
||||
self.assertIsNone(vm.platform)
|
||||
self.assertIsNone(vm.vcpus)
|
||||
self.assertIsNone(vm.memory)
|
||||
|
||||
def test_vm_created_without_type(self):
|
||||
"""
|
||||
A VM created without a VirtualMachineType should not raise any errors
|
||||
in apply_type_defaults() and should leave all fields as None.
|
||||
"""
|
||||
vm = VirtualMachine(
|
||||
name='vm-no-type',
|
||||
cluster=self.cluster_with_site,
|
||||
)
|
||||
vm.full_clean()
|
||||
vm.save()
|
||||
vm.refresh_from_db()
|
||||
|
||||
self.assertIsNone(vm.virtual_machine_type)
|
||||
self.assertIsNone(vm.platform)
|
||||
self.assertIsNone(vm.vcpus)
|
||||
self.assertIsNone(vm.memory)
|
||||
|
||||
def test_vm_type_is_included_in_clone_fields(self):
|
||||
"""
|
||||
Verify that virtual_machine_type is part of clone_fields so it
|
||||
carries over when cloning a VM.
|
||||
"""
|
||||
self.assertIn('virtual_machine_type', VirtualMachine.clone_fields)
|
||||
|
||||
#
|
||||
# Device assignment tests
|
||||
#
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
@@ -204,81 +202,6 @@ class ClusterTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
|
||||
class VirtualMachineTypeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = VirtualMachineType
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
cls.platforms = (
|
||||
Platform(name='Platform 1', slug='platform-1'),
|
||||
Platform(name='Platform 2', slug='platform-2'),
|
||||
Platform(name='Platform 3', slug='platform-3'),
|
||||
)
|
||||
for platform in cls.platforms:
|
||||
platform.save()
|
||||
|
||||
cls.virtual_machine_types = (
|
||||
VirtualMachineType(
|
||||
name='Virtual Machine Type 1',
|
||||
slug='virtual-machine-type-1',
|
||||
default_platform=cls.platforms[0],
|
||||
default_vcpus=Decimal('1.00'),
|
||||
default_memory=1024,
|
||||
),
|
||||
VirtualMachineType(
|
||||
name='Virtual Machine Type 2',
|
||||
slug='virtual-machine-type-2',
|
||||
default_platform=cls.platforms[1],
|
||||
default_vcpus=Decimal('2.00'),
|
||||
default_memory=2048,
|
||||
),
|
||||
VirtualMachineType(
|
||||
name='Virtual Machine Type 3',
|
||||
slug='virtual-machine-type-3',
|
||||
default_platform=cls.platforms[2],
|
||||
default_vcpus=Decimal('4.00'),
|
||||
default_memory=4096,
|
||||
),
|
||||
)
|
||||
for virtual_machine_type in cls.virtual_machine_types:
|
||||
virtual_machine_type.save()
|
||||
|
||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
|
||||
cls.form_data = {
|
||||
'name': 'Virtual Machine Type X',
|
||||
'slug': 'virtual-machine-type-x',
|
||||
'default_platform': cls.platforms[1].pk,
|
||||
'default_vcpus': 8,
|
||||
'default_memory': 8192,
|
||||
'description': 'A new virtual machine type',
|
||||
'comments': 'Some comments',
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
'name,slug,default_platform,default_vcpus,default_memory,description',
|
||||
'Virtual Machine Type 4,virtual-machine-type-4,Platform 1,1.00,1024,Fourth virtual machine type',
|
||||
'Virtual Machine Type 5,virtual-machine-type-5,Platform 2,2.00,2048,Fifth virtual machine type',
|
||||
'Virtual Machine Type 6,virtual-machine-type-6,Platform 3,4.00,4096,Sixth virtual machine type',
|
||||
)
|
||||
|
||||
cls.csv_update_data = (
|
||||
'id,name,description',
|
||||
f'{cls.virtual_machine_types[0].pk},Virtual Machine Type 7,New description 7',
|
||||
f'{cls.virtual_machine_types[1].pk},Virtual Machine Type 8,New description 8',
|
||||
f'{cls.virtual_machine_types[2].pk},Virtual Machine Type 9,New description 9',
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'default_platform': cls.platforms[2].pk,
|
||||
'default_vcpus': 16,
|
||||
'default_memory': 16384,
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
|
||||
class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = VirtualMachine
|
||||
|
||||
@@ -292,79 +215,57 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
for role in roles:
|
||||
role.save()
|
||||
|
||||
cls.platforms = (
|
||||
platforms = (
|
||||
Platform(name='Platform 1', slug='platform-1'),
|
||||
Platform(name='Platform 2', slug='platform-2'),
|
||||
)
|
||||
for platform in cls.platforms:
|
||||
for platform in platforms:
|
||||
platform.save()
|
||||
|
||||
cls.sites = (
|
||||
sites = (
|
||||
Site(name='Site 1', slug='site-1'),
|
||||
Site(name='Site 2', slug='site-2'),
|
||||
)
|
||||
Site.objects.bulk_create(cls.sites)
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
||||
|
||||
cls.clusters = (
|
||||
Cluster(name='Cluster 1', type=clustertype, scope=cls.sites[0]),
|
||||
Cluster(name='Cluster 2', type=clustertype, scope=cls.sites[1]),
|
||||
clusters = (
|
||||
Cluster(name='Cluster 1', type=clustertype, scope=sites[0]),
|
||||
Cluster(name='Cluster 2', type=clustertype, scope=sites[1]),
|
||||
)
|
||||
for cluster in cls.clusters:
|
||||
for cluster in clusters:
|
||||
cluster.save()
|
||||
|
||||
cls.devices = (
|
||||
create_test_device('device1', site=cls.sites[0], cluster=cls.clusters[0]),
|
||||
create_test_device('device2', site=cls.sites[1], cluster=cls.clusters[1]),
|
||||
devices = (
|
||||
create_test_device('device1', site=sites[0], cluster=clusters[0]),
|
||||
create_test_device('device2', site=sites[1], cluster=clusters[1]),
|
||||
)
|
||||
|
||||
cls.vm_types = (
|
||||
VirtualMachineType(
|
||||
name='Virtual Machine Type 1',
|
||||
slug='virtual-machine-type-1',
|
||||
default_platform=cls.platforms[0],
|
||||
default_vcpus=Decimal('2.00'),
|
||||
default_memory=4096,
|
||||
),
|
||||
VirtualMachineType(
|
||||
name='Virtual Machine Type 2',
|
||||
slug='virtual-machine-type-2',
|
||||
default_platform=cls.platforms[1],
|
||||
default_vcpus=Decimal('4.00'),
|
||||
default_memory=8192,
|
||||
),
|
||||
)
|
||||
for vm_type in cls.vm_types:
|
||||
vm_type.save()
|
||||
|
||||
virtual_machines = (
|
||||
VirtualMachine(
|
||||
name='Virtual Machine 1',
|
||||
virtual_machine_type=cls.vm_types[0],
|
||||
site=cls.sites[0],
|
||||
cluster=cls.clusters[0],
|
||||
device=cls.devices[0],
|
||||
site=sites[0],
|
||||
cluster=clusters[0],
|
||||
device=devices[0],
|
||||
role=roles[0],
|
||||
platform=cls.platforms[0],
|
||||
platform=platforms[0],
|
||||
),
|
||||
VirtualMachine(
|
||||
name='Virtual Machine 2',
|
||||
virtual_machine_type=cls.vm_types[0],
|
||||
site=cls.sites[0],
|
||||
cluster=cls.clusters[0],
|
||||
device=cls.devices[0],
|
||||
site=sites[0],
|
||||
cluster=clusters[0],
|
||||
device=devices[0],
|
||||
role=roles[0],
|
||||
platform=cls.platforms[0],
|
||||
platform=platforms[0],
|
||||
),
|
||||
VirtualMachine(
|
||||
name='Virtual Machine 3',
|
||||
virtual_machine_type=cls.vm_types[1],
|
||||
site=cls.sites[0],
|
||||
cluster=cls.clusters[0],
|
||||
device=cls.devices[0],
|
||||
site=sites[0],
|
||||
cluster=clusters[0],
|
||||
device=devices[0],
|
||||
role=roles[0],
|
||||
platform=cls.platforms[0],
|
||||
platform=platforms[0],
|
||||
),
|
||||
)
|
||||
VirtualMachine.objects.bulk_create(virtual_machines)
|
||||
@@ -372,12 +273,11 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
|
||||
cls.form_data = {
|
||||
'virtual_machine_type': cls.vm_types[1].pk,
|
||||
'cluster': cls.clusters[1].pk,
|
||||
'device': cls.devices[1].pk,
|
||||
'site': cls.sites[1].pk,
|
||||
'cluster': clusters[1].pk,
|
||||
'device': devices[1].pk,
|
||||
'site': sites[1].pk,
|
||||
'tenant': None,
|
||||
'platform': cls.platforms[1].pk,
|
||||
'platform': platforms[1].pk,
|
||||
'name': 'Virtual Machine X',
|
||||
'status': VirtualMachineStatusChoices.STATUS_STAGED,
|
||||
'start_on_boot': VirtualMachineStartOnBootChoices.STATUS_ON,
|
||||
@@ -394,61 +294,34 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
'name,status,site,cluster,device,virtual_machine_type',
|
||||
'Virtual Machine 4,active,Site 1,Cluster 1,device1,Virtual Machine Type 1',
|
||||
'Virtual Machine 5,active,Site 1,Cluster 1,device1,Virtual Machine Type 2',
|
||||
'Virtual Machine 6,active,Site 1,Cluster 1,,Virtual Machine Type 1',
|
||||
"name,status,site,cluster,device",
|
||||
"Virtual Machine 4,active,Site 1,Cluster 1,device1",
|
||||
"Virtual Machine 5,active,Site 1,Cluster 1,device1",
|
||||
"Virtual Machine 6,active,Site 1,Cluster 1,",
|
||||
)
|
||||
|
||||
cls.csv_update_data = (
|
||||
'id,name,comments',
|
||||
f'{virtual_machines[0].pk},Virtual Machine 7,New comments 7',
|
||||
f'{virtual_machines[1].pk},Virtual Machine 8,New comments 8',
|
||||
f'{virtual_machines[2].pk},Virtual Machine 9,New comments 9',
|
||||
"id,name,comments",
|
||||
f"{virtual_machines[0].pk},Virtual Machine 7,New comments 7",
|
||||
f"{virtual_machines[1].pk},Virtual Machine 8,New comments 8",
|
||||
f"{virtual_machines[2].pk},Virtual Machine 9,New comments 9",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'virtual_machine_type': cls.vm_types[1].pk,
|
||||
'site': cls.sites[1].pk,
|
||||
'cluster': cls.clusters[1].pk,
|
||||
'device': cls.devices[1].pk,
|
||||
'site': sites[1].pk,
|
||||
'cluster': clusters[1].pk,
|
||||
'device': devices[1].pk,
|
||||
'tenant': None,
|
||||
'platform': cls.platforms[1].pk,
|
||||
'platform': platforms[1].pk,
|
||||
'status': VirtualMachineStatusChoices.STATUS_STAGED,
|
||||
'role': roles[1].pk,
|
||||
'vcpus': Decimal('8.00'),
|
||||
'vcpus': 8,
|
||||
'memory': 65535,
|
||||
'disk': 8000,
|
||||
'comments': 'New comments',
|
||||
'start_on_boot': VirtualMachineStartOnBootChoices.STATUS_OFF,
|
||||
}
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
|
||||
def test_create_virtualmachine_with_type_defaults(self):
|
||||
self.add_permissions('virtualization.add_virtualmachine')
|
||||
|
||||
response = self.client.post(
|
||||
self._get_url('add'),
|
||||
data={
|
||||
'name': 'Virtual Machine Defaults',
|
||||
'virtual_machine_type': self.vm_types[0].pk,
|
||||
'status': VirtualMachineStatusChoices.STATUS_ACTIVE,
|
||||
'start_on_boot': VirtualMachineStartOnBootChoices.STATUS_OFF,
|
||||
'site': self.sites[0].pk,
|
||||
'cluster': self.clusters[0].pk,
|
||||
'platform': '',
|
||||
'vcpus': '',
|
||||
'memory': '',
|
||||
},
|
||||
)
|
||||
self.assertHttpStatus(response, 302)
|
||||
|
||||
vm = VirtualMachine.objects.get(name='Virtual Machine Defaults')
|
||||
self.assertEqual(vm.virtual_machine_type, self.vm_types[0])
|
||||
self.assertEqual(vm.platform, self.platforms[0])
|
||||
self.assertEqual(vm.vcpus, self.vm_types[0].default_vcpus)
|
||||
self.assertEqual(vm.memory, self.vm_types[0].default_memory)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_virtualmachine_interfaces(self):
|
||||
virtualmachine = VirtualMachine.objects.first()
|
||||
|
||||
@@ -2,10 +2,6 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from netbox.ui import attrs, panels
|
||||
|
||||
#
|
||||
# Cluster
|
||||
#
|
||||
|
||||
|
||||
class ClusterPanel(panels.ObjectAttributesPanel):
|
||||
name = attrs.TextAttr('name')
|
||||
@@ -17,27 +13,8 @@ class ClusterPanel(panels.ObjectAttributesPanel):
|
||||
scope = attrs.GenericForeignKeyAttr('scope', linkify=True)
|
||||
|
||||
|
||||
#
|
||||
# Virtual machine types
|
||||
#
|
||||
|
||||
|
||||
class VirtualMachineTypePanel(panels.ObjectAttributesPanel):
|
||||
name = attrs.TextAttr('name')
|
||||
default_platform = attrs.RelatedObjectAttr('default_platform', linkify=True)
|
||||
default_vcpus = attrs.TextAttr('default_vcpus', label=_('Default vCPUs'))
|
||||
default_memory = attrs.TextAttr('default_memory', format_string=_('{} MB'), label=_('Default memory'))
|
||||
description = attrs.TextAttr('description')
|
||||
|
||||
|
||||
#
|
||||
# Virtual machines
|
||||
#
|
||||
|
||||
|
||||
class VirtualMachinePanel(panels.ObjectAttributesPanel):
|
||||
name = attrs.TextAttr('name')
|
||||
virtual_machine_type = attrs.RelatedObjectAttr('virtual_machine_type', linkify=True, label=_('Type'))
|
||||
status = attrs.ChoiceAttr('status')
|
||||
start_on_boot = attrs.ChoiceAttr('start_on_boot')
|
||||
role = attrs.RelatedObjectAttr('role', linkify=True)
|
||||
@@ -67,11 +44,6 @@ class VirtualMachinePlacementPanel(panels.ObjectAttributesPanel):
|
||||
device = attrs.RelatedObjectAttr('device', linkify=True)
|
||||
|
||||
|
||||
#
|
||||
# Virtual disks
|
||||
#
|
||||
|
||||
|
||||
class VirtualDiskPanel(panels.ObjectAttributesPanel):
|
||||
virtual_machine = attrs.RelatedObjectAttr('virtual_machine', linkify=True, label=_('Virtual Machine'))
|
||||
name = attrs.TextAttr('name')
|
||||
@@ -79,11 +51,6 @@ class VirtualDiskPanel(panels.ObjectAttributesPanel):
|
||||
description = attrs.TextAttr('description')
|
||||
|
||||
|
||||
#
|
||||
# VM interfaces
|
||||
#
|
||||
|
||||
|
||||
class VMInterfacePanel(panels.ObjectAttributesPanel):
|
||||
virtual_machine = attrs.RelatedObjectAttr('virtual_machine', linkify=True, label=_('Virtual Machine'))
|
||||
name = attrs.TextAttr('name')
|
||||
|
||||
@@ -16,9 +16,6 @@ urlpatterns = [
|
||||
path('clusters/', include(get_model_urls('virtualization', 'cluster', detail=False))),
|
||||
path('clusters/<int:pk>/', include(get_model_urls('virtualization', 'cluster'))),
|
||||
|
||||
path('virtual-machine-types/', include(get_model_urls('virtualization', 'virtualmachinetype', detail=False))),
|
||||
path('virtual-machine-types/<int:pk>/', include(get_model_urls('virtualization', 'virtualmachinetype'))),
|
||||
|
||||
path('virtual-machines/', include(get_model_urls('virtualization', 'virtualmachine', detail=False))),
|
||||
path('virtual-machines/<int:pk>/', include(get_model_urls('virtualization', 'virtualmachine'))),
|
||||
|
||||
|
||||
@@ -387,80 +387,6 @@ class ClusterAddDevicesView(generic.ObjectEditView):
|
||||
})
|
||||
|
||||
|
||||
#
|
||||
# Virtual machine types
|
||||
#
|
||||
|
||||
|
||||
@register_model_view(VirtualMachineType, 'list', path='', detail=False)
|
||||
class VirtualMachineTypeListView(generic.ObjectListView):
|
||||
queryset = VirtualMachineType.objects.all()
|
||||
filterset = filtersets.VirtualMachineTypeFilterSet
|
||||
filterset_form = forms.VirtualMachineTypeFilterForm
|
||||
table = tables.VirtualMachineTypeTable
|
||||
|
||||
|
||||
@register_model_view(VirtualMachineType)
|
||||
class VirtualMachineTypeView(GetRelatedModelsMixin, generic.ObjectView):
|
||||
queryset = VirtualMachineType.objects.all()
|
||||
layout = layout.SimpleLayout(
|
||||
left_panels=[
|
||||
panels.VirtualMachineTypePanel(),
|
||||
TagsPanel(),
|
||||
CommentsPanel(),
|
||||
],
|
||||
right_panels=[
|
||||
RelatedObjectsPanel(),
|
||||
CustomFieldsPanel(),
|
||||
ImageAttachmentsPanel(),
|
||||
],
|
||||
)
|
||||
|
||||
def get_extra_context(self, request, instance):
|
||||
return {
|
||||
'related_models': self.get_related_models(request, instance),
|
||||
}
|
||||
|
||||
|
||||
@register_model_view(VirtualMachineType, 'add', detail=False)
|
||||
@register_model_view(VirtualMachineType, 'edit')
|
||||
class VirtualMachineTypeEditView(generic.ObjectEditView):
|
||||
queryset = VirtualMachineType.objects.all()
|
||||
form = forms.VirtualMachineTypeForm
|
||||
|
||||
|
||||
@register_model_view(VirtualMachineType, 'delete')
|
||||
class VirtualMachineTypeDeleteView(generic.ObjectDeleteView):
|
||||
queryset = VirtualMachineType.objects.all()
|
||||
|
||||
|
||||
@register_model_view(VirtualMachineType, 'bulk_import', path='import', detail=False)
|
||||
class VirtualMachineTypeBulkImportView(generic.BulkImportView):
|
||||
queryset = VirtualMachineType.objects.all()
|
||||
model_form = forms.VirtualMachineTypeImportForm
|
||||
|
||||
|
||||
@register_model_view(VirtualMachineType, 'bulk_edit', path='edit', detail=False)
|
||||
class VirtualMachineTypeBulkEditView(generic.BulkEditView):
|
||||
queryset = VirtualMachineType.objects.all()
|
||||
filterset = filtersets.VirtualMachineTypeFilterSet
|
||||
table = tables.VirtualMachineTypeTable
|
||||
form = forms.VirtualMachineTypeBulkEditForm
|
||||
|
||||
|
||||
@register_model_view(VirtualMachineType, 'bulk_rename', path='rename', detail=False)
|
||||
class VirtualMachineTypeBulkRenameView(generic.BulkRenameView):
|
||||
queryset = VirtualMachineType.objects.all()
|
||||
filterset = filtersets.VirtualMachineTypeFilterSet
|
||||
|
||||
|
||||
@register_model_view(VirtualMachineType, 'bulk_delete', path='delete', detail=False)
|
||||
class VirtualMachineTypeBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = VirtualMachineType.objects.all()
|
||||
filterset = filtersets.VirtualMachineTypeFilterSet
|
||||
table = tables.VirtualMachineTypeTable
|
||||
|
||||
|
||||
#
|
||||
# Virtual machines
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user