|
@@ -1,6 +1,6 @@
|
|
|
from django import forms
|
|
from django import forms
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
-from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|
|
|
|
|
|
+from django.core.exceptions import ValidationError
|
|
|
from django.utils.safestring import mark_safe
|
|
from django.utils.safestring import mark_safe
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
|
|
@@ -13,18 +13,16 @@ from ipam.models import *
|
|
|
from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm
|
|
from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm
|
|
|
from tenancy.forms import TenancyForm
|
|
from tenancy.forms import TenancyForm
|
|
|
from utilities.exceptions import PermissionsViolation
|
|
from utilities.exceptions import PermissionsViolation
|
|
|
-from utilities.forms import add_blank_choice
|
|
|
|
|
|
|
+from utilities.forms import GenericObjectFormMixin, add_blank_choice
|
|
|
from utilities.forms.fields import (
|
|
from utilities.forms.fields import (
|
|
|
- ContentTypeChoiceField,
|
|
|
|
|
DynamicModelChoiceField,
|
|
DynamicModelChoiceField,
|
|
|
DynamicModelMultipleChoiceField,
|
|
DynamicModelMultipleChoiceField,
|
|
|
|
|
+ GenericObjectChoiceField,
|
|
|
NumericArrayField,
|
|
NumericArrayField,
|
|
|
NumericRangeArrayField,
|
|
NumericRangeArrayField,
|
|
|
)
|
|
)
|
|
|
from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, TabbedGroups
|
|
from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, TabbedGroups
|
|
|
-from utilities.forms.utils import get_field_value
|
|
|
|
|
-from utilities.forms.widgets import DatePicker, HTMXSelect
|
|
|
|
|
-from utilities.templatetags.builtins.filters import bettertitle
|
|
|
|
|
|
|
+from utilities.forms.widgets import DatePicker
|
|
|
from virtualization.models import VirtualMachine, VMInterface
|
|
from virtualization.models import VirtualMachine, VMInterface
|
|
|
|
|
|
|
|
__all__ = (
|
|
__all__ = (
|
|
@@ -215,7 +213,7 @@ class PrefixForm(TenancyForm, ScopedForm, PrimaryModelForm):
|
|
|
required=False,
|
|
required=False,
|
|
|
selector=True,
|
|
selector=True,
|
|
|
query_params={
|
|
query_params={
|
|
|
- 'available_at_site': '$scope',
|
|
|
|
|
|
|
+ 'available_at_site': '$scope_object_id',
|
|
|
},
|
|
},
|
|
|
label=_('VLAN'),
|
|
label=_('VLAN'),
|
|
|
)
|
|
)
|
|
@@ -230,7 +228,7 @@ class PrefixForm(TenancyForm, ScopedForm, PrimaryModelForm):
|
|
|
FieldSet(
|
|
FieldSet(
|
|
|
'prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', name=_('Prefix')
|
|
'prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', name=_('Prefix')
|
|
|
),
|
|
),
|
|
|
- FieldSet('scope_type', 'scope', name=_('Scope'), html_id='scope'),
|
|
|
|
|
|
|
+ FieldSet('scope', name=_('Scope'), html_id='scope'),
|
|
|
FieldSet('vlan', name=_('VLAN Assignment')),
|
|
FieldSet('vlan', name=_('VLAN Assignment')),
|
|
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
|
|
)
|
|
)
|
|
@@ -238,16 +236,16 @@ class PrefixForm(TenancyForm, ScopedForm, PrimaryModelForm):
|
|
|
class Meta:
|
|
class Meta:
|
|
|
model = Prefix
|
|
model = Prefix
|
|
|
fields = [
|
|
fields = [
|
|
|
- 'prefix', 'vrf', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'scope_type', 'tenant_group',
|
|
|
|
|
|
|
+ 'prefix', 'vrf', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'tenant_group',
|
|
|
'tenant', 'description', 'owner', 'comments', 'tags',
|
|
'tenant', 'description', 'owner', 'comments', 'tags',
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
def __init__(self, *args, **kwargs):
|
|
|
super().__init__(*args, **kwargs)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
- # #18605: only filter VLAN select list if scope field is a Site
|
|
|
|
|
|
|
+ # #18605: only filter VLAN select list if the selected scope is a Site (or none is selected yet)
|
|
|
if scope_field := self.fields.get('scope', None):
|
|
if scope_field := self.fields.get('scope', None):
|
|
|
- if scope_field.queryset.model is not Site:
|
|
|
|
|
|
|
+ if scope_field.selected_model not in (None, Site):
|
|
|
self.fields['vlan'].widget.attrs.pop('data-dynamic-params', None)
|
|
self.fields['vlan'].widget.attrs.pop('data-dynamic-params', None)
|
|
|
|
|
|
|
|
|
|
|
|
@@ -262,7 +260,7 @@ class PrefixBulkAddForm(PrefixForm):
|
|
|
FieldSet(
|
|
FieldSet(
|
|
|
'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', name=_('Prefix')
|
|
'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', name=_('Prefix')
|
|
|
),
|
|
),
|
|
|
- FieldSet('scope_type', 'scope', name=_('Scope'), html_id='scope'),
|
|
|
|
|
|
|
+ FieldSet('scope', name=_('Scope'), html_id='scope'),
|
|
|
FieldSet('vlan', name=_('VLAN Assignment')),
|
|
FieldSet('vlan', name=_('VLAN Assignment')),
|
|
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
|
|
)
|
|
)
|
|
@@ -620,68 +618,32 @@ class FHRPGroupAssignmentForm(forms.ModelForm):
|
|
|
return group
|
|
return group
|
|
|
|
|
|
|
|
|
|
|
|
|
-class VLANGroupForm(TenancyForm, OrganizationalModelForm):
|
|
|
|
|
|
|
+class VLANGroupForm(GenericObjectFormMixin, TenancyForm, OrganizationalModelForm):
|
|
|
vid_ranges = NumericRangeArrayField(
|
|
vid_ranges = NumericRangeArrayField(
|
|
|
label=_('VLAN IDs')
|
|
label=_('VLAN IDs')
|
|
|
)
|
|
)
|
|
|
- scope_type = ContentTypeChoiceField(
|
|
|
|
|
- queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
|
|
|
|
|
- widget=HTMXSelect(hx_target_id='scope'),
|
|
|
|
|
- required=False,
|
|
|
|
|
- label=_('Scope type')
|
|
|
|
|
- )
|
|
|
|
|
- scope = DynamicModelChoiceField(
|
|
|
|
|
|
|
+ scope = GenericObjectChoiceField(
|
|
|
label=_('Scope'),
|
|
label=_('Scope'),
|
|
|
- queryset=Site.objects.none(), # Initial queryset
|
|
|
|
|
|
|
+ content_type_queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
|
|
|
required=False,
|
|
required=False,
|
|
|
- disabled=True,
|
|
|
|
|
- selector=True
|
|
|
|
|
|
|
+ selector=True,
|
|
|
|
|
+ hx_target_id='scope',
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
fieldsets = (
|
|
fieldsets = (
|
|
|
FieldSet('name', 'slug', 'description', 'tags', name=_('VLAN Group')),
|
|
FieldSet('name', 'slug', 'description', 'tags', name=_('VLAN Group')),
|
|
|
FieldSet('vid_ranges', name=_('Child VLANs')),
|
|
FieldSet('vid_ranges', name=_('Child VLANs')),
|
|
|
- FieldSet('scope_type', 'scope', name=_('Scope'), html_id='scope'),
|
|
|
|
|
|
|
+ FieldSet('scope', name=_('Scope'), html_id='scope'),
|
|
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
class Meta:
|
|
|
model = VLANGroup
|
|
model = VLANGroup
|
|
|
fields = [
|
|
fields = [
|
|
|
- 'name', 'slug', 'description', 'vid_ranges', 'scope_type', 'tenant_group', 'tenant', 'owner', 'comments',
|
|
|
|
|
|
|
+ 'name', 'slug', 'description', 'vid_ranges', 'tenant_group', 'tenant', 'owner', 'comments',
|
|
|
'tags',
|
|
'tags',
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
- def __init__(self, *args, **kwargs):
|
|
|
|
|
- instance = kwargs.get('instance')
|
|
|
|
|
- initial = kwargs.get('initial', {})
|
|
|
|
|
-
|
|
|
|
|
- if instance is not None and instance.scope:
|
|
|
|
|
- initial['scope'] = instance.scope
|
|
|
|
|
- kwargs['initial'] = initial
|
|
|
|
|
-
|
|
|
|
|
- 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
|
|
|
|
|
-
|
|
|
|
|
- if self.instance and scope_type_id != self.instance.scope_type_id:
|
|
|
|
|
- self.initial['scope'] = None
|
|
|
|
|
-
|
|
|
|
|
- def clean(self):
|
|
|
|
|
- super().clean()
|
|
|
|
|
-
|
|
|
|
|
- # Assign the selected scope (if any)
|
|
|
|
|
- self.instance.scope = self.cleaned_data.get('scope')
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
class VLANForm(TenancyForm, PrimaryModelForm):
|
|
class VLANForm(TenancyForm, PrimaryModelForm):
|
|
|
group = DynamicModelChoiceField(
|
|
group = DynamicModelChoiceField(
|
|
@@ -798,19 +760,13 @@ class ServiceTemplateForm(PrimaryModelForm):
|
|
|
fields = ('name', 'protocol', 'ports', 'description', 'owner', 'comments', 'tags')
|
|
fields = ('name', 'protocol', 'ports', 'description', 'owner', 'comments', 'tags')
|
|
|
|
|
|
|
|
|
|
|
|
|
-class ServiceForm(PrimaryModelForm):
|
|
|
|
|
- parent_object_type = ContentTypeChoiceField(
|
|
|
|
|
- queryset=ContentType.objects.filter(SERVICE_ASSIGNMENT_MODELS),
|
|
|
|
|
- widget=HTMXSelect(hx_target_id='service'),
|
|
|
|
|
- required=True,
|
|
|
|
|
- label=_('Parent type')
|
|
|
|
|
- )
|
|
|
|
|
- parent = DynamicModelChoiceField(
|
|
|
|
|
|
|
+class ServiceForm(GenericObjectFormMixin, PrimaryModelForm):
|
|
|
|
|
+ parent = GenericObjectChoiceField(
|
|
|
label=_('Parent'),
|
|
label=_('Parent'),
|
|
|
- queryset=Device.objects.none(), # Initial queryset
|
|
|
|
|
|
|
+ content_type_queryset=ContentType.objects.filter(SERVICE_ASSIGNMENT_MODELS),
|
|
|
required=True,
|
|
required=True,
|
|
|
- disabled=True,
|
|
|
|
|
- selector=True
|
|
|
|
|
|
|
+ selector=True,
|
|
|
|
|
+ hx_target_id='service',
|
|
|
)
|
|
)
|
|
|
ports = NumericArrayField(
|
|
ports = NumericArrayField(
|
|
|
label=_('Ports'),
|
|
label=_('Ports'),
|
|
@@ -828,7 +784,7 @@ class ServiceForm(PrimaryModelForm):
|
|
|
|
|
|
|
|
fieldsets = (
|
|
fieldsets = (
|
|
|
FieldSet(
|
|
FieldSet(
|
|
|
- 'parent_object_type', 'parent', 'name',
|
|
|
|
|
|
|
+ 'parent', 'name',
|
|
|
InlineFields('protocol', 'ports', label=_('Port(s)')),
|
|
InlineFields('protocol', 'ports', label=_('Port(s)')),
|
|
|
'ipaddresses', 'description', 'tags', name=_('Application Service'),
|
|
'ipaddresses', 'description', 'tags', name=_('Application Service'),
|
|
|
html_id='service',
|
|
html_id='service',
|
|
@@ -839,48 +795,20 @@ class ServiceForm(PrimaryModelForm):
|
|
|
model = Service
|
|
model = Service
|
|
|
fields = [
|
|
fields = [
|
|
|
'name', 'protocol', 'ports', 'ipaddresses', 'description', 'owner', 'comments', 'tags',
|
|
'name', 'protocol', 'ports', 'ipaddresses', 'description', 'owner', 'comments', 'tags',
|
|
|
- 'parent_object_type',
|
|
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
def __init__(self, *args, **kwargs):
|
|
|
- initial = kwargs.get('initial', {}).copy()
|
|
|
|
|
-
|
|
|
|
|
- if (instance := kwargs.get('instance', None)) and instance.parent:
|
|
|
|
|
- initial['parent'] = instance.parent
|
|
|
|
|
-
|
|
|
|
|
- kwargs['initial'] = initial
|
|
|
|
|
-
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
- if parent_object_type_id := get_field_value(self, 'parent_object_type'):
|
|
|
|
|
- try:
|
|
|
|
|
- parent_type = ContentType.objects.get(pk=parent_object_type_id)
|
|
|
|
|
- model = parent_type.model_class()
|
|
|
|
|
- if model == Device:
|
|
|
|
|
- self.fields['ipaddresses'].widget.add_query_params({
|
|
|
|
|
- 'device_id': '$parent',
|
|
|
|
|
- })
|
|
|
|
|
- elif model == VirtualMachine:
|
|
|
|
|
- self.fields['ipaddresses'].widget.add_query_params({
|
|
|
|
|
- 'virtual_machine_id': '$parent',
|
|
|
|
|
- })
|
|
|
|
|
- elif model == FHRPGroup:
|
|
|
|
|
- self.fields['ipaddresses'].widget.add_query_params({
|
|
|
|
|
- 'fhrpgroup_id': '$parent',
|
|
|
|
|
- })
|
|
|
|
|
- self.fields['parent'].queryset = model.objects.all()
|
|
|
|
|
- self.fields['parent'].widget.attrs['selector'] = model._meta.label_lower
|
|
|
|
|
- self.fields['parent'].disabled = False
|
|
|
|
|
- self.fields['parent'].label = _(bettertitle(model._meta.verbose_name))
|
|
|
|
|
- except ObjectDoesNotExist:
|
|
|
|
|
- pass
|
|
|
|
|
-
|
|
|
|
|
- if self.instance and self.instance.pk and parent_object_type_id != self.instance.parent_object_type_id:
|
|
|
|
|
- self.initial['parent'] = None
|
|
|
|
|
-
|
|
|
|
|
- def clean(self):
|
|
|
|
|
- super().clean()
|
|
|
|
|
- self.instance.parent = self.cleaned_data.get('parent')
|
|
|
|
|
|
|
+ # Filter the IP address selector to those belonging to the selected parent. The object subwidget is
|
|
|
|
|
+ # named "parent_object_id", so the dynamic param references "$parent_object_id".
|
|
|
|
|
+ parent_model = self.fields['parent'].selected_model
|
|
|
|
|
+ if parent_model is Device:
|
|
|
|
|
+ self.fields['ipaddresses'].widget.add_query_params({'device_id': '$parent_object_id'})
|
|
|
|
|
+ elif parent_model is VirtualMachine:
|
|
|
|
|
+ self.fields['ipaddresses'].widget.add_query_params({'virtual_machine_id': '$parent_object_id'})
|
|
|
|
|
+ elif parent_model is FHRPGroup:
|
|
|
|
|
+ self.fields['ipaddresses'].widget.add_query_params({'fhrpgroup_id': '$parent_object_id'})
|
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceCreateForm(ServiceForm):
|
|
class ServiceCreateForm(ServiceForm):
|
|
@@ -892,7 +820,7 @@ class ServiceCreateForm(ServiceForm):
|
|
|
|
|
|
|
|
fieldsets = (
|
|
fieldsets = (
|
|
|
FieldSet(
|
|
FieldSet(
|
|
|
- 'parent_object_type', 'parent',
|
|
|
|
|
|
|
+ 'parent',
|
|
|
TabbedGroups(
|
|
TabbedGroups(
|
|
|
FieldSet('service_template', name=_('From Template')),
|
|
FieldSet('service_template', name=_('From Template')),
|
|
|
FieldSet('name', 'protocol', 'ports', name=_('Custom')),
|
|
FieldSet('name', 'protocol', 'ports', name=_('Custom')),
|
|
@@ -905,7 +833,7 @@ class ServiceCreateForm(ServiceForm):
|
|
|
class Meta(ServiceForm.Meta):
|
|
class Meta(ServiceForm.Meta):
|
|
|
fields = [
|
|
fields = [
|
|
|
'service_template', 'name', 'protocol', 'ports', 'ipaddresses', 'description',
|
|
'service_template', 'name', 'protocol', 'ports', 'ipaddresses', 'description',
|
|
|
- 'comments', 'tags', 'parent_object_type',
|
|
|
|
|
|
|
+ 'comments', 'tags',
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
def __init__(self, *args, **kwargs):
|