Closes #20304: Object owners (#20634)

This commit is contained in:
Jeremy Stretch
2025-10-24 16:08:01 -04:00
committed by GitHub
parent 52d4498caf
commit be74436884
196 changed files with 15831 additions and 2715 deletions

View File

@@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer
from netbox.api.serializers import OrganizationalModelSerializer, PrimaryModelSerializer
from tenancy.api.serializers_.tenants import TenantSerializer
from virtualization.choices import *
from virtualization.models import Cluster, ClusterGroup, ClusterType
@@ -16,7 +16,7 @@ __all__ = (
)
class ClusterTypeSerializer(NetBoxModelSerializer):
class ClusterTypeSerializer(OrganizationalModelSerializer):
# Related object counts
cluster_count = RelatedObjectCountField('clusters')
@@ -24,13 +24,13 @@ class ClusterTypeSerializer(NetBoxModelSerializer):
class Meta:
model = ClusterType
fields = [
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'tags', 'custom_fields',
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'owner', 'tags', 'custom_fields',
'created', 'last_updated', 'cluster_count',
]
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'cluster_count')
class ClusterGroupSerializer(NetBoxModelSerializer):
class ClusterGroupSerializer(OrganizationalModelSerializer):
# Related object counts
cluster_count = RelatedObjectCountField('clusters')
@@ -38,13 +38,13 @@ class ClusterGroupSerializer(NetBoxModelSerializer):
class Meta:
model = ClusterGroup
fields = [
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'tags', 'custom_fields',
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'owner', 'tags', 'custom_fields',
'created', 'last_updated', 'cluster_count',
]
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'cluster_count')
class ClusterSerializer(NetBoxModelSerializer):
class ClusterSerializer(PrimaryModelSerializer):
type = ClusterTypeSerializer(nested=True)
group = ClusterGroupSerializer(nested=True, required=False, allow_null=True, default=None)
status = ChoiceField(choices=ClusterStatusChoices, required=False)
@@ -76,7 +76,7 @@ class ClusterSerializer(NetBoxModelSerializer):
model = Cluster
fields = [
'id', 'url', 'display_url', 'display', 'name', 'type', 'group', 'status', 'tenant', 'scope_type',
'scope_id', 'scope', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
'scope_id', 'scope', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
'device_count', 'virtualmachine_count', 'allocated_vcpus', 'allocated_memory', 'allocated_disk'
]
brief_fields = ('id', 'url', 'display', 'name', 'description', 'virtualmachine_count')

View File

@@ -13,7 +13,7 @@ from ipam.api.serializers_.vlans import VLANSerializer, VLANTranslationPolicySer
from ipam.api.serializers_.vrfs import VRFSerializer
from ipam.models import VLAN
from netbox.api.fields import ChoiceField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer
from netbox.api.serializers import NetBoxModelSerializer, PrimaryModelSerializer
from tenancy.api.serializers_.tenants import TenantSerializer
from virtualization.choices import *
from virtualization.models import VirtualDisk, VirtualMachine, VMInterface
@@ -29,7 +29,7 @@ __all__ = (
)
class VirtualMachineSerializer(NetBoxModelSerializer):
class VirtualMachineSerializer(PrimaryModelSerializer):
status = ChoiceField(choices=VirtualMachineStatusChoices, required=False)
site = SiteSerializer(nested=True, required=False, allow_null=True, default=None)
cluster = ClusterSerializer(nested=True, required=False, allow_null=True, default=None)
@@ -51,8 +51,8 @@ class VirtualMachineSerializer(NetBoxModelSerializer):
fields = [
'id', 'url', 'display_url', 'display', 'name', 'status', 'site', 'cluster', 'device', 'serial', 'role',
'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'description',
'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
'interface_count', 'virtual_disk_count',
'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')

View File

@@ -9,8 +9,10 @@ from dcim.models import MACAddress
from extras.filtersets import LocalConfigContextFilterSet
from extras.models import ConfigTemplate
from ipam.filtersets import PrimaryIPFilterSet
from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
from users.filterset_mixins import OwnerFilterMixin
from utilities.filters import MultiValueCharFilter, MultiValueMACAddressFilter, TreeNodeMultipleChoiceFilter
from .choices import *
from .models import *
@@ -39,7 +41,7 @@ class ClusterGroupFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet)
fields = ('id', 'name', 'slug', 'description')
class ClusterFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ScopedFilterSet, ContactModelFilterSet):
class ClusterFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ScopedFilterSet, ContactModelFilterSet):
group_id = django_filters.ModelMultipleChoiceFilter(
queryset=ClusterGroup.objects.all(),
label=_('Parent group (ID)'),
@@ -80,7 +82,7 @@ class ClusterFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ScopedFilterSet,
class VirtualMachineFilterSet(
NetBoxModelFilterSet,
PrimaryModelFilterSet,
TenancyFilterSet,
ContactModelFilterSet,
LocalConfigContextFilterSet,
@@ -235,7 +237,7 @@ class VirtualMachineFilterSet(
return queryset.exclude(params)
class VMInterfaceFilterSet(NetBoxModelFilterSet, CommonInterfaceFilterSet):
class VMInterfaceFilterSet(CommonInterfaceFilterSet, OwnerFilterMixin, NetBoxModelFilterSet):
cluster_id = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_machine__cluster',
queryset=Cluster.objects.all(),
@@ -297,7 +299,7 @@ class VMInterfaceFilterSet(NetBoxModelFilterSet, CommonInterfaceFilterSet):
)
class VirtualDiskFilterSet(NetBoxModelFilterSet):
class VirtualDiskFilterSet(OwnerFilterMixin, NetBoxModelFilterSet):
virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_machine',
queryset=VirtualMachine.objects.all(),

View File

@@ -7,10 +7,11 @@ from dcim.forms.mixins import ScopedBulkEditForm
from dcim.models import Device, DeviceRole, Platform, Site
from extras.models import ConfigTemplate
from ipam.models import VLAN, VLANGroup, VLANTranslationPolicy, VRF
from netbox.forms import NetBoxModelBulkEditForm
from netbox.forms import NetBoxModelBulkEditForm, OrganizationalModelBulkEditForm, PrimaryModelBulkEditForm
from netbox.forms.mixins import OwnerMixin
from tenancy.models import Tenant
from utilities.forms import BulkRenameForm, add_blank_choice
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import BulkEditNullBooleanSelect
from virtualization.choices import *
@@ -28,13 +29,7 @@ __all__ = (
)
class ClusterTypeBulkEditForm(NetBoxModelBulkEditForm):
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
class ClusterTypeBulkEditForm(OrganizationalModelBulkEditForm):
model = ClusterType
fieldsets = (
FieldSet('description'),
@@ -42,13 +37,7 @@ class ClusterTypeBulkEditForm(NetBoxModelBulkEditForm):
nullable_fields = ('description',)
class ClusterGroupBulkEditForm(NetBoxModelBulkEditForm):
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
class ClusterGroupBulkEditForm(OrganizationalModelBulkEditForm):
model = ClusterGroup
fieldsets = (
FieldSet('description'),
@@ -56,7 +45,7 @@ class ClusterGroupBulkEditForm(NetBoxModelBulkEditForm):
nullable_fields = ('description',)
class ClusterBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
class ClusterBulkEditForm(ScopedBulkEditForm, PrimaryModelBulkEditForm):
type = DynamicModelChoiceField(
label=_('Type'),
queryset=ClusterType.objects.all(),
@@ -78,12 +67,6 @@ class ClusterBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
queryset=Tenant.objects.all(),
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField()
model = Cluster
fieldsets = (
@@ -95,7 +78,7 @@ class ClusterBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
)
class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
class VirtualMachineBulkEditForm(PrimaryModelBulkEditForm):
status = forms.ChoiceField(
label=_('Status'),
choices=add_blank_choice(VirtualMachineStatusChoices),
@@ -155,16 +138,10 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
required=False,
label=_('Disk (MB)')
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
config_template = DynamicModelChoiceField(
queryset=ConfigTemplate.objects.all(),
required=False
)
comments = CommentField()
model = VirtualMachine
fieldsets = (
@@ -177,7 +154,7 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
)
class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
class VMInterfaceBulkEditForm(OwnerMixin, NetBoxModelBulkEditForm):
virtual_machine = forms.ModelChoiceField(
label=_('Virtual machine'),
queryset=VirtualMachine.objects.all(),
@@ -311,7 +288,7 @@ class VMInterfaceBulkRenameForm(BulkRenameForm):
)
class VirtualDiskBulkEditForm(NetBoxModelBulkEditForm):
class VirtualDiskBulkEditForm(OwnerMixin, NetBoxModelBulkEditForm):
virtual_machine = forms.ModelChoiceField(
label=_('Virtual machine'),
queryset=VirtualMachine.objects.all(),

View File

@@ -5,9 +5,11 @@ from dcim.forms.mixins import ScopedImportForm
from dcim.models import Device, DeviceRole, Platform, Site
from extras.models import ConfigTemplate
from ipam.models import VRF
from netbox.forms import NetBoxModelImportForm
from netbox.forms import (
NetBoxModelImportForm, OrganizationalModelImportForm, OwnerCSVMixin, PrimaryModelImportForm,
)
from tenancy.models import Tenant
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField
from virtualization.choices import *
from virtualization.models import *
@@ -21,23 +23,21 @@ __all__ = (
)
class ClusterTypeImportForm(NetBoxModelImportForm):
slug = SlugField()
class ClusterTypeImportForm(OrganizationalModelImportForm):
class Meta:
model = ClusterType
fields = ('name', 'slug', 'description', 'tags')
fields = ('name', 'slug', 'description', 'owner', 'tags')
class ClusterGroupImportForm(NetBoxModelImportForm):
slug = SlugField()
class ClusterGroupImportForm(OrganizationalModelImportForm):
class Meta:
model = ClusterGroup
fields = ('name', 'slug', 'description', 'tags')
fields = ('name', 'slug', 'description', 'owner', 'tags')
class ClusterImportForm(ScopedImportForm, NetBoxModelImportForm):
class ClusterImportForm(ScopedImportForm, PrimaryModelImportForm):
type = CSVModelChoiceField(
label=_('Type'),
queryset=ClusterType.objects.all(),
@@ -74,14 +74,15 @@ class ClusterImportForm(ScopedImportForm, NetBoxModelImportForm):
class Meta:
model = Cluster
fields = (
'name', 'type', 'group', 'status', 'scope_type', 'scope_id', 'tenant', 'description', 'comments', 'tags',
'name', 'type', 'group', 'status', 'scope_type', 'scope_id', 'tenant', 'description', 'owner', 'comments',
'tags',
)
labels = {
'scope_id': _('Scope ID'),
}
class VirtualMachineImportForm(NetBoxModelImportForm):
class VirtualMachineImportForm(PrimaryModelImportForm):
status = CSVChoiceField(
label=_('Status'),
choices=VirtualMachineStatusChoices,
@@ -143,11 +144,11 @@ class VirtualMachineImportForm(NetBoxModelImportForm):
model = VirtualMachine
fields = (
'name', 'status', 'role', 'site', 'cluster', 'device', 'tenant', 'platform', 'vcpus', 'memory', 'disk',
'description', 'serial', 'config_template', 'comments', 'tags',
'description', 'serial', 'config_template', 'comments', 'owner', 'tags',
)
class VMInterfaceImportForm(NetBoxModelImportForm):
class VMInterfaceImportForm(OwnerCSVMixin, NetBoxModelImportForm):
virtual_machine = CSVModelChoiceField(
label=_('Virtual machine'),
queryset=VirtualMachine.objects.all(),
@@ -185,7 +186,7 @@ class VMInterfaceImportForm(NetBoxModelImportForm):
model = VMInterface
fields = (
'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mtu', 'description', 'mode',
'vrf', 'tags'
'vrf', 'owner', 'tags'
)
def __init__(self, data=None, *args, **kwargs):
@@ -208,7 +209,7 @@ class VMInterfaceImportForm(NetBoxModelImportForm):
return self.cleaned_data['enabled']
class VirtualDiskImportForm(NetBoxModelImportForm):
class VirtualDiskImportForm(OwnerCSVMixin, NetBoxModelImportForm):
virtual_machine = CSVModelChoiceField(
label=_('Virtual machine'),
queryset=VirtualMachine.objects.all(),
@@ -218,5 +219,5 @@ class VirtualDiskImportForm(NetBoxModelImportForm):
class Meta:
model = VirtualDisk
fields = (
'virtual_machine', 'name', 'size', 'description', 'tags'
'virtual_machine', 'name', 'size', 'description', 'owner', 'tags'
)

View File

@@ -6,10 +6,11 @@ from dcim.models import Device, DeviceRole, Location, Platform, Region, Site, Si
from extras.forms import LocalConfigContextFilterForm
from extras.models import ConfigTemplate
from ipam.models import VRF, VLANTranslationPolicy
from netbox.forms import NetBoxModelFilterSetForm
from netbox.forms import NetBoxModelFilterSetForm, OrganizationalModelFilterSetForm, PrimaryModelFilterSetForm
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
from users.models import Owner
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
from utilities.forms.fields import DynamicModelMultipleChoiceField, TagFilterField
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField
from utilities.forms.rendering import FieldSet
from virtualization.choices import *
from virtualization.models import *
@@ -25,24 +26,27 @@ __all__ = (
)
class ClusterTypeFilterForm(NetBoxModelFilterSetForm):
class ClusterTypeFilterForm(OrganizationalModelFilterSetForm):
model = ClusterType
fieldsets = (
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
)
tag = TagFilterField(model)
class ClusterGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
class ClusterGroupFilterForm(ContactModelFilterForm, OrganizationalModelFilterSetForm):
model = ClusterGroup
tag = TagFilterField(model)
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
)
class ClusterFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
class ClusterFilterForm(TenancyFilterForm, ContactModelFilterForm, PrimaryModelFilterSetForm):
model = Cluster
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('group_id', 'type_id', 'status', name=_('Attributes')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Scope')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
@@ -97,11 +101,11 @@ class VirtualMachineFilterForm(
LocalConfigContextFilterForm,
TenancyFilterForm,
ContactModelFilterForm,
NetBoxModelFilterSetForm
PrimaryModelFilterSetForm
):
model = VirtualMachine
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('cluster_group_id', 'cluster_type_id', 'cluster_id', 'device_id', name=_('Cluster')),
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
FieldSet(
@@ -199,7 +203,7 @@ class VirtualMachineFilterForm(
class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
model = VMInterface
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('cluster_id', 'virtual_machine_id', name=_('Virtual Machine')),
FieldSet('enabled', name=_('Attributes')),
FieldSet('vrf_id', 'l2vpn_id', 'mac_address', name=_('Addressing')),
@@ -250,13 +254,18 @@ class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
required=False,
label=_('VLAN Translation Policy')
)
owner_id = DynamicModelChoiceField(
queryset=Owner.objects.all(),
required=False,
label=_('Owner'),
)
tag = TagFilterField(model)
class VirtualDiskFilterForm(NetBoxModelFilterSetForm):
model = VirtualDisk
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
FieldSet('virtual_machine_id', name=_('Virtual Machine')),
FieldSet('size', name=_('Attributes')),
)
@@ -270,4 +279,9 @@ class VirtualDiskFilterForm(NetBoxModelFilterSetForm):
required=False,
min_value=1
)
owner_id = DynamicModelChoiceField(
queryset=Owner.objects.all(),
required=False,
label=_('Owner'),
)
tag = TagFilterField(model)

View File

@@ -10,12 +10,11 @@ from dcim.models import Device, DeviceRole, MACAddress, Platform, Rack, Region,
from extras.models import ConfigTemplate
from ipam.choices import VLANQinQRoleChoices
from ipam.models import IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF
from netbox.forms import NetBoxModelForm
from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm
from netbox.forms.mixins import OwnerMixin
from tenancy.forms import TenancyForm
from utilities.forms import ConfirmationForm
from utilities.forms.fields import (
CommentField, 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 virtualization.models import *
@@ -32,9 +31,7 @@ __all__ = (
)
class ClusterTypeForm(NetBoxModelForm):
slug = SlugField()
class ClusterTypeForm(OrganizationalModelForm):
fieldsets = (
FieldSet('name', 'slug', 'description', 'tags', name=_('Cluster Type')),
)
@@ -42,13 +39,11 @@ class ClusterTypeForm(NetBoxModelForm):
class Meta:
model = ClusterType
fields = (
'name', 'slug', 'description', 'tags',
'name', 'slug', 'description', 'owner', 'tags',
)
class ClusterGroupForm(NetBoxModelForm):
slug = SlugField()
class ClusterGroupForm(OrganizationalModelForm):
fieldsets = (
FieldSet('name', 'slug', 'description', 'tags', name=_('Cluster Group')),
)
@@ -56,11 +51,11 @@ class ClusterGroupForm(NetBoxModelForm):
class Meta:
model = ClusterGroup
fields = (
'name', 'slug', 'description', 'tags',
'name', 'slug', 'description', 'owner', 'tags',
)
class ClusterForm(TenancyForm, ScopedForm, NetBoxModelForm):
class ClusterForm(TenancyForm, ScopedForm, PrimaryModelForm):
type = DynamicModelChoiceField(
label=_('Type'),
queryset=ClusterType.objects.all(),
@@ -72,7 +67,6 @@ class ClusterForm(TenancyForm, ScopedForm, NetBoxModelForm):
required=False,
quick_add=True
)
comments = CommentField()
fieldsets = (
FieldSet('name', 'type', 'group', 'status', 'description', 'tags', name=_('Cluster')),
@@ -83,7 +77,7 @@ class ClusterForm(TenancyForm, ScopedForm, NetBoxModelForm):
class Meta:
model = Cluster
fields = (
'name', 'type', 'group', 'status', 'tenant', 'scope_type', 'description', 'comments', 'tags',
'name', 'type', 'group', 'status', 'tenant', 'scope_type', 'description', 'owner', 'comments', 'tags',
)
@@ -173,7 +167,7 @@ class ClusterRemoveDevicesForm(ConfirmationForm):
)
class VirtualMachineForm(TenancyForm, NetBoxModelForm):
class VirtualMachineForm(TenancyForm, PrimaryModelForm):
site = DynamicModelChoiceField(
label=_('Site'),
queryset=Site.objects.all(),
@@ -221,7 +215,6 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
required=False,
label=_('Config template')
)
comments = CommentField()
fieldsets = (
FieldSet('name', 'role', 'status', 'description', 'serial', 'tags', name=_('Virtual Machine')),
@@ -236,7 +229,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
model = VirtualMachine
fields = [
'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4',
'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'serial', 'comments', 'tags',
'primary_ip6', 'vcpus', 'memory', 'disk', 'description', 'serial', 'owner', 'comments', 'tags',
'local_context_data', 'config_template',
]
@@ -288,7 +281,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
# Virtual machine components
#
class VMComponentForm(NetBoxModelForm):
class VMComponentForm(OwnerMixin, NetBoxModelForm):
virtual_machine = DynamicModelChoiceField(
label=_('Virtual machine'),
queryset=VirtualMachine.objects.all(),
@@ -387,7 +380,7 @@ class VMInterfaceForm(InterfaceCommonForm, VMComponentForm):
fields = [
'virtual_machine', 'name', 'parent', 'bridge', 'enabled', 'mtu', 'description', 'mode', 'vlan_group',
'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'vrf', 'primary_mac_address',
'tags',
'owner', 'tags',
]
labels = {
'mode': _('802.1Q Mode'),
@@ -406,5 +399,5 @@ class VirtualDiskForm(VMComponentForm):
class Meta:
model = VirtualDisk
fields = [
'virtual_machine', 'name', 'size', 'description', 'tags',
'virtual_machine', 'name', 'size', 'description', 'owner', 'tags',
]

View File

@@ -6,7 +6,8 @@ import strawberry_django
from extras.graphql.mixins import ConfigContextMixin, ContactsMixin
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
from netbox.graphql.scalars import BigInt
from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType
from netbox.graphql.types import OrganizationalObjectType, PrimaryObjectType, NetBoxObjectType
from users.graphql.mixins import OwnerMixin
from virtualization import models
from .filters import *
@@ -36,7 +37,7 @@ __all__ = (
@strawberry.type
class ComponentType(NetBoxObjectType):
class ComponentType(OwnerMixin, NetBoxObjectType):
"""
Base type for device/VM components
"""
@@ -49,7 +50,7 @@ class ComponentType(NetBoxObjectType):
filters=ClusterFilter,
pagination=True
)
class ClusterType(ContactsMixin, VLANGroupsMixin, NetBoxObjectType):
class ClusterType(ContactsMixin, VLANGroupsMixin, PrimaryObjectType):
type: Annotated["ClusterTypeType", strawberry.lazy('virtualization.graphql.types')] | None
group: Annotated["ClusterGroupType", strawberry.lazy('virtualization.graphql.types')] | None
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
@@ -94,7 +95,7 @@ class ClusterTypeType(OrganizationalObjectType):
filters=VirtualMachineFilter,
pagination=True
)
class VirtualMachineType(ConfigContextMixin, ContactsMixin, NetBoxObjectType):
class VirtualMachineType(ConfigContextMixin, ContactsMixin, PrimaryObjectType):
interface_count: BigInt
virtual_disk_count: BigInt
interface_count: BigInt

View File

@@ -0,0 +1,54 @@
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0015_owner'),
('virtualization', '0048_populate_mac_addresses'),
]
operations = [
migrations.AddField(
model_name='cluster',
name='owner',
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
),
),
migrations.AddField(
model_name='clustergroup',
name='owner',
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
),
),
migrations.AddField(
model_name='clustertype',
name='owner',
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
),
),
migrations.AddField(
model_name='virtualdisk',
name='owner',
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
),
),
migrations.AddField(
model_name='virtualmachine',
name='owner',
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
),
),
migrations.AddField(
model_name='vminterface',
name='owner',
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
),
),
]

View File

@@ -15,6 +15,7 @@ from extras.querysets import ConfigContextModelQuerySet
from netbox.config import get_config
from netbox.models import NetBoxModel, PrimaryModel
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
from netbox.models.mixins import OwnerMixin
from utilities.fields import CounterCacheField, NaturalOrderingField
from utilities.ordering import naturalize_interface
from utilities.query_functions import CollateAsChar
@@ -263,7 +264,7 @@ class VirtualMachine(ContactsMixin, ImageAttachmentsMixin, RenderConfigMixin, Co
#
class ComponentModel(NetBoxModel):
class ComponentModel(OwnerMixin, NetBoxModel):
"""
An abstract model inherited by any model which has a parent VirtualMachine.
"""

View File

@@ -1,10 +1,10 @@
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from netbox.tables import OrganizationalModelTable, PrimaryModelTable, columns
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
from virtualization.models import Cluster, ClusterGroup, ClusterType
from netbox.tables import NetBoxTable, columns
__all__ = (
'ClusterTable',
'ClusterGroupTable',
@@ -12,7 +12,7 @@ __all__ = (
)
class ClusterTypeTable(NetBoxTable):
class ClusterTypeTable(OrganizationalModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -26,7 +26,7 @@ class ClusterTypeTable(NetBoxTable):
url_name='virtualization:clustertype_list'
)
class Meta(NetBoxTable.Meta):
class Meta(OrganizationalModelTable.Meta):
model = ClusterType
fields = (
'pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'created', 'last_updated', 'tags', 'actions',
@@ -34,7 +34,7 @@ class ClusterTypeTable(NetBoxTable):
default_columns = ('pk', 'name', 'cluster_count', 'description')
class ClusterGroupTable(ContactsColumnMixin, NetBoxTable):
class ClusterGroupTable(ContactsColumnMixin, OrganizationalModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -48,7 +48,7 @@ class ClusterGroupTable(ContactsColumnMixin, NetBoxTable):
url_name='virtualization:clustergroup_list'
)
class Meta(NetBoxTable.Meta):
class Meta(OrganizationalModelTable.Meta):
model = ClusterGroup
fields = (
'pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'contacts', 'tags', 'created', 'last_updated',
@@ -57,7 +57,7 @@ class ClusterGroupTable(ContactsColumnMixin, NetBoxTable):
default_columns = ('pk', 'name', 'cluster_count', 'description')
class ClusterTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
class ClusterTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -91,14 +91,11 @@ class ClusterTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
url_params={'cluster_id': 'pk'},
verbose_name=_('VMs')
)
comments = columns.MarkdownColumn(
verbose_name=_('Comments'),
)
tags = columns.TagColumn(
url_name='virtualization:cluster_list'
)
class Meta(NetBoxTable.Meta):
class Meta(PrimaryModelTable.Meta):
model = Cluster
fields = (
'pk', 'id', 'name', 'type', 'group', 'status', 'tenant', 'tenant_group', 'scope', 'scope_type',

View File

@@ -2,7 +2,7 @@ import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from dcim.tables.devices import BaseInterfaceTable
from netbox.tables import NetBoxTable, columns
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
@@ -21,7 +21,7 @@ __all__ = (
# Virtual machines
#
class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
name = tables.Column(
verbose_name=_('Name'),
linkify=True
@@ -48,9 +48,6 @@ class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable)
linkify=True,
verbose_name=_('Platform')
)
comments = columns.MarkdownColumn(
verbose_name=_('Comments'),
)
primary_ip4 = tables.Column(
linkify=True,
verbose_name=_('IPv4 Address')
@@ -81,7 +78,7 @@ class VirtualMachineTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable)
verbose_name=_('Disk'),
)
class Meta(NetBoxTable.Meta):
class Meta(PrimaryModelTable.Meta):
model = VirtualMachine
fields = (
'pk', 'id', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'tenant_group', 'vcpus',