mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-30 04:16:24 -06:00
7699 refactor mixins
This commit is contained in:
parent
0c7710ddb6
commit
5c022f0ae2
@ -123,3 +123,8 @@ COMPATIBLE_TERMINATION_TYPES = {
|
|||||||
'powerport': ['poweroutlet', 'powerfeed'],
|
'powerport': ['poweroutlet', 'powerfeed'],
|
||||||
'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
|
'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# models values for ContentTypes which may be Cluster scope types
|
||||||
|
LOCATION_SCOPE_TYPES = (
|
||||||
|
'region', 'sitegroup', 'site', 'location',
|
||||||
|
)
|
||||||
|
@ -2,15 +2,36 @@ from django import forms
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from dcim.constants import LOCATION_SCOPE_TYPES
|
||||||
|
from dcim.models import Site
|
||||||
from utilities.forms import get_field_value
|
from utilities.forms import get_field_value
|
||||||
|
from utilities.forms.fields import (
|
||||||
|
ContentTypeChoiceField, CSVContentTypeField, DynamicModelChoiceField,
|
||||||
|
)
|
||||||
from utilities.templatetags.builtins.filters import bettertitle
|
from utilities.templatetags.builtins.filters import bettertitle
|
||||||
|
from utilities.forms.widgets import HTMXSelect
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
'ScopedBulkEditForm',
|
||||||
'ScopedForm',
|
'ScopedForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ScopedForm(forms.Form):
|
class ScopedForm(forms.Form):
|
||||||
|
scope_type = ContentTypeChoiceField(
|
||||||
|
queryset=ContentType.objects.filter(model__in=LOCATION_SCOPE_TYPES),
|
||||||
|
widget=HTMXSelect(),
|
||||||
|
required=False,
|
||||||
|
label=_('Scope type')
|
||||||
|
)
|
||||||
|
scope = DynamicModelChoiceField(
|
||||||
|
label=_('Scope'),
|
||||||
|
queryset=Site.objects.none(), # Initial queryset
|
||||||
|
required=False,
|
||||||
|
disabled=True,
|
||||||
|
selector=True
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
instance = kwargs.get('instance')
|
instance = kwargs.get('instance')
|
||||||
@ -43,3 +64,40 @@ class ScopedForm(forms.Form):
|
|||||||
|
|
||||||
if self.instance and scope_type_id != self.instance.scope_type_id:
|
if self.instance and scope_type_id != self.instance.scope_type_id:
|
||||||
self.initial['scope'] = None
|
self.initial['scope'] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ScopedBulkEditForm(forms.Form):
|
||||||
|
scope_type = ContentTypeChoiceField(
|
||||||
|
queryset=ContentType.objects.filter(model__in=LOCATION_SCOPE_TYPES),
|
||||||
|
widget=HTMXSelect(method='post', attrs={'hx-select': '#form_fields'}),
|
||||||
|
required=False,
|
||||||
|
label=_('Scope type')
|
||||||
|
)
|
||||||
|
scope = DynamicModelChoiceField(
|
||||||
|
label=_('Scope'),
|
||||||
|
queryset=Site.objects.none(), # Initial queryset
|
||||||
|
required=False,
|
||||||
|
disabled=True,
|
||||||
|
selector=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if scope_type_id := get_field_value(self, 'scope_type'):
|
||||||
|
try:
|
||||||
|
scope_type = ContentType.objects.get(pk=scope_type_id)
|
||||||
|
model = scope_type.model_class()
|
||||||
|
self.fields['scope'].queryset = model.objects.all()
|
||||||
|
self.fields['scope'].widget.attrs['selector'] = model._meta.label_lower
|
||||||
|
self.fields['scope'].disabled = False
|
||||||
|
self.fields['scope'].label = _(bettertitle(model._meta.verbose_name))
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ScopedImportForm(forms.Form):
|
||||||
|
scope_type = CSVContentTypeField(
|
||||||
|
queryset=ContentType.objects.filter(model__in=LOCATION_SCOPE_TYPES),
|
||||||
|
required=False,
|
||||||
|
label=_('Scope type (app & model)')
|
||||||
|
)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from dcim.constants import LOCATION_SCOPE_TYPES
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'CachedScopeMixin',
|
'CachedScopeMixin',
|
||||||
@ -33,8 +35,25 @@ class RenderConfigMixin(models.Model):
|
|||||||
|
|
||||||
class CachedScopeMixin(models.Model):
|
class CachedScopeMixin(models.Model):
|
||||||
"""
|
"""
|
||||||
Cached associations for scope to enable efficient filtering - must define scope and scope_type on model
|
Cached associations for scope to enable efficient filtering
|
||||||
"""
|
"""
|
||||||
|
scope_type = models.ForeignKey(
|
||||||
|
to='contenttypes.ContentType',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
limit_choices_to=models.Q(model__in=LOCATION_SCOPE_TYPES),
|
||||||
|
related_name='+',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
scope_id = models.PositiveBigIntegerField(
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
scope = GenericForeignKey(
|
||||||
|
ct_field='scope_type',
|
||||||
|
fk_field='scope_id'
|
||||||
|
)
|
||||||
|
|
||||||
_location = models.ForeignKey(
|
_location = models.ForeignKey(
|
||||||
to='dcim.Location',
|
to='dcim.Location',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from dcim.constants import LOCATION_SCOPE_TYPES
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
@ -5,7 +6,6 @@ from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountF
|
|||||||
from netbox.api.serializers import NetBoxModelSerializer
|
from netbox.api.serializers import NetBoxModelSerializer
|
||||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||||
from virtualization.choices import *
|
from virtualization.choices import *
|
||||||
from virtualization.constants import CLUSTER_SCOPE_TYPES
|
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||||
from utilities.api import get_serializer_for_model
|
from utilities.api import get_serializer_for_model
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ class ClusterSerializer(NetBoxModelSerializer):
|
|||||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||||
scope_type = ContentTypeField(
|
scope_type = ContentTypeField(
|
||||||
queryset=ContentType.objects.filter(
|
queryset=ContentType.objects.filter(
|
||||||
model__in=CLUSTER_SCOPE_TYPES
|
model__in=LOCATION_SCOPE_TYPES
|
||||||
),
|
),
|
||||||
allow_null=True,
|
allow_null=True,
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
# models values for ContentTypes which may be Cluster scope types
|
|
||||||
CLUSTER_SCOPE_TYPES = (
|
|
||||||
'region', 'sitegroup', 'site', 'location',
|
|
||||||
)
|
|
@ -1,20 +1,19 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.choices import InterfaceModeChoices
|
from dcim.choices import InterfaceModeChoices
|
||||||
from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
|
from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
|
||||||
|
from dcim.forms.mixins import ScopedBulkEditForm
|
||||||
from dcim.models import Device, DeviceRole, Platform, Site
|
from dcim.models import Device, DeviceRole, Platform, Site
|
||||||
from extras.models import ConfigTemplate
|
from extras.models import ConfigTemplate
|
||||||
from ipam.models import VLAN, VLANGroup, VRF
|
from ipam.models import VLAN, VLANGroup, VRF
|
||||||
from netbox.forms import NetBoxModelBulkEditForm
|
from netbox.forms import NetBoxModelBulkEditForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import BulkRenameForm, add_blank_choice, get_field_value
|
from utilities.forms import BulkRenameForm, add_blank_choice
|
||||||
from utilities.forms.fields import CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||||
from utilities.forms.rendering import FieldSet
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import BulkEditNullBooleanSelect, HTMXSelect
|
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||||
from virtualization.choices import *
|
from virtualization.choices import *
|
||||||
from virtualization.constants import CLUSTER_SCOPE_TYPES
|
|
||||||
from virtualization.models import *
|
from virtualization.models import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -57,7 +56,7 @@ class ClusterGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
nullable_fields = ('description',)
|
nullable_fields = ('description',)
|
||||||
|
|
||||||
|
|
||||||
class ClusterBulkEditForm(NetBoxModelBulkEditForm):
|
class ClusterBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
|
||||||
type = DynamicModelChoiceField(
|
type = DynamicModelChoiceField(
|
||||||
label=_('Type'),
|
label=_('Type'),
|
||||||
queryset=ClusterType.objects.all(),
|
queryset=ClusterType.objects.all(),
|
||||||
@ -79,19 +78,6 @@ class ClusterBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
scope_type = ContentTypeChoiceField(
|
|
||||||
queryset=ContentType.objects.filter(model__in=CLUSTER_SCOPE_TYPES),
|
|
||||||
widget=HTMXSelect(method='post', attrs={'hx-select': '#form_fields'}),
|
|
||||||
required=False,
|
|
||||||
label=_('Scope type')
|
|
||||||
)
|
|
||||||
scope = DynamicModelChoiceField(
|
|
||||||
label=_('Scope'),
|
|
||||||
queryset=Site.objects.none(), # Initial queryset
|
|
||||||
required=False,
|
|
||||||
disabled=True,
|
|
||||||
selector=True
|
|
||||||
)
|
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
label=_('Description'),
|
label=_('Description'),
|
||||||
max_length=200,
|
max_length=200,
|
||||||
@ -109,21 +95,6 @@ class ClusterBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
if scope_type_id := get_field_value(self, 'scope_type'):
|
|
||||||
try:
|
|
||||||
scope_type = ContentType.objects.get(pk=scope_type_id)
|
|
||||||
model = scope_type.model_class()
|
|
||||||
self.fields['scope'].queryset = model.objects.all()
|
|
||||||
self.fields['scope'].widget.attrs['selector'] = model._meta.label_lower
|
|
||||||
self.fields['scope'].disabled = False
|
|
||||||
self.fields['scope'].label = _(bettertitle(model._meta.verbose_name))
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
|
class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
status = forms.ChoiceField(
|
status = forms.ChoiceField(
|
||||||
label=_('Status'),
|
label=_('Status'),
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.choices import InterfaceModeChoices
|
from dcim.choices import InterfaceModeChoices
|
||||||
|
from dcim.forms.mixins import ScopedImportForm
|
||||||
from dcim.models import Device, DeviceRole, Platform, Site
|
from dcim.models import Device, DeviceRole, Platform, Site
|
||||||
from extras.models import ConfigTemplate
|
from extras.models import ConfigTemplate
|
||||||
from ipam.models import VRF
|
from ipam.models import VRF
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms.fields import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField
|
from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
|
||||||
from virtualization.choices import *
|
from virtualization.choices import *
|
||||||
from virtualization.constants import CLUSTER_SCOPE_TYPES
|
|
||||||
from virtualization.models import *
|
from virtualization.models import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -38,7 +37,7 @@ class ClusterGroupImportForm(NetBoxModelImportForm):
|
|||||||
fields = ('name', 'slug', 'description', 'tags')
|
fields = ('name', 'slug', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ClusterImportForm(NetBoxModelImportForm):
|
class ClusterImportForm(ScopedImportForm, NetBoxModelImportForm):
|
||||||
type = CSVModelChoiceField(
|
type = CSVModelChoiceField(
|
||||||
label=_('Type'),
|
label=_('Type'),
|
||||||
queryset=ClusterType.objects.all(),
|
queryset=ClusterType.objects.all(),
|
||||||
@ -57,11 +56,6 @@ class ClusterImportForm(NetBoxModelImportForm):
|
|||||||
choices=ClusterStatusChoices,
|
choices=ClusterStatusChoices,
|
||||||
help_text=_('Operational status')
|
help_text=_('Operational status')
|
||||||
)
|
)
|
||||||
scope_type = CSVContentTypeField(
|
|
||||||
queryset=ContentType.objects.filter(model__in=CLUSTER_SCOPE_TYPES),
|
|
||||||
required=False,
|
|
||||||
label=_('Scope type (app & model)')
|
|
||||||
)
|
|
||||||
site = CSVModelChoiceField(
|
site = CSVModelChoiceField(
|
||||||
label=_('Site'),
|
label=_('Site'),
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
|
@ -14,10 +14,8 @@ from utilities.forms import ConfirmationForm
|
|||||||
from utilities.forms.fields import (
|
from utilities.forms.fields import (
|
||||||
CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField,
|
CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField,
|
||||||
)
|
)
|
||||||
from utilities.forms.fields import ContentTypeChoiceField
|
|
||||||
from utilities.forms.rendering import FieldSet
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import HTMXSelect
|
from utilities.forms.widgets import HTMXSelect
|
||||||
from virtualization.constants import CLUSTER_SCOPE_TYPES
|
|
||||||
from virtualization.models import *
|
from virtualization.models import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -70,19 +68,6 @@ class ClusterForm(TenancyForm, ScopedForm, NetBoxModelForm):
|
|||||||
queryset=ClusterGroup.objects.all(),
|
queryset=ClusterGroup.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
scope_type = ContentTypeChoiceField(
|
|
||||||
queryset=ContentType.objects.filter(model__in=CLUSTER_SCOPE_TYPES),
|
|
||||||
widget=HTMXSelect(),
|
|
||||||
required=False,
|
|
||||||
label=_('Scope type')
|
|
||||||
)
|
|
||||||
scope = DynamicModelChoiceField(
|
|
||||||
label=_('Scope'),
|
|
||||||
queryset=Site.objects.none(), # Initial queryset
|
|
||||||
required=False,
|
|
||||||
disabled=True,
|
|
||||||
selector=True
|
|
||||||
)
|
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -9,7 +9,6 @@ from dcim.models.mixins import CachedScopeMixin
|
|||||||
from netbox.models import OrganizationalModel, PrimaryModel
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
from netbox.models.features import ContactsMixin
|
from netbox.models.features import ContactsMixin
|
||||||
from virtualization.choices import *
|
from virtualization.choices import *
|
||||||
from virtualization.constants import CLUSTER_SCOPE_TYPES
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Cluster',
|
'Cluster',
|
||||||
@ -79,22 +78,6 @@ class Cluster(ContactsMixin, CachedScopeMixin, PrimaryModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
scope_type = models.ForeignKey(
|
|
||||||
to='contenttypes.ContentType',
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
limit_choices_to=models.Q(model__in=CLUSTER_SCOPE_TYPES),
|
|
||||||
related_name='+',
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
scope_id = models.PositiveBigIntegerField(
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
|
||||||
scope = GenericForeignKey(
|
|
||||||
ct_field='scope_type',
|
|
||||||
fk_field='scope_id'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generic relations
|
# Generic relations
|
||||||
vlan_groups = GenericRelation(
|
vlan_groups = GenericRelation(
|
||||||
|
Loading…
Reference in New Issue
Block a user