mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Merge pull request #8504 from netbox-community/8488-plugins-forms
Closes #8488: Support form components for plugins
This commit is contained in:
commit
4347f624d8
78
docs/plugins/development/forms.md
Normal file
78
docs/plugins/development/forms.md
Normal file
@ -0,0 +1,78 @@
|
||||
# Forms
|
||||
|
||||
## Form Classes
|
||||
|
||||
NetBox provides several base form classes for use by plugins. These are documented below.
|
||||
|
||||
* `NetBoxModelForm`
|
||||
* `NetBoxModelCSVForm`
|
||||
* `NetBoxModelBulkEditForm`
|
||||
* `NetBoxModelFilterSetForm`
|
||||
|
||||
### TODO: Include forms reference
|
||||
|
||||
In addition to the [form fields provided by Django](https://docs.djangoproject.com/en/stable/ref/forms/fields/), NetBox provides several field classes for use within forms to handle specific types of data. These can be imported from `utilities.forms.fields` and are documented below.
|
||||
|
||||
## General Purpose Fields
|
||||
|
||||
::: utilities.forms.ColorField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.CommentField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.JSONField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.MACAddressField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.SlugField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
## Dynamic Object Fields
|
||||
|
||||
::: utilities.forms.DynamicModelChoiceField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.DynamicModelMultipleChoiceField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
## Content Type Fields
|
||||
|
||||
::: utilities.forms.ContentTypeChoiceField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.ContentTypeMultipleChoiceField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
## CSV Import Fields
|
||||
|
||||
::: utilities.forms.CSVChoiceField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.CSVMultipleChoiceField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.CSVModelChoiceField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.CSVContentTypeField
|
||||
selection:
|
||||
members: false
|
||||
|
||||
::: utilities.forms.CSVMultipleContentTypeField
|
||||
selection:
|
||||
members: false
|
@ -105,6 +105,7 @@ nav:
|
||||
- Models: 'plugins/development/models.md'
|
||||
- Views: 'plugins/development/views.md'
|
||||
- Tables: 'plugins/development/tables.md'
|
||||
- Forms: 'plugins/development/forms.md'
|
||||
- Filter Sets: 'plugins/development/filtersets.md'
|
||||
- REST API: 'plugins/development/rest-api.md'
|
||||
- Background Tasks: 'plugins/development/background-tasks.md'
|
||||
|
@ -2,7 +2,7 @@ from django import forms
|
||||
|
||||
from circuits.choices import CircuitStatusChoices
|
||||
from circuits.models import *
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import add_blank_choice, CommentField, DynamicModelChoiceField, SmallTextarea, StaticSelect
|
||||
|
||||
@ -14,7 +14,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ProviderBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ProviderBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -47,13 +47,12 @@ class ProviderBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
|
||||
]
|
||||
nullable_fields = (
|
||||
'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
|
||||
)
|
||||
|
||||
|
||||
class ProviderNetworkBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -75,13 +74,12 @@ class ProviderNetworkBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditFor
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'service_id', 'description', 'comments',
|
||||
]
|
||||
nullable_fields = (
|
||||
'service_id', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
class CircuitTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=CircuitType.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -91,11 +89,10 @@ class CircuitTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['description']
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class CircuitBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Circuit.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -131,7 +128,6 @@ class CircuitBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'tenant', 'commit_rate', 'description', 'comments',
|
||||
]
|
||||
nullable_fields = (
|
||||
'tenant', 'commit_rate', 'description', 'comments',
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
from circuits.choices import CircuitStatusChoices
|
||||
from circuits.models import *
|
||||
from extras.forms import CustomFieldModelCSVForm
|
||||
from netbox.forms import NetBoxModelCSVForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
|
||||
|
||||
@ -12,7 +12,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ProviderCSVForm(CustomFieldModelCSVForm):
|
||||
class ProviderCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@ -22,7 +22,7 @@ class ProviderCSVForm(CustomFieldModelCSVForm):
|
||||
)
|
||||
|
||||
|
||||
class ProviderNetworkCSVForm(CustomFieldModelCSVForm):
|
||||
class ProviderNetworkCSVForm(NetBoxModelCSVForm):
|
||||
provider = CSVModelChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -36,7 +36,7 @@ class ProviderNetworkCSVForm(CustomFieldModelCSVForm):
|
||||
]
|
||||
|
||||
|
||||
class CircuitTypeCSVForm(CustomFieldModelCSVForm):
|
||||
class CircuitTypeCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@ -47,7 +47,7 @@ class CircuitTypeCSVForm(CustomFieldModelCSVForm):
|
||||
}
|
||||
|
||||
|
||||
class CircuitCSVForm(CustomFieldModelCSVForm):
|
||||
class CircuitCSVForm(NetBoxModelCSVForm):
|
||||
provider = CSVModelChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
to_field_name='name',
|
||||
|
@ -4,7 +4,7 @@ from django.utils.translation import gettext as _
|
||||
from circuits.choices import CircuitStatusChoices
|
||||
from circuits.models import *
|
||||
from dcim.models import Region, Site, SiteGroup
|
||||
from extras.forms import CustomFieldModelFilterForm
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import TenancyFilterForm
|
||||
from utilities.forms import DynamicModelMultipleChoiceField, StaticSelectMultiple, TagFilterField
|
||||
|
||||
@ -16,13 +16,13 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ProviderFilterForm(CustomFieldModelFilterForm):
|
||||
class ProviderFilterForm(NetBoxModelFilterSetForm):
|
||||
model = Provider
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['region_id', 'site_group_id', 'site_id'],
|
||||
['asn'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
('ASN', ('asn',)),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -49,11 +49,11 @@ class ProviderFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ProviderNetworkFilterForm(CustomFieldModelFilterForm):
|
||||
class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ProviderNetwork
|
||||
field_groups = (
|
||||
('q', 'tag'),
|
||||
('provider_id',),
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('provider_id', 'service_id')),
|
||||
)
|
||||
provider_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
@ -67,20 +67,20 @@ class ProviderNetworkFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class CircuitTypeFilterForm(CustomFieldModelFilterForm):
|
||||
class CircuitTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = CircuitType
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class CircuitFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class CircuitFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Circuit
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['provider_id', 'provider_network_id'],
|
||||
['type_id', 'status', 'commit_rate'],
|
||||
['region_id', 'site_group_id', 'site_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Provider', ('provider_id', 'provider_network_id')),
|
||||
('Attributes', ('type_id', 'status', 'commit_rate')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
type_id = DynamicModelMultipleChoiceField(
|
||||
queryset=CircuitType.objects.all(),
|
||||
required=False,
|
||||
|
@ -2,8 +2,8 @@ from django import forms
|
||||
|
||||
from circuits.models import *
|
||||
from dcim.models import Region, Site, SiteGroup
|
||||
from extras.forms import CustomFieldModelForm
|
||||
from extras.models import Tag
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import (
|
||||
BootstrapMixin, CommentField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
@ -19,7 +19,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ProviderForm(CustomFieldModelForm):
|
||||
class ProviderForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
comments = CommentField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
@ -27,15 +27,16 @@ class ProviderForm(CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Provider', ('name', 'slug', 'asn', 'tags')),
|
||||
('Support Info', ('account', 'portal_url', 'noc_contact', 'admin_contact')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = [
|
||||
'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Provider', ('name', 'slug', 'asn', 'tags')),
|
||||
('Support Info', ('account', 'portal_url', 'noc_contact', 'admin_contact')),
|
||||
)
|
||||
widgets = {
|
||||
'noc_contact': SmallTextarea(
|
||||
attrs={'rows': 5}
|
||||
@ -53,7 +54,7 @@ class ProviderForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ProviderNetworkForm(CustomFieldModelForm):
|
||||
class ProviderNetworkForm(NetBoxModelForm):
|
||||
provider = DynamicModelChoiceField(
|
||||
queryset=Provider.objects.all()
|
||||
)
|
||||
@ -63,17 +64,18 @@ class ProviderNetworkForm(CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Provider Network', ('provider', 'name', 'service_id', 'description', 'tags')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ProviderNetwork
|
||||
fields = [
|
||||
'provider', 'name', 'service_id', 'description', 'comments', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Provider Network', ('provider', 'name', 'service_id', 'description', 'tags')),
|
||||
)
|
||||
|
||||
|
||||
class CircuitTypeForm(CustomFieldModelForm):
|
||||
class CircuitTypeForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -87,7 +89,7 @@ class CircuitTypeForm(CustomFieldModelForm):
|
||||
]
|
||||
|
||||
|
||||
class CircuitForm(TenancyForm, CustomFieldModelForm):
|
||||
class CircuitForm(TenancyForm, NetBoxModelForm):
|
||||
provider = DynamicModelChoiceField(
|
||||
queryset=Provider.objects.all()
|
||||
)
|
||||
@ -100,16 +102,17 @@ class CircuitForm(TenancyForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Circuit', ('provider', 'cid', 'type', 'status', 'install_date', 'commit_rate', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Circuit
|
||||
fields = [
|
||||
'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant',
|
||||
'comments', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Circuit', ('provider', 'cid', 'type', 'status', 'install_date', 'commit_rate', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
help_texts = {
|
||||
'cid': "Unique circuit ID",
|
||||
'commit_rate': "Committed rate",
|
||||
|
@ -6,8 +6,8 @@ from timezone_field import TimeZoneFormField
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import *
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||
from ipam.models import ASN, VLAN, VRF
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, CommentField, DynamicModelChoiceField,
|
||||
@ -57,7 +57,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class RegionBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class RegionBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -71,11 +71,10 @@ class RegionBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['parent', 'description']
|
||||
nullable_fields = ('parent', 'description')
|
||||
|
||||
|
||||
class SiteGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class SiteGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -89,11 +88,10 @@ class SiteGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['parent', 'description']
|
||||
nullable_fields = ('parent', 'description')
|
||||
|
||||
|
||||
class SiteBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class SiteBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -131,13 +129,12 @@ class SiteBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
widget=StaticSelect()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'region', 'group', 'tenant', 'asns', 'description', 'time_zone',
|
||||
]
|
||||
nullable_fields = (
|
||||
'region', 'group', 'tenant', 'asns', 'description', 'time_zone',
|
||||
)
|
||||
|
||||
|
||||
class LocationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class LocationBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Location.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -162,11 +159,10 @@ class LocationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['parent', 'tenant', 'description']
|
||||
nullable_fields = ('parent', 'tenant', 'description')
|
||||
|
||||
|
||||
class RackRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class RackRoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=RackRole.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -179,11 +175,10 @@ class RackRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['color', 'description']
|
||||
nullable_fields = ('color', 'description')
|
||||
|
||||
|
||||
class RackBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class RackBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -277,13 +272,12 @@ class RackBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
|
||||
]
|
||||
nullable_fields = (
|
||||
'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
|
||||
)
|
||||
|
||||
|
||||
class RackReservationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=RackReservation.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -304,11 +298,8 @@ class RackReservationBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditFor
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = []
|
||||
|
||||
|
||||
class ManufacturerBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ManufacturerBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -318,11 +309,10 @@ class ManufacturerBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['description']
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class DeviceTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=DeviceType.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -349,11 +339,10 @@ class DeviceTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
widget=StaticSelect()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['part_number', 'airflow']
|
||||
nullable_fields = ('part_number', 'airflow')
|
||||
|
||||
|
||||
class ModuleTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ModuleType.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -366,11 +355,10 @@ class ModuleTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['part_number']
|
||||
nullable_fields = ('part_number',)
|
||||
|
||||
|
||||
class DeviceRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=DeviceRole.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -388,11 +376,10 @@ class DeviceRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['color', 'description']
|
||||
nullable_fields = ('color', 'description')
|
||||
|
||||
|
||||
class PlatformBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class PlatformBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Platform.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -411,11 +398,10 @@ class PlatformBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['manufacturer', 'napalm_driver', 'description']
|
||||
nullable_fields = ('manufacturer', 'napalm_driver', 'description')
|
||||
|
||||
|
||||
class DeviceBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class DeviceBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -470,13 +456,12 @@ class DeviceBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
label='Serial Number'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'tenant', 'platform', 'serial', 'airflow',
|
||||
]
|
||||
nullable_fields = (
|
||||
'tenant', 'platform', 'serial', 'airflow',
|
||||
)
|
||||
|
||||
|
||||
class ModuleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ModuleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Module.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -498,11 +483,10 @@ class ModuleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
label='Serial Number'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['serial']
|
||||
nullable_fields = ('serial',)
|
||||
|
||||
|
||||
class CableBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class CableBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Cable.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -541,10 +525,9 @@ class CableBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
widget=StaticSelect()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'type', 'status', 'tenant', 'label', 'color', 'length',
|
||||
]
|
||||
nullable_fields = (
|
||||
'type', 'status', 'tenant', 'label', 'color', 'length',
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
@ -558,7 +541,7 @@ class CableBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
})
|
||||
|
||||
|
||||
class VirtualChassisBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=VirtualChassis.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -568,11 +551,10 @@ class VirtualChassisBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['domain']
|
||||
nullable_fields = ('domain',)
|
||||
|
||||
|
||||
class PowerPanelBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=PowerPanel.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -607,11 +589,10 @@ class PowerPanelBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
}
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['location']
|
||||
nullable_fields = ('location',)
|
||||
|
||||
|
||||
class PowerFeedBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=PowerFeed.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -666,10 +647,7 @@ class PowerFeedBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'location', 'comments',
|
||||
]
|
||||
nullable_fields = ('location', 'comments')
|
||||
|
||||
|
||||
#
|
||||
@ -691,8 +669,7 @@ class ConsolePortTemplateBulkEditForm(BulkEditForm):
|
||||
widget=StaticSelect()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ('label', 'type', 'description')
|
||||
nullable_fields = ('label', 'type', 'description')
|
||||
|
||||
|
||||
class ConsoleServerPortTemplateBulkEditForm(BulkEditForm):
|
||||
@ -713,8 +690,7 @@ class ConsoleServerPortTemplateBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ('label', 'type', 'description')
|
||||
nullable_fields = ('label', 'type', 'description')
|
||||
|
||||
|
||||
class PowerPortTemplateBulkEditForm(BulkEditForm):
|
||||
@ -745,8 +721,7 @@ class PowerPortTemplateBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ('label', 'type', 'maximum_draw', 'allocated_draw', 'description')
|
||||
nullable_fields = ('label', 'type', 'maximum_draw', 'allocated_draw', 'description')
|
||||
|
||||
|
||||
class PowerOutletTemplateBulkEditForm(BulkEditForm):
|
||||
@ -782,8 +757,7 @@ class PowerOutletTemplateBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ('label', 'type', 'power_port', 'feed_leg', 'description')
|
||||
nullable_fields = ('label', 'type', 'power_port', 'feed_leg', 'description')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -820,8 +794,7 @@ class InterfaceTemplateBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ('label', 'description')
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
||||
class FrontPortTemplateBulkEditForm(BulkEditForm):
|
||||
@ -845,8 +818,7 @@ class FrontPortTemplateBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ('description',)
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class RearPortTemplateBulkEditForm(BulkEditForm):
|
||||
@ -870,8 +842,7 @@ class RearPortTemplateBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ('description',)
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class ModuleBayTemplateBulkEditForm(BulkEditForm):
|
||||
@ -887,8 +858,7 @@ class ModuleBayTemplateBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ('label', 'position', 'description')
|
||||
nullable_fields = ('label', 'position', 'description')
|
||||
|
||||
|
||||
class DeviceBayTemplateBulkEditForm(BulkEditForm):
|
||||
@ -904,8 +874,7 @@ class DeviceBayTemplateBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ('label', 'description')
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
||||
class InventoryItemTemplateBulkEditForm(BulkEditForm):
|
||||
@ -929,8 +898,7 @@ class InventoryItemTemplateBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'role', 'manufacturer', 'part_id', 'description']
|
||||
nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
|
||||
|
||||
|
||||
#
|
||||
@ -939,8 +907,7 @@ class InventoryItemTemplateBulkEditForm(BulkEditForm):
|
||||
|
||||
class ConsolePortBulkEditForm(
|
||||
form_from_model(ConsolePort, ['label', 'type', 'speed', 'mark_connected', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ConsolePort.objects.all(),
|
||||
@ -951,14 +918,12 @@ class ConsolePortBulkEditForm(
|
||||
widget=BulkEditNullBooleanSelect
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'description']
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
||||
class ConsoleServerPortBulkEditForm(
|
||||
form_from_model(ConsoleServerPort, ['label', 'type', 'speed', 'mark_connected', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ConsoleServerPort.objects.all(),
|
||||
@ -969,14 +934,12 @@ class ConsoleServerPortBulkEditForm(
|
||||
widget=BulkEditNullBooleanSelect
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'description']
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
||||
class PowerPortBulkEditForm(
|
||||
form_from_model(PowerPort, ['label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=PowerPort.objects.all(),
|
||||
@ -987,14 +950,12 @@ class PowerPortBulkEditForm(
|
||||
widget=BulkEditNullBooleanSelect
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'description']
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
||||
class PowerOutletBulkEditForm(
|
||||
form_from_model(PowerOutlet, ['label', 'type', 'feed_leg', 'power_port', 'mark_connected', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=PowerOutlet.objects.all(),
|
||||
@ -1011,8 +972,7 @@ class PowerOutletBulkEditForm(
|
||||
widget=BulkEditNullBooleanSelect
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'type', 'feed_leg', 'power_port', 'description']
|
||||
nullable_fields = ('label', 'type', 'feed_leg', 'power_port', 'description')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -1031,8 +991,7 @@ class InterfaceBulkEditForm(
|
||||
'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected',
|
||||
'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
|
||||
]),
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
@ -1092,11 +1051,10 @@ class InterfaceBulkEditForm(
|
||||
label='VRF'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'description', 'mode', 'rf_channel',
|
||||
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'vrf',
|
||||
]
|
||||
nullable_fields = (
|
||||
'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'description', 'mode',
|
||||
'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'vrf',
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -1154,64 +1112,55 @@ class InterfaceBulkEditForm(
|
||||
|
||||
class FrontPortBulkEditForm(
|
||||
form_from_model(FrontPort, ['label', 'type', 'color', 'mark_connected', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=FrontPort.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'description']
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
||||
class RearPortBulkEditForm(
|
||||
form_from_model(RearPort, ['label', 'type', 'color', 'mark_connected', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=RearPort.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'description']
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
||||
class ModuleBayBulkEditForm(
|
||||
form_from_model(DeviceBay, ['label', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ModuleBay.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'position', 'description']
|
||||
nullable_fields = ('label', 'position', 'description')
|
||||
|
||||
|
||||
class DeviceBayBulkEditForm(
|
||||
form_from_model(DeviceBay, ['label', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=DeviceBay.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'description']
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
||||
class InventoryItemBulkEditForm(
|
||||
form_from_model(InventoryItem, ['label', 'role', 'manufacturer', 'part_id', 'description']),
|
||||
AddRemoveTagsForm,
|
||||
CustomFieldModelBulkEditForm
|
||||
NetBoxModelBulkEditForm
|
||||
):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=InventoryItem.objects.all(),
|
||||
@ -1226,15 +1175,14 @@ class InventoryItemBulkEditForm(
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'role', 'manufacturer', 'part_id', 'description']
|
||||
nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
|
||||
|
||||
|
||||
#
|
||||
# Device component roles
|
||||
#
|
||||
|
||||
class InventoryItemRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class InventoryItemRoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=InventoryItemRole.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -1247,5 +1195,4 @@ class InventoryItemRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditF
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['color', 'description']
|
||||
nullable_fields = ('color', 'description')
|
||||
|
@ -7,8 +7,8 @@ from django.utils.safestring import mark_safe
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import *
|
||||
from extras.forms import CustomFieldModelCSVForm
|
||||
from ipam.models import VRF
|
||||
from netbox.forms import NetBoxModelCSVForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVTypedChoiceField, SlugField
|
||||
from virtualization.models import Cluster
|
||||
@ -46,7 +46,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class RegionCSVForm(CustomFieldModelCSVForm):
|
||||
class RegionCSVForm(NetBoxModelCSVForm):
|
||||
parent = CSVModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -59,7 +59,7 @@ class RegionCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'slug', 'parent', 'description')
|
||||
|
||||
|
||||
class SiteGroupCSVForm(CustomFieldModelCSVForm):
|
||||
class SiteGroupCSVForm(NetBoxModelCSVForm):
|
||||
parent = CSVModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False,
|
||||
@ -72,7 +72,7 @@ class SiteGroupCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'slug', 'parent', 'description')
|
||||
|
||||
|
||||
class SiteCSVForm(CustomFieldModelCSVForm):
|
||||
class SiteCSVForm(NetBoxModelCSVForm):
|
||||
status = CSVChoiceField(
|
||||
choices=SiteStatusChoices,
|
||||
help_text='Operational status'
|
||||
@ -109,7 +109,7 @@ class SiteCSVForm(CustomFieldModelCSVForm):
|
||||
}
|
||||
|
||||
|
||||
class LocationCSVForm(CustomFieldModelCSVForm):
|
||||
class LocationCSVForm(NetBoxModelCSVForm):
|
||||
site = CSVModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -136,7 +136,7 @@ class LocationCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('site', 'parent', 'name', 'slug', 'tenant', 'description')
|
||||
|
||||
|
||||
class RackRoleCSVForm(CustomFieldModelCSVForm):
|
||||
class RackRoleCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@ -147,7 +147,7 @@ class RackRoleCSVForm(CustomFieldModelCSVForm):
|
||||
}
|
||||
|
||||
|
||||
class RackCSVForm(CustomFieldModelCSVForm):
|
||||
class RackCSVForm(NetBoxModelCSVForm):
|
||||
site = CSVModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -205,7 +205,7 @@ class RackCSVForm(CustomFieldModelCSVForm):
|
||||
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
|
||||
|
||||
|
||||
class RackReservationCSVForm(CustomFieldModelCSVForm):
|
||||
class RackReservationCSVForm(NetBoxModelCSVForm):
|
||||
site = CSVModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -255,14 +255,14 @@ class RackReservationCSVForm(CustomFieldModelCSVForm):
|
||||
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
||||
|
||||
|
||||
class ManufacturerCSVForm(CustomFieldModelCSVForm):
|
||||
class ManufacturerCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = Manufacturer
|
||||
fields = ('name', 'slug', 'description')
|
||||
|
||||
|
||||
class DeviceRoleCSVForm(CustomFieldModelCSVForm):
|
||||
class DeviceRoleCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@ -273,7 +273,7 @@ class DeviceRoleCSVForm(CustomFieldModelCSVForm):
|
||||
}
|
||||
|
||||
|
||||
class PlatformCSVForm(CustomFieldModelCSVForm):
|
||||
class PlatformCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
manufacturer = CSVModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
@ -287,7 +287,7 @@ class PlatformCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description')
|
||||
|
||||
|
||||
class BaseDeviceCSVForm(CustomFieldModelCSVForm):
|
||||
class BaseDeviceCSVForm(NetBoxModelCSVForm):
|
||||
device_role = CSVModelChoiceField(
|
||||
queryset=DeviceRole.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -403,7 +403,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
|
||||
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
||||
|
||||
|
||||
class ModuleCSVForm(CustomFieldModelCSVForm):
|
||||
class ModuleCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -478,7 +478,7 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm):
|
||||
# Device components
|
||||
#
|
||||
|
||||
class ConsolePortCSVForm(CustomFieldModelCSVForm):
|
||||
class ConsolePortCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -501,7 +501,7 @@ class ConsolePortCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description')
|
||||
|
||||
|
||||
class ConsoleServerPortCSVForm(CustomFieldModelCSVForm):
|
||||
class ConsoleServerPortCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -524,7 +524,7 @@ class ConsoleServerPortCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description')
|
||||
|
||||
|
||||
class PowerPortCSVForm(CustomFieldModelCSVForm):
|
||||
class PowerPortCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -542,7 +542,7 @@ class PowerPortCSVForm(CustomFieldModelCSVForm):
|
||||
)
|
||||
|
||||
|
||||
class PowerOutletCSVForm(CustomFieldModelCSVForm):
|
||||
class PowerOutletCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -591,7 +591,7 @@ class PowerOutletCSVForm(CustomFieldModelCSVForm):
|
||||
self.fields['power_port'].queryset = PowerPort.objects.none()
|
||||
|
||||
|
||||
class InterfaceCSVForm(CustomFieldModelCSVForm):
|
||||
class InterfaceCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -655,7 +655,7 @@ class InterfaceCSVForm(CustomFieldModelCSVForm):
|
||||
return self.cleaned_data['enabled']
|
||||
|
||||
|
||||
class FrontPortCSVForm(CustomFieldModelCSVForm):
|
||||
class FrontPortCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -703,7 +703,7 @@ class FrontPortCSVForm(CustomFieldModelCSVForm):
|
||||
self.fields['rear_port'].queryset = RearPort.objects.none()
|
||||
|
||||
|
||||
class RearPortCSVForm(CustomFieldModelCSVForm):
|
||||
class RearPortCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -721,7 +721,7 @@ class RearPortCSVForm(CustomFieldModelCSVForm):
|
||||
}
|
||||
|
||||
|
||||
class ModuleBayCSVForm(CustomFieldModelCSVForm):
|
||||
class ModuleBayCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -732,7 +732,7 @@ class ModuleBayCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('device', 'name', 'label', 'position', 'description')
|
||||
|
||||
|
||||
class DeviceBayCSVForm(CustomFieldModelCSVForm):
|
||||
class DeviceBayCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -778,7 +778,7 @@ class DeviceBayCSVForm(CustomFieldModelCSVForm):
|
||||
self.fields['installed_device'].queryset = Interface.objects.none()
|
||||
|
||||
|
||||
class InventoryItemCSVForm(CustomFieldModelCSVForm):
|
||||
class InventoryItemCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name'
|
||||
@ -827,7 +827,7 @@ class InventoryItemCSVForm(CustomFieldModelCSVForm):
|
||||
# Device component roles
|
||||
#
|
||||
|
||||
class InventoryItemRoleCSVForm(CustomFieldModelCSVForm):
|
||||
class InventoryItemRoleCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@ -842,7 +842,7 @@ class InventoryItemRoleCSVForm(CustomFieldModelCSVForm):
|
||||
# Cables
|
||||
#
|
||||
|
||||
class CableCSVForm(CustomFieldModelCSVForm):
|
||||
class CableCSVForm(NetBoxModelCSVForm):
|
||||
# Termination A
|
||||
side_a_device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
@ -947,7 +947,7 @@ class CableCSVForm(CustomFieldModelCSVForm):
|
||||
# Virtual chassis
|
||||
#
|
||||
|
||||
class VirtualChassisCSVForm(CustomFieldModelCSVForm):
|
||||
class VirtualChassisCSVForm(NetBoxModelCSVForm):
|
||||
master = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -964,7 +964,7 @@ class VirtualChassisCSVForm(CustomFieldModelCSVForm):
|
||||
# Power
|
||||
#
|
||||
|
||||
class PowerPanelCSVForm(CustomFieldModelCSVForm):
|
||||
class PowerPanelCSVForm(NetBoxModelCSVForm):
|
||||
site = CSVModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -990,7 +990,7 @@ class PowerPanelCSVForm(CustomFieldModelCSVForm):
|
||||
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
|
||||
|
||||
|
||||
class PowerFeedCSVForm(CustomFieldModelCSVForm):
|
||||
class PowerFeedCSVForm(NetBoxModelCSVForm):
|
||||
site = CSVModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='name',
|
||||
|
@ -1,7 +1,7 @@
|
||||
from circuits.models import Circuit, CircuitTermination, Provider
|
||||
from dcim.models import *
|
||||
from extras.forms import CustomFieldModelForm
|
||||
from extras.models import Tag
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect
|
||||
|
||||
@ -18,7 +18,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ConnectCableToDeviceForm(TenancyForm, CustomFieldModelForm):
|
||||
class ConnectCableToDeviceForm(TenancyForm, NetBoxModelForm):
|
||||
"""
|
||||
Base form for connecting a Cable to a Device component
|
||||
"""
|
||||
@ -171,7 +171,7 @@ class ConnectCableToRearPortForm(ConnectCableToDeviceForm):
|
||||
)
|
||||
|
||||
|
||||
class ConnectCableToCircuitTerminationForm(TenancyForm, CustomFieldModelForm):
|
||||
class ConnectCableToCircuitTerminationForm(TenancyForm, NetBoxModelForm):
|
||||
termination_b_provider = DynamicModelChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
label='Provider',
|
||||
@ -229,7 +229,7 @@ class ConnectCableToCircuitTerminationForm(TenancyForm, CustomFieldModelForm):
|
||||
return getattr(self.cleaned_data['termination_b_id'], 'pk', None)
|
||||
|
||||
|
||||
class ConnectCableToPowerFeedForm(TenancyForm, CustomFieldModelForm):
|
||||
class ConnectCableToPowerFeedForm(TenancyForm, NetBoxModelForm):
|
||||
termination_b_region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
label='Region',
|
||||
|
@ -5,8 +5,9 @@ from django.utils.translation import gettext as _
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import *
|
||||
from extras.forms import CustomFieldModelFilterForm, LocalConfigContextFilterForm
|
||||
from extras.forms import LocalConfigContextFilterForm
|
||||
from ipam.models import ASN, VRF
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import TenancyFilterForm
|
||||
from utilities.forms import (
|
||||
APISelectMultiple, add_blank_choice, ColorField, DynamicModelMultipleChoiceField, FilterForm, StaticSelect,
|
||||
@ -52,7 +53,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class DeviceComponentFilterForm(CustomFieldModelFilterForm):
|
||||
class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
|
||||
name = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
@ -103,7 +104,7 @@ class DeviceComponentFilterForm(CustomFieldModelFilterForm):
|
||||
)
|
||||
|
||||
|
||||
class RegionFilterForm(CustomFieldModelFilterForm):
|
||||
class RegionFilterForm(NetBoxModelFilterSetForm):
|
||||
model = Region
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
@ -113,7 +114,7 @@ class RegionFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class SiteGroupFilterForm(CustomFieldModelFilterForm):
|
||||
class SiteGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
model = SiteGroup
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
@ -123,14 +124,13 @@ class SiteGroupFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class SiteFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class SiteFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Site
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['status', 'region_id', 'group_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
['asn_id']
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('status', 'region_id', 'group_id', 'asn_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
choices=SiteStatusChoices,
|
||||
required=False,
|
||||
@ -154,13 +154,13 @@ class SiteFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class LocationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class LocationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Location
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['region_id', 'site_group_id', 'site_id', 'parent_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Parent', ('region_id', 'site_group_id', 'site_id', 'parent_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -192,20 +192,20 @@ class LocationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class RackRoleFilterForm(CustomFieldModelFilterForm):
|
||||
class RackRoleFilterForm(NetBoxModelFilterSetForm):
|
||||
model = RackRole
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class RackFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class RackFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Rack
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['region_id', 'site_id', 'location_id'],
|
||||
['status', 'role_id'],
|
||||
['type', 'width', 'serial', 'asset_tag'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Location', ('region_id', 'site_id', 'location_id')),
|
||||
('Function', ('status', 'role_id')),
|
||||
('Hardware', ('type', 'width', 'serial', 'asset_tag')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -270,14 +270,14 @@ class RackElevationFilterForm(RackFilterForm):
|
||||
)
|
||||
|
||||
|
||||
class RackReservationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = RackReservation
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['user_id'],
|
||||
['region_id', 'site_id', 'location_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('User', ('user_id',)),
|
||||
('Rack', ('region_id', 'site_id', 'location_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -308,18 +308,21 @@ class RackReservationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ManufacturerFilterForm(CustomFieldModelFilterForm):
|
||||
class ManufacturerFilterForm(NetBoxModelFilterSetForm):
|
||||
model = Manufacturer
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class DeviceTypeFilterForm(CustomFieldModelFilterForm):
|
||||
class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = DeviceType
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['manufacturer_id', 'part_number', 'subdevice_role', 'airflow'],
|
||||
['console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Hardware', ('manufacturer_id', 'part_number', 'subdevice_role', 'airflow')),
|
||||
('Components', (
|
||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||
'pass_through_ports',
|
||||
)),
|
||||
)
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False,
|
||||
@ -383,13 +386,16 @@ class DeviceTypeFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ModuleTypeFilterForm(CustomFieldModelFilterForm):
|
||||
class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ModuleType
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['manufacturer_id', 'part_number'],
|
||||
['console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Hardware', ('manufacturer_id', 'part_number')),
|
||||
('Components', (
|
||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||
'pass_through_ports',
|
||||
)),
|
||||
)
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False,
|
||||
@ -444,12 +450,12 @@ class ModuleTypeFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class DeviceRoleFilterForm(CustomFieldModelFilterForm):
|
||||
class DeviceRoleFilterForm(NetBoxModelFilterSetForm):
|
||||
model = DeviceRole
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class PlatformFilterForm(CustomFieldModelFilterForm):
|
||||
class PlatformFilterForm(NetBoxModelFilterSetForm):
|
||||
model = Platform
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
@ -459,19 +465,19 @@ class PlatformFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Device
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id'],
|
||||
['status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address'],
|
||||
['manufacturer_id', 'device_type_id', 'platform_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
[
|
||||
'has_primary_ip', 'virtual_chassis_member', 'console_ports', 'console_server_ports', 'power_ports',
|
||||
'power_outlets', 'interfaces', 'pass_through_ports', 'local_context_data',
|
||||
],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
('Operation', ('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address')),
|
||||
('Hardware', ('manufacturer_id', 'device_type_id', 'platform_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
('Components', (
|
||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
|
||||
)),
|
||||
('Miscellaneous', ('has_primary_ip', 'virtual_chassis_member', 'local_context_data'))
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -613,13 +619,12 @@ class DeviceFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFi
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Module
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['manufacturer_id', 'module_type_id'],
|
||||
['serial', 'asset_tag'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Hardware', ('manufacturer_id', 'module_type_id', 'serial', 'asset_tag')),
|
||||
)
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False,
|
||||
@ -644,13 +649,13 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFi
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VirtualChassisFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VirtualChassis
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['region_id', 'site_group_id', 'site_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -673,14 +678,14 @@ class VirtualChassisFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Cable
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['site_id', 'rack_id', 'device_id'],
|
||||
['type', 'status', 'color', 'length', 'length_unit'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Location', ('site_id', 'rack_id', 'device_id')),
|
||||
('Attributes', ('type', 'status', 'color', 'length', 'length_unit')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -736,11 +741,11 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class PowerPanelFilterForm(CustomFieldModelFilterForm):
|
||||
class PowerPanelFilterForm(NetBoxModelFilterSetForm):
|
||||
model = PowerPanel
|
||||
field_groups = (
|
||||
('q', 'tag'),
|
||||
('region_id', 'site_group_id', 'site_id', 'location_id')
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id'))
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
@ -773,14 +778,13 @@ class PowerPanelFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class PowerFeedFilterForm(CustomFieldModelFilterForm):
|
||||
class PowerFeedFilterForm(NetBoxModelFilterSetForm):
|
||||
model = PowerFeed
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['region_id', 'site_group_id', 'site_id'],
|
||||
['power_panel_id', 'rack_id'],
|
||||
['status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')),
|
||||
('Attributes', ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -855,11 +859,11 @@ class PowerFeedFilterForm(CustomFieldModelFilterForm):
|
||||
|
||||
class ConsolePortFilterForm(DeviceComponentFilterForm):
|
||||
model = ConsolePort
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label', 'type', 'speed'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type', 'speed')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
choices=ConsolePortTypeChoices,
|
||||
required=False,
|
||||
@ -875,11 +879,11 @@ class ConsolePortFilterForm(DeviceComponentFilterForm):
|
||||
|
||||
class ConsoleServerPortFilterForm(DeviceComponentFilterForm):
|
||||
model = ConsoleServerPort
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label', 'type', 'speed'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type', 'speed')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
choices=ConsolePortTypeChoices,
|
||||
required=False,
|
||||
@ -895,11 +899,11 @@ class ConsoleServerPortFilterForm(DeviceComponentFilterForm):
|
||||
|
||||
class PowerPortFilterForm(DeviceComponentFilterForm):
|
||||
model = PowerPort
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label', 'type'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
choices=PowerPortTypeChoices,
|
||||
required=False,
|
||||
@ -910,11 +914,11 @@ class PowerPortFilterForm(DeviceComponentFilterForm):
|
||||
|
||||
class PowerOutletFilterForm(DeviceComponentFilterForm):
|
||||
model = PowerOutlet
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label', 'type'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
choices=PowerOutletTypeChoices,
|
||||
required=False,
|
||||
@ -925,13 +929,13 @@ class PowerOutletFilterForm(DeviceComponentFilterForm):
|
||||
|
||||
class InterfaceFilterForm(DeviceComponentFilterForm):
|
||||
model = Interface
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only'],
|
||||
['vrf_id', 'mac_address', 'wwn'],
|
||||
['rf_role', 'rf_channel', 'rf_channel_width', 'tx_power'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')),
|
||||
('Addressing', ('vrf_id', 'mac_address', 'wwn')),
|
||||
('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
kind = forms.MultipleChoiceField(
|
||||
choices=InterfaceKindChoices,
|
||||
required=False,
|
||||
@ -1008,11 +1012,11 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
|
||||
|
||||
|
||||
class FrontPortFilterForm(DeviceComponentFilterForm):
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label', 'type', 'color'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type', 'color')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
model = FrontPort
|
||||
type = forms.MultipleChoiceField(
|
||||
choices=PortTypeChoices,
|
||||
@ -1027,11 +1031,11 @@ class FrontPortFilterForm(DeviceComponentFilterForm):
|
||||
|
||||
class RearPortFilterForm(DeviceComponentFilterForm):
|
||||
model = RearPort
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label', 'type', 'color'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type', 'color')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
choices=PortTypeChoices,
|
||||
required=False,
|
||||
@ -1045,11 +1049,11 @@ class RearPortFilterForm(DeviceComponentFilterForm):
|
||||
|
||||
class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||
model = ModuleBay
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label', 'position'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('name', 'label', 'position')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
position = forms.CharField(
|
||||
required=False
|
||||
@ -1058,21 +1062,21 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||
|
||||
class DeviceBayFilterForm(DeviceComponentFilterForm):
|
||||
model = DeviceBay
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('name', 'label')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class InventoryItemFilterForm(DeviceComponentFilterForm):
|
||||
model = InventoryItem
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['name', 'label', 'manufacturer_id', 'serial', 'asset_tag', 'discovered'],
|
||||
['region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('name', 'label', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
role_id = DynamicModelMultipleChoiceField(
|
||||
queryset=InventoryItemRole.objects.all(),
|
||||
required=False,
|
||||
@ -1103,7 +1107,7 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
|
||||
# Device component roles
|
||||
#
|
||||
|
||||
class InventoryItemRoleFilterForm(CustomFieldModelFilterForm):
|
||||
class InventoryItemRoleFilterForm(NetBoxModelFilterSetForm):
|
||||
model = InventoryItemRole
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
@ -7,9 +7,9 @@ from timezone_field import TimeZoneFormField
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import *
|
||||
from extras.forms import CustomFieldModelForm
|
||||
from extras.models import Tag
|
||||
from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VRF
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import (
|
||||
APISelect, add_blank_choice, BootstrapMixin, ClearableFileInput, CommentField, ContentTypeChoiceField,
|
||||
@ -72,7 +72,7 @@ Tagged (All): Implies all VLANs are available (w/optional untagged VLAN)
|
||||
"""
|
||||
|
||||
|
||||
class RegionForm(CustomFieldModelForm):
|
||||
class RegionForm(NetBoxModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False
|
||||
@ -90,7 +90,7 @@ class RegionForm(CustomFieldModelForm):
|
||||
)
|
||||
|
||||
|
||||
class SiteGroupForm(CustomFieldModelForm):
|
||||
class SiteGroupForm(NetBoxModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False
|
||||
@ -108,7 +108,7 @@ class SiteGroupForm(CustomFieldModelForm):
|
||||
)
|
||||
|
||||
|
||||
class SiteForm(TenancyForm, CustomFieldModelForm):
|
||||
class SiteForm(TenancyForm, NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False
|
||||
@ -134,19 +134,20 @@ class SiteForm(TenancyForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Site', (
|
||||
'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
|
||||
)),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
('Contact Info', ('physical_address', 'shipping_address', 'latitude', 'longitude')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Site
|
||||
fields = (
|
||||
'name', 'slug', 'status', 'region', 'group', 'tenant_group', 'tenant', 'facility', 'asns', 'time_zone',
|
||||
'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags',
|
||||
)
|
||||
fieldsets = (
|
||||
('Site', (
|
||||
'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
|
||||
)),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
('Contact Info', ('physical_address', 'shipping_address', 'latitude', 'longitude')),
|
||||
)
|
||||
widgets = {
|
||||
'physical_address': SmallTextarea(
|
||||
attrs={
|
||||
@ -173,7 +174,7 @@ class SiteForm(TenancyForm, CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class LocationForm(TenancyForm, CustomFieldModelForm):
|
||||
class LocationForm(TenancyForm, NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -208,20 +209,21 @@ class LocationForm(TenancyForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Location', (
|
||||
'region', 'site_group', 'site', 'parent', 'name', 'slug', 'description', 'tags',
|
||||
)),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Location
|
||||
fields = (
|
||||
'region', 'site_group', 'site', 'parent', 'name', 'slug', 'description', 'tenant_group', 'tenant', 'tags',
|
||||
)
|
||||
fieldsets = (
|
||||
('Location', (
|
||||
'region', 'site_group', 'site', 'parent', 'name', 'slug', 'description', 'tags',
|
||||
)),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
|
||||
class RackRoleForm(CustomFieldModelForm):
|
||||
class RackRoleForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -235,7 +237,7 @@ class RackRoleForm(CustomFieldModelForm):
|
||||
]
|
||||
|
||||
|
||||
class RackForm(TenancyForm, CustomFieldModelForm):
|
||||
class RackForm(TenancyForm, NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -295,7 +297,7 @@ class RackForm(TenancyForm, CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class RackReservationForm(TenancyForm, CustomFieldModelForm):
|
||||
class RackReservationForm(TenancyForm, NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -347,19 +349,20 @@ class RackReservationForm(TenancyForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Reservation', ('region', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'location', 'rack', 'units', 'user', 'tenant_group', 'tenant',
|
||||
'description', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Reservation', ('region', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
|
||||
class ManufacturerForm(CustomFieldModelForm):
|
||||
class ManufacturerForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -373,7 +376,7 @@ class ManufacturerForm(CustomFieldModelForm):
|
||||
]
|
||||
|
||||
|
||||
class DeviceTypeForm(CustomFieldModelForm):
|
||||
class DeviceTypeForm(NetBoxModelForm):
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
queryset=Manufacturer.objects.all()
|
||||
)
|
||||
@ -386,21 +389,22 @@ class DeviceTypeForm(CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Device Type', (
|
||||
'manufacturer', 'model', 'slug', 'part_number', 'tags',
|
||||
)),
|
||||
('Chassis', (
|
||||
'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
|
||||
)),
|
||||
('Images', ('front_image', 'rear_image')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = DeviceType
|
||||
fields = [
|
||||
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
|
||||
'front_image', 'rear_image', 'comments', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Device Type', (
|
||||
'manufacturer', 'model', 'slug', 'part_number', 'tags',
|
||||
)),
|
||||
('Chassis', (
|
||||
'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
|
||||
)),
|
||||
('Images', ('front_image', 'rear_image')),
|
||||
)
|
||||
widgets = {
|
||||
'subdevice_role': StaticSelect(),
|
||||
'front_image': ClearableFileInput(attrs={
|
||||
@ -412,7 +416,7 @@ class DeviceTypeForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ModuleTypeForm(CustomFieldModelForm):
|
||||
class ModuleTypeForm(NetBoxModelForm):
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
queryset=Manufacturer.objects.all()
|
||||
)
|
||||
@ -429,7 +433,7 @@ class ModuleTypeForm(CustomFieldModelForm):
|
||||
]
|
||||
|
||||
|
||||
class DeviceRoleForm(CustomFieldModelForm):
|
||||
class DeviceRoleForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -443,7 +447,7 @@ class DeviceRoleForm(CustomFieldModelForm):
|
||||
]
|
||||
|
||||
|
||||
class PlatformForm(CustomFieldModelForm):
|
||||
class PlatformForm(NetBoxModelForm):
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False
|
||||
@ -466,7 +470,7 @@ class PlatformForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class DeviceForm(TenancyForm, CustomFieldModelForm):
|
||||
class DeviceForm(TenancyForm, NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -648,7 +652,7 @@ class DeviceForm(TenancyForm, CustomFieldModelForm):
|
||||
self.fields['position'].widget.choices = [(position, f'U{position}')]
|
||||
|
||||
|
||||
class ModuleForm(CustomFieldModelForm):
|
||||
class ModuleForm(NetBoxModelForm):
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
@ -688,7 +692,7 @@ class ModuleForm(CustomFieldModelForm):
|
||||
]
|
||||
|
||||
|
||||
class CableForm(TenancyForm, CustomFieldModelForm):
|
||||
class CableForm(TenancyForm, NetBoxModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
@ -711,7 +715,7 @@ class CableForm(TenancyForm, CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class PowerPanelForm(CustomFieldModelForm):
|
||||
class PowerPanelForm(NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -745,17 +749,18 @@ class PowerPanelForm(CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Power Panel', ('region', 'site_group', 'site', 'location', 'name', 'tags')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'location', 'name', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Power Panel', ('region', 'site_group', 'site', 'location', 'name', 'tags')),
|
||||
)
|
||||
|
||||
|
||||
class PowerFeedForm(CustomFieldModelForm):
|
||||
class PowerFeedForm(NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -800,17 +805,18 @@ class PowerFeedForm(CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Power Panel', ('region', 'site', 'power_panel')),
|
||||
('Power Feed', ('rack', 'name', 'status', 'type', 'mark_connected', 'tags')),
|
||||
('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PowerFeed
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply',
|
||||
'phase', 'voltage', 'amperage', 'max_utilization', 'comments', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Power Panel', ('region', 'site', 'power_panel')),
|
||||
('Power Feed', ('rack', 'name', 'status', 'type', 'mark_connected', 'tags')),
|
||||
('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
|
||||
)
|
||||
widgets = {
|
||||
'status': StaticSelect(),
|
||||
'type': StaticSelect(),
|
||||
@ -823,7 +829,7 @@ class PowerFeedForm(CustomFieldModelForm):
|
||||
# Virtual chassis
|
||||
#
|
||||
|
||||
class VirtualChassisForm(CustomFieldModelForm):
|
||||
class VirtualChassisForm(NetBoxModelForm):
|
||||
master = forms.ModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
@ -1101,16 +1107,17 @@ class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
widget=forms.HiddenInput
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Inventory Item', ('device_type', 'parent', 'name', 'label', 'role', 'description')),
|
||||
('Hardware', ('manufacturer', 'part_id')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItemTemplate
|
||||
fields = [
|
||||
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||
'component_type', 'component_id',
|
||||
]
|
||||
fieldsets = (
|
||||
('Inventory Item', ('device_type', 'parent', 'name', 'label', 'role', 'description')),
|
||||
('Hardware', ('manufacturer', 'part_id')),
|
||||
)
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
}
|
||||
@ -1120,7 +1127,7 @@ class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
# Device components
|
||||
#
|
||||
|
||||
class ConsolePortForm(CustomFieldModelForm):
|
||||
class ConsolePortForm(NetBoxModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
@ -1138,7 +1145,7 @@ class ConsolePortForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ConsoleServerPortForm(CustomFieldModelForm):
|
||||
class ConsoleServerPortForm(NetBoxModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
@ -1156,7 +1163,7 @@ class ConsoleServerPortForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class PowerPortForm(CustomFieldModelForm):
|
||||
class PowerPortForm(NetBoxModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
@ -1174,7 +1181,7 @@ class PowerPortForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class PowerOutletForm(CustomFieldModelForm):
|
||||
class PowerOutletForm(NetBoxModelForm):
|
||||
power_port = DynamicModelChoiceField(
|
||||
queryset=PowerPort.objects.all(),
|
||||
required=False,
|
||||
@ -1199,7 +1206,7 @@ class PowerOutletForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
|
||||
class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
required=False,
|
||||
@ -1271,6 +1278,17 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Interface', ('device', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')),
|
||||
('Addressing', ('vrf', 'mac_address', 'wwn')),
|
||||
('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
||||
('Related Interfaces', ('parent', 'bridge', 'lag')),
|
||||
('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
|
||||
('Wireless', (
|
||||
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
|
||||
)),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Interface
|
||||
fields = [
|
||||
@ -1278,17 +1296,6 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
|
||||
'mgmt_only', 'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency',
|
||||
'rf_channel_width', 'tx_power', 'wireless_lans', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Interface', ('device', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')),
|
||||
('Addressing', ('vrf', 'mac_address', 'wwn')),
|
||||
('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
||||
('Related Interfaces', ('parent', 'bridge', 'lag')),
|
||||
('802.1Q Switching', ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
|
||||
('Wireless', (
|
||||
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group',
|
||||
'wireless_lans',
|
||||
)),
|
||||
)
|
||||
widgets = {
|
||||
'device': forms.HiddenInput(),
|
||||
'type': StaticSelect(),
|
||||
@ -1308,7 +1315,7 @@ class InterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class FrontPortForm(CustomFieldModelForm):
|
||||
class FrontPortForm(NetBoxModelForm):
|
||||
rear_port = DynamicModelChoiceField(
|
||||
queryset=RearPort.objects.all(),
|
||||
query_params={
|
||||
@ -1332,7 +1339,7 @@ class FrontPortForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class RearPortForm(CustomFieldModelForm):
|
||||
class RearPortForm(NetBoxModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
@ -1349,7 +1356,7 @@ class RearPortForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ModuleBayForm(CustomFieldModelForm):
|
||||
class ModuleBayForm(NetBoxModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
@ -1365,7 +1372,7 @@ class ModuleBayForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class DeviceBayForm(CustomFieldModelForm):
|
||||
class DeviceBayForm(NetBoxModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
@ -1401,7 +1408,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
|
||||
).exclude(pk=device_bay.device.pk)
|
||||
|
||||
|
||||
class InventoryItemForm(CustomFieldModelForm):
|
||||
class InventoryItemForm(NetBoxModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=InventoryItem.objects.all(),
|
||||
required=False,
|
||||
@ -1432,16 +1439,17 @@ class InventoryItemForm(CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Inventory Item', ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
|
||||
('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = [
|
||||
'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
||||
'description', 'component_type', 'component_id', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Inventory Item', ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
|
||||
('Hardware', ('manufacturer', 'part_id', 'serial', 'asset_tag')),
|
||||
)
|
||||
widgets = {
|
||||
'device': forms.HiddenInput(),
|
||||
}
|
||||
@ -1451,7 +1459,7 @@ class InventoryItemForm(CustomFieldModelForm):
|
||||
# Device component roles
|
||||
#
|
||||
|
||||
class InventoryItemRoleForm(CustomFieldModelForm):
|
||||
class InventoryItemRoleForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
|
@ -1,8 +1,8 @@
|
||||
from django import forms
|
||||
|
||||
from dcim.models import *
|
||||
from extras.forms import CustomFieldModelForm
|
||||
from extras.models import Tag
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from utilities.forms import (
|
||||
BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField,
|
||||
)
|
||||
@ -149,7 +149,7 @@ class FrontPortCreateForm(DeviceComponentCreateForm):
|
||||
}
|
||||
|
||||
|
||||
class VirtualChassisCreateForm(CustomFieldModelForm):
|
||||
class VirtualChassisCreateForm(NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
|
@ -33,8 +33,7 @@ class CustomFieldBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = []
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class CustomLinkBulkEditForm(BulkEditForm):
|
||||
@ -64,9 +63,6 @@ class CustomLinkBulkEditForm(BulkEditForm):
|
||||
widget=StaticSelect()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = []
|
||||
|
||||
|
||||
class ExportTemplateBulkEditForm(BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
@ -95,8 +91,7 @@ class ExportTemplateBulkEditForm(BulkEditForm):
|
||||
widget=BulkEditNullBooleanSelect()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['description', 'mime_type', 'file_extension']
|
||||
nullable_fields = ('description', 'mime_type', 'file_extension')
|
||||
|
||||
|
||||
class WebhookBulkEditForm(BulkEditForm):
|
||||
@ -138,8 +133,7 @@ class WebhookBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['secret', 'conditions', 'ca_file_path']
|
||||
nullable_fields = ('secret', 'conditions', 'ca_file_path')
|
||||
|
||||
|
||||
class TagBulkEditForm(BulkEditForm):
|
||||
@ -155,8 +149,7 @@ class TagBulkEditForm(BulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['description']
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class ConfigContextBulkEditForm(BulkEditForm):
|
||||
@ -177,10 +170,7 @@ class ConfigContextBulkEditForm(BulkEditForm):
|
||||
max_length=100
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'description',
|
||||
]
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class JournalEntryBulkEditForm(BulkEditForm):
|
||||
@ -196,6 +186,3 @@ class JournalEntryBulkEditForm(BulkEditForm):
|
||||
required=False,
|
||||
widget=forms.Textarea()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = []
|
||||
|
@ -1,16 +1,8 @@
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
|
||||
from extras.choices import *
|
||||
from extras.models import *
|
||||
from utilities.forms import BootstrapMixin, BulkEditBaseForm, CSVModelForm
|
||||
|
||||
__all__ = (
|
||||
'CustomFieldModelCSVForm',
|
||||
'CustomFieldModelBulkEditForm',
|
||||
'CustomFieldModelFilterForm',
|
||||
'CustomFieldModelForm',
|
||||
'CustomFieldsMixin',
|
||||
)
|
||||
|
||||
@ -50,76 +42,3 @@ class CustomFieldsMixin:
|
||||
|
||||
# Annotate the field in the list of CustomField form fields
|
||||
self.custom_fields[field_name] = customfield
|
||||
|
||||
|
||||
class CustomFieldModelForm(BootstrapMixin, CustomFieldsMixin, forms.ModelForm):
|
||||
"""
|
||||
Extend ModelForm to include custom field support.
|
||||
"""
|
||||
def _get_content_type(self):
|
||||
return ContentType.objects.get_for_model(self._meta.model)
|
||||
|
||||
def _get_form_field(self, customfield):
|
||||
if self.instance.pk:
|
||||
form_field = customfield.to_form_field(set_initial=False)
|
||||
form_field.initial = self.instance.custom_field_data.get(customfield.name, None)
|
||||
return form_field
|
||||
|
||||
return customfield.to_form_field()
|
||||
|
||||
def clean(self):
|
||||
|
||||
# Save custom field data on instance
|
||||
for cf_name, customfield in self.custom_fields.items():
|
||||
key = cf_name[3:] # Strip "cf_" from field name
|
||||
value = self.cleaned_data.get(cf_name)
|
||||
|
||||
# Convert "empty" values to null
|
||||
if value in self.fields[cf_name].empty_values:
|
||||
self.instance.custom_field_data[key] = None
|
||||
else:
|
||||
self.instance.custom_field_data[key] = customfield.serialize(value)
|
||||
|
||||
return super().clean()
|
||||
|
||||
|
||||
class CustomFieldModelCSVForm(CSVModelForm, CustomFieldModelForm):
|
||||
|
||||
def _get_form_field(self, customfield):
|
||||
return customfield.to_form_field(for_csv_import=True)
|
||||
|
||||
|
||||
class CustomFieldModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, BulkEditBaseForm):
|
||||
|
||||
def _get_form_field(self, customfield):
|
||||
return customfield.to_form_field(set_initial=False, enforce_required=False)
|
||||
|
||||
def _append_customfield_fields(self):
|
||||
"""
|
||||
Append form fields for all CustomFields assigned to this object type.
|
||||
"""
|
||||
for customfield in self._get_custom_fields(self._get_content_type()):
|
||||
# Annotate non-required custom fields as nullable
|
||||
if not customfield.required:
|
||||
self.nullable_fields.append(customfield.name)
|
||||
|
||||
self.fields[customfield.name] = self._get_form_field(customfield)
|
||||
|
||||
# Annotate the field in the list of CustomField form fields
|
||||
self.custom_fields[customfield.name] = customfield
|
||||
|
||||
|
||||
class CustomFieldModelFilterForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
label='Search'
|
||||
)
|
||||
|
||||
def _get_custom_fields(self, content_type):
|
||||
return CustomField.objects.filter(content_types=content_type).exclude(
|
||||
Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
|
||||
Q(type=CustomFieldTypeChoices.TYPE_JSON)
|
||||
)
|
||||
|
||||
def _get_form_field(self, customfield):
|
||||
return customfield.to_form_field(set_initial=False, enforce_required=False)
|
||||
|
@ -28,11 +28,10 @@ __all__ = (
|
||||
|
||||
|
||||
class CustomFieldFilterForm(FilterForm):
|
||||
field_groups = [
|
||||
['q'],
|
||||
['type', 'content_types'],
|
||||
['weight', 'required'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
('Attributes', ('type', 'content_types', 'weight', 'required')),
|
||||
)
|
||||
content_types = ContentTypeMultipleChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
limit_choices_to=FeatureQuery('custom_fields'),
|
||||
@ -56,10 +55,10 @@ class CustomFieldFilterForm(FilterForm):
|
||||
|
||||
|
||||
class CustomLinkFilterForm(FilterForm):
|
||||
field_groups = [
|
||||
['q'],
|
||||
['content_type', 'enabled', 'new_window', 'weight'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
('Attributes', ('content_type', 'enabled', 'new_window', 'weight')),
|
||||
)
|
||||
content_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
limit_choices_to=FeatureQuery('custom_fields'),
|
||||
@ -83,10 +82,10 @@ class CustomLinkFilterForm(FilterForm):
|
||||
|
||||
|
||||
class ExportTemplateFilterForm(FilterForm):
|
||||
field_groups = [
|
||||
['q'],
|
||||
['content_type', 'mime_type', 'file_extension', 'as_attachment'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
('Attributes', ('content_type', 'mime_type', 'file_extension', 'as_attachment')),
|
||||
)
|
||||
content_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
limit_choices_to=FeatureQuery('custom_fields'),
|
||||
@ -108,11 +107,11 @@ class ExportTemplateFilterForm(FilterForm):
|
||||
|
||||
|
||||
class WebhookFilterForm(FilterForm):
|
||||
field_groups = [
|
||||
['q'],
|
||||
['content_types', 'http_method', 'enabled'],
|
||||
['type_create', 'type_update', 'type_delete'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
('Attributes', ('content_types', 'http_method', 'enabled')),
|
||||
('Events', ('type_create', 'type_update', 'type_delete')),
|
||||
)
|
||||
content_types = ContentTypeMultipleChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
limit_choices_to=FeatureQuery('custom_fields'),
|
||||
@ -160,13 +159,13 @@ class TagFilterForm(FilterForm):
|
||||
|
||||
|
||||
class ConfigContextFilterForm(FilterForm):
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['region_id', 'site_group_id', 'site_id'],
|
||||
['device_type_id', 'platform_id', 'role_id'],
|
||||
['cluster_type_id', 'cluster_group_id', 'cluster_id'],
|
||||
['tenant_group_id', 'tenant_id']
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
('Device', ('device_type_id', 'platform_id', 'role_id')),
|
||||
('Cluster', ('cluster_type_id', 'cluster_group_id', 'cluster_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id'))
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -243,11 +242,11 @@ class LocalConfigContextFilterForm(forms.Form):
|
||||
|
||||
class JournalEntryFilterForm(FilterForm):
|
||||
model = JournalEntry
|
||||
field_groups = [
|
||||
['q'],
|
||||
['created_before', 'created_after', 'created_by_id'],
|
||||
['assigned_object_type_id', 'kind']
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
('Creation', ('created_before', 'created_after', 'created_by_id')),
|
||||
('Attributes', ('assigned_object_type_id', 'kind'))
|
||||
)
|
||||
created_after = forms.DateTimeField(
|
||||
required=False,
|
||||
label=_('After'),
|
||||
@ -283,11 +282,11 @@ class JournalEntryFilterForm(FilterForm):
|
||||
|
||||
class ObjectChangeFilterForm(FilterForm):
|
||||
model = ObjectChange
|
||||
field_groups = [
|
||||
['q'],
|
||||
['time_before', 'time_after', 'action'],
|
||||
['user_id', 'changed_object_type_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
('Time', ('time_before', 'time_after')),
|
||||
('Attributes', ('action', 'user_id', 'changed_object_type_id')),
|
||||
)
|
||||
time_after = forms.DateTimeField(
|
||||
required=False,
|
||||
label=_('After'),
|
||||
|
@ -13,7 +13,6 @@ from utilities.forms import (
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||
|
||||
__all__ = (
|
||||
'AddRemoveTagsForm',
|
||||
'ConfigContextForm',
|
||||
'CustomFieldForm',
|
||||
'CustomLinkForm',
|
||||
@ -31,16 +30,17 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
||||
limit_choices_to=FeatureQuery('custom_fields')
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Custom Field', ('name', 'label', 'type', 'object_type', 'weight', 'required', 'description')),
|
||||
('Assigned Models', ('content_types',)),
|
||||
('Behavior', ('filter_logic',)),
|
||||
('Values', ('default', 'choices')),
|
||||
('Validation', ('validation_minimum', 'validation_maximum', 'validation_regex')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CustomField
|
||||
fields = '__all__'
|
||||
fieldsets = (
|
||||
('Custom Field', ('name', 'label', 'type', 'object_type', 'weight', 'required', 'description')),
|
||||
('Assigned Models', ('content_types',)),
|
||||
('Behavior', ('filter_logic',)),
|
||||
('Values', ('default', 'choices')),
|
||||
('Validation', ('validation_minimum', 'validation_maximum', 'validation_regex')),
|
||||
)
|
||||
widgets = {
|
||||
'type': StaticSelect(),
|
||||
'filter_logic': StaticSelect(),
|
||||
@ -53,13 +53,14 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm):
|
||||
limit_choices_to=FeatureQuery('custom_links')
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Custom Link', ('name', 'content_type', 'weight', 'group_name', 'button_class', 'enabled', 'new_window')),
|
||||
('Templates', ('link_text', 'link_url')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CustomLink
|
||||
fields = '__all__'
|
||||
fieldsets = (
|
||||
('Custom Link', ('name', 'content_type', 'weight', 'group_name', 'button_class', 'enabled', 'new_window')),
|
||||
('Templates', ('link_text', 'link_url')),
|
||||
)
|
||||
widgets = {
|
||||
'button_class': StaticSelect(),
|
||||
'link_text': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||
@ -78,14 +79,15 @@ class ExportTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
limit_choices_to=FeatureQuery('export_templates')
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Export Template', ('name', 'content_type', 'description')),
|
||||
('Template', ('template_code',)),
|
||||
('Rendering', ('mime_type', 'file_extension', 'as_attachment')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ExportTemplate
|
||||
fields = '__all__'
|
||||
fieldsets = (
|
||||
('Export Template', ('name', 'content_type', 'description')),
|
||||
('Template', ('template_code',)),
|
||||
('Rendering', ('mime_type', 'file_extension', 'as_attachment')),
|
||||
)
|
||||
widgets = {
|
||||
'template_code': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||
}
|
||||
@ -97,18 +99,19 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
|
||||
limit_choices_to=FeatureQuery('webhooks')
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Webhook', ('name', 'content_types', 'enabled')),
|
||||
('Events', ('type_create', 'type_update', 'type_delete')),
|
||||
('HTTP Request', (
|
||||
'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
|
||||
)),
|
||||
('Conditions', ('conditions',)),
|
||||
('SSL', ('ssl_verification', 'ca_file_path')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Webhook
|
||||
fields = '__all__'
|
||||
fieldsets = (
|
||||
('Webhook', ('name', 'content_types', 'enabled')),
|
||||
('Events', ('type_create', 'type_update', 'type_delete')),
|
||||
('HTTP Request', (
|
||||
'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
|
||||
)),
|
||||
('Conditions', ('conditions',)),
|
||||
('SSL', ('ssl_verification', 'ca_file_path')),
|
||||
)
|
||||
labels = {
|
||||
'type_create': 'Creations',
|
||||
'type_update': 'Updates',
|
||||
@ -124,30 +127,15 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
|
||||
class TagForm(BootstrapMixin, forms.ModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
('Tag', ('name', 'slug', 'color', 'description')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = [
|
||||
'name', 'slug', 'color', 'description'
|
||||
]
|
||||
fieldsets = (
|
||||
('Tag', ('name', 'slug', 'color', 'description')),
|
||||
)
|
||||
|
||||
|
||||
class AddRemoveTagsForm(forms.Form):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Add add/remove tags fields
|
||||
self.fields['add_tags'] = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
self.fields['remove_tags'] = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
|
||||
class ConfigContextForm(BootstrapMixin, forms.ModelForm):
|
||||
|
@ -1,11 +1,11 @@
|
||||
from django import forms
|
||||
|
||||
from dcim.models import Region, Site, SiteGroup
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||
from ipam.choices import *
|
||||
from ipam.constants import *
|
||||
from ipam.models import *
|
||||
from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BulkEditNullBooleanSelect, DatePicker, DynamicModelChoiceField, NumericArrayField, StaticSelect,
|
||||
@ -30,7 +30,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class VRFBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class VRFBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -49,13 +49,10 @@ class VRFBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'tenant', 'description',
|
||||
]
|
||||
nullable_fields = ('tenant', 'description')
|
||||
|
||||
|
||||
class RouteTargetBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class RouteTargetBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=RouteTarget.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -69,13 +66,10 @@ class RouteTargetBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'tenant', 'description',
|
||||
]
|
||||
nullable_fields = ('tenant', 'description')
|
||||
|
||||
|
||||
class RIRBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class RIRBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=RIR.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -89,11 +83,10 @@ class RIRBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['is_private', 'description']
|
||||
nullable_fields = ('is_private', 'description')
|
||||
|
||||
|
||||
class ASNBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ASNBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ASN.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -116,16 +109,10 @@ class ASNBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'date_added', 'description',
|
||||
]
|
||||
widgets = {
|
||||
'date_added': DatePicker(),
|
||||
}
|
||||
nullable_fields = ('date_added', 'description')
|
||||
|
||||
|
||||
class AggregateBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class AggregateBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Aggregate.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -147,16 +134,10 @@ class AggregateBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'date_added', 'description',
|
||||
]
|
||||
widgets = {
|
||||
'date_added': DatePicker(),
|
||||
}
|
||||
nullable_fields = ('date_added', 'description')
|
||||
|
||||
|
||||
class RoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class RoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Role.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -169,11 +150,10 @@ class RoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['description']
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class PrefixBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class PrefixBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Prefix.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -232,13 +212,12 @@ class PrefixBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'site', 'vrf', 'tenant', 'role', 'description',
|
||||
]
|
||||
nullable_fields = (
|
||||
'site', 'vrf', 'tenant', 'role', 'description',
|
||||
)
|
||||
|
||||
|
||||
class IPRangeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=IPRange.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -266,13 +245,12 @@ class IPRangeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'vrf', 'tenant', 'role', 'description',
|
||||
]
|
||||
nullable_fields = (
|
||||
'vrf', 'tenant', 'role', 'description',
|
||||
)
|
||||
|
||||
|
||||
class IPAddressBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=IPAddress.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -311,13 +289,12 @@ class IPAddressBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'vrf', 'role', 'tenant', 'dns_name', 'description',
|
||||
]
|
||||
nullable_fields = (
|
||||
'vrf', 'role', 'tenant', 'dns_name', 'description',
|
||||
)
|
||||
|
||||
|
||||
class FHRPGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=FHRPGroup.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -348,11 +325,10 @@ class FHRPGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['auth_type', 'auth_key', 'description']
|
||||
nullable_fields = ('auth_type', 'auth_key', 'description')
|
||||
|
||||
|
||||
class VLANGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=VLANGroup.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -378,11 +354,10 @@ class VLANGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['site', 'description']
|
||||
nullable_fields = ('site', 'description')
|
||||
|
||||
|
||||
class VLANBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class VLANBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=VLAN.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -428,13 +403,12 @@ class VLANBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'site', 'group', 'tenant', 'role', 'description',
|
||||
]
|
||||
nullable_fields = (
|
||||
'site', 'group', 'tenant', 'role', 'description',
|
||||
)
|
||||
|
||||
|
||||
class ServiceTemplateBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ServiceTemplate.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -456,10 +430,7 @@ class ServiceTemplateBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditFor
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'description',
|
||||
]
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class ServiceBulkEditForm(ServiceTemplateBulkEditForm):
|
||||
|
@ -2,10 +2,10 @@ from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from dcim.models import Device, Interface, Site
|
||||
from extras.forms import CustomFieldModelCSVForm
|
||||
from ipam.choices import *
|
||||
from ipam.constants import *
|
||||
from ipam.models import *
|
||||
from netbox.forms import NetBoxModelCSVForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField
|
||||
from virtualization.models import VirtualMachine, VMInterface
|
||||
@ -28,7 +28,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class VRFCSVForm(CustomFieldModelCSVForm):
|
||||
class VRFCSVForm(NetBoxModelCSVForm):
|
||||
tenant = CSVModelChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False,
|
||||
@ -41,7 +41,7 @@ class VRFCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description')
|
||||
|
||||
|
||||
class RouteTargetCSVForm(CustomFieldModelCSVForm):
|
||||
class RouteTargetCSVForm(NetBoxModelCSVForm):
|
||||
tenant = CSVModelChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False,
|
||||
@ -54,7 +54,7 @@ class RouteTargetCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'description', 'tenant')
|
||||
|
||||
|
||||
class RIRCSVForm(CustomFieldModelCSVForm):
|
||||
class RIRCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@ -65,7 +65,7 @@ class RIRCSVForm(CustomFieldModelCSVForm):
|
||||
}
|
||||
|
||||
|
||||
class AggregateCSVForm(CustomFieldModelCSVForm):
|
||||
class AggregateCSVForm(NetBoxModelCSVForm):
|
||||
rir = CSVModelChoiceField(
|
||||
queryset=RIR.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -83,7 +83,7 @@ class AggregateCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('prefix', 'rir', 'tenant', 'date_added', 'description')
|
||||
|
||||
|
||||
class ASNCSVForm(CustomFieldModelCSVForm):
|
||||
class ASNCSVForm(NetBoxModelCSVForm):
|
||||
rir = CSVModelChoiceField(
|
||||
queryset=RIR.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -102,7 +102,7 @@ class ASNCSVForm(CustomFieldModelCSVForm):
|
||||
help_texts = {}
|
||||
|
||||
|
||||
class RoleCSVForm(CustomFieldModelCSVForm):
|
||||
class RoleCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@ -110,7 +110,7 @@ class RoleCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'slug', 'weight', 'description')
|
||||
|
||||
|
||||
class PrefixCSVForm(CustomFieldModelCSVForm):
|
||||
class PrefixCSVForm(NetBoxModelCSVForm):
|
||||
vrf = CSVModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -174,7 +174,7 @@ class PrefixCSVForm(CustomFieldModelCSVForm):
|
||||
self.fields['vlan'].queryset = self.fields['vlan'].queryset.filter(**params)
|
||||
|
||||
|
||||
class IPRangeCSVForm(CustomFieldModelCSVForm):
|
||||
class IPRangeCSVForm(NetBoxModelCSVForm):
|
||||
vrf = CSVModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -205,7 +205,7 @@ class IPRangeCSVForm(CustomFieldModelCSVForm):
|
||||
)
|
||||
|
||||
|
||||
class IPAddressCSVForm(CustomFieldModelCSVForm):
|
||||
class IPAddressCSVForm(NetBoxModelCSVForm):
|
||||
vrf = CSVModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -312,7 +312,7 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
|
||||
return ipaddress
|
||||
|
||||
|
||||
class FHRPGroupCSVForm(CustomFieldModelCSVForm):
|
||||
class FHRPGroupCSVForm(NetBoxModelCSVForm):
|
||||
protocol = CSVChoiceField(
|
||||
choices=FHRPGroupProtocolChoices
|
||||
)
|
||||
@ -326,7 +326,7 @@ class FHRPGroupCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'description')
|
||||
|
||||
|
||||
class VLANGroupCSVForm(CustomFieldModelCSVForm):
|
||||
class VLANGroupCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
scope_type = CSVContentTypeField(
|
||||
queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
|
||||
@ -354,7 +354,7 @@ class VLANGroupCSVForm(CustomFieldModelCSVForm):
|
||||
}
|
||||
|
||||
|
||||
class VLANCSVForm(CustomFieldModelCSVForm):
|
||||
class VLANCSVForm(NetBoxModelCSVForm):
|
||||
site = CSVModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
@ -393,7 +393,7 @@ class VLANCSVForm(CustomFieldModelCSVForm):
|
||||
}
|
||||
|
||||
|
||||
class ServiceTemplateCSVForm(CustomFieldModelCSVForm):
|
||||
class ServiceTemplateCSVForm(NetBoxModelCSVForm):
|
||||
protocol = CSVChoiceField(
|
||||
choices=ServiceProtocolChoices,
|
||||
help_text='IP protocol'
|
||||
@ -404,7 +404,7 @@ class ServiceTemplateCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'protocol', 'ports', 'description')
|
||||
|
||||
|
||||
class ServiceCSVForm(CustomFieldModelCSVForm):
|
||||
class ServiceCSVForm(NetBoxModelCSVForm):
|
||||
device = CSVModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
|
@ -2,11 +2,11 @@ from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from dcim.models import Location, Rack, Region, Site, SiteGroup
|
||||
from extras.forms import CustomFieldModelFilterForm
|
||||
from ipam.choices import *
|
||||
from ipam.constants import *
|
||||
from ipam.models import *
|
||||
from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import TenancyFilterForm
|
||||
from utilities.forms import (
|
||||
add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField, StaticSelect, StaticSelectMultiple,
|
||||
@ -39,13 +39,13 @@ IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([
|
||||
])
|
||||
|
||||
|
||||
class VRFFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VRF
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['import_target_id', 'export_target_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Route Targets', ('import_target_id', 'export_target_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
import_target_id = DynamicModelMultipleChoiceField(
|
||||
queryset=RouteTarget.objects.all(),
|
||||
required=False,
|
||||
@ -59,13 +59,13 @@ class VRFFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class RouteTargetFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = RouteTarget
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['importing_vrf_id', 'exporting_vrf_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('VRF', ('importing_vrf_id', 'exporting_vrf_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
importing_vrf_id = DynamicModelMultipleChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
@ -79,7 +79,7 @@ class RouteTargetFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class RIRFilterForm(CustomFieldModelFilterForm):
|
||||
class RIRFilterForm(NetBoxModelFilterSetForm):
|
||||
model = RIR
|
||||
is_private = forms.NullBooleanField(
|
||||
required=False,
|
||||
@ -91,13 +91,13 @@ class RIRFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class AggregateFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Aggregate
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['family', 'rir_id'],
|
||||
['tenant_group_id', 'tenant_id']
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('family', 'rir_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
family = forms.ChoiceField(
|
||||
required=False,
|
||||
choices=add_blank_choice(IPAddressFamilyChoices),
|
||||
@ -112,14 +112,13 @@ class AggregateFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ASNFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = ASN
|
||||
field_groups = [
|
||||
['q'],
|
||||
['rir_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
['site_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Assignment', ('rir_id', 'site_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
rir_id = DynamicModelMultipleChoiceField(
|
||||
queryset=RIR.objects.all(),
|
||||
required=False,
|
||||
@ -130,22 +129,23 @@ class ASNFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
required=False,
|
||||
label=_('Site')
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class RoleFilterForm(CustomFieldModelFilterForm):
|
||||
class RoleFilterForm(NetBoxModelFilterSetForm):
|
||||
model = Role
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class PrefixFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Prefix
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized'],
|
||||
['vrf_id', 'present_in_vrf_id'],
|
||||
['region_id', 'site_group_id', 'site_id'],
|
||||
['tenant_group_id', 'tenant_id']
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Addressing', ('within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized')),
|
||||
('VRF', ('vrf_id', 'present_in_vrf_id')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
mask_length__lte = forms.IntegerField(
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
@ -228,13 +228,13 @@ class PrefixFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class IPRangeFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = IPRange
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['family', 'vrf_id', 'status', 'role_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attriubtes', ('family', 'vrf_id', 'status', 'role_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
family = forms.ChoiceField(
|
||||
required=False,
|
||||
choices=add_blank_choice(IPAddressFamilyChoices),
|
||||
@ -261,14 +261,14 @@ class IPRangeFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class IPAddressFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = IPAddress
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface'],
|
||||
['vrf_id', 'present_in_vrf_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface')),
|
||||
('VRF', ('vrf_id', 'present_in_vrf_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
parent = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
@ -321,12 +321,12 @@ class IPAddressFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class FHRPGroupFilterForm(CustomFieldModelFilterForm):
|
||||
class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
model = FHRPGroup
|
||||
field_groups = (
|
||||
('q', 'tag'),
|
||||
('protocol', 'group_id'),
|
||||
('auth_type', 'auth_key'),
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('protocol', 'group_id')),
|
||||
('Authentication', ('auth_type', 'auth_key')),
|
||||
)
|
||||
protocol = forms.MultipleChoiceField(
|
||||
choices=FHRPGroupProtocolChoices,
|
||||
@ -351,12 +351,12 @@ class FHRPGroupFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VLANGroupFilterForm(CustomFieldModelFilterForm):
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['region', 'sitegroup', 'site', 'location', 'rack'],
|
||||
['min_vid', 'max_vid'],
|
||||
]
|
||||
class VLANGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Location', ('region', 'sitegroup', 'site', 'location', 'rack')),
|
||||
('VLAN ID', ('min_vid', 'max_vid')),
|
||||
)
|
||||
model = VLANGroup
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
@ -394,14 +394,14 @@ class VLANGroupFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VLANFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VLAN
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['region_id', 'site_group_id', 'site_id'],
|
||||
['group_id', 'status', 'role_id', 'vid'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
('Attributes', ('group_id', 'status', 'role_id', 'vid')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
required=False,
|
||||
@ -448,11 +448,11 @@ class VLANFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ServiceTemplateFilterForm(CustomFieldModelFilterForm):
|
||||
class ServiceTemplateFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ServiceTemplate
|
||||
field_groups = (
|
||||
('q', 'tag'),
|
||||
('protocol', 'port'),
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('protocol', 'port')),
|
||||
)
|
||||
protocol = forms.ChoiceField(
|
||||
choices=add_blank_choice(ServiceProtocolChoices),
|
||||
|
@ -2,13 +2,13 @@ from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from dcim.models import Device, Interface, Location, Rack, Region, Site, SiteGroup
|
||||
from extras.forms import CustomFieldModelForm
|
||||
from extras.models import Tag
|
||||
from ipam.choices import *
|
||||
from ipam.constants import *
|
||||
from ipam.formfields import IPNetworkFormField
|
||||
from ipam.models import *
|
||||
from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.exceptions import PermissionsViolation
|
||||
from utilities.forms import (
|
||||
@ -39,7 +39,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class VRFForm(TenancyForm, CustomFieldModelForm):
|
||||
class VRFForm(TenancyForm, NetBoxModelForm):
|
||||
import_targets = DynamicModelMultipleChoiceField(
|
||||
queryset=RouteTarget.objects.all(),
|
||||
required=False
|
||||
@ -53,17 +53,18 @@ class VRFForm(TenancyForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('VRF', ('name', 'rd', 'enforce_unique', 'description', 'tags')),
|
||||
('Route Targets', ('import_targets', 'export_targets')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VRF
|
||||
fields = [
|
||||
'name', 'rd', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tenant_group', 'tenant',
|
||||
'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('VRF', ('name', 'rd', 'enforce_unique', 'description', 'tags')),
|
||||
('Route Targets', ('import_targets', 'export_targets')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
labels = {
|
||||
'rd': "RD",
|
||||
}
|
||||
@ -72,24 +73,25 @@ class VRFForm(TenancyForm, CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class RouteTargetForm(TenancyForm, CustomFieldModelForm):
|
||||
class RouteTargetForm(TenancyForm, NetBoxModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Route Target', ('name', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RouteTarget
|
||||
fields = [
|
||||
'name', 'description', 'tenant_group', 'tenant', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Route Target', ('name', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
|
||||
class RIRForm(CustomFieldModelForm):
|
||||
class RIRForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -103,7 +105,7 @@ class RIRForm(CustomFieldModelForm):
|
||||
]
|
||||
|
||||
|
||||
class AggregateForm(TenancyForm, CustomFieldModelForm):
|
||||
class AggregateForm(TenancyForm, NetBoxModelForm):
|
||||
rir = DynamicModelChoiceField(
|
||||
queryset=RIR.objects.all(),
|
||||
label='RIR'
|
||||
@ -113,15 +115,16 @@ class AggregateForm(TenancyForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Aggregate', ('prefix', 'rir', 'date_added', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Aggregate
|
||||
fields = [
|
||||
'prefix', 'rir', 'date_added', 'description', 'tenant_group', 'tenant', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Aggregate', ('prefix', 'rir', 'date_added', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
help_texts = {
|
||||
'prefix': "IPv4 or IPv6 network",
|
||||
'rir': "Regional Internet Registry responsible for this prefix",
|
||||
@ -131,7 +134,7 @@ class AggregateForm(TenancyForm, CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ASNForm(TenancyForm, CustomFieldModelForm):
|
||||
class ASNForm(TenancyForm, NetBoxModelForm):
|
||||
rir = DynamicModelChoiceField(
|
||||
queryset=RIR.objects.all(),
|
||||
label='RIR',
|
||||
@ -146,15 +149,16 @@ class ASNForm(TenancyForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('ASN', ('asn', 'rir', 'sites', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ASN
|
||||
fields = [
|
||||
'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'tags'
|
||||
]
|
||||
fieldsets = (
|
||||
('ASN', ('asn', 'rir', 'sites', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
help_texts = {
|
||||
'asn': "AS number",
|
||||
'rir': "Regional Internet Registry responsible for this prefix",
|
||||
@ -175,7 +179,7 @@ class ASNForm(TenancyForm, CustomFieldModelForm):
|
||||
return instance
|
||||
|
||||
|
||||
class RoleForm(CustomFieldModelForm):
|
||||
class RoleForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -189,7 +193,7 @@ class RoleForm(CustomFieldModelForm):
|
||||
]
|
||||
|
||||
|
||||
class PrefixForm(TenancyForm, CustomFieldModelForm):
|
||||
class PrefixForm(TenancyForm, NetBoxModelForm):
|
||||
vrf = DynamicModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
@ -248,23 +252,24 @@ class PrefixForm(TenancyForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Prefix', ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
|
||||
('Site/VLAN Assignment', ('region', 'site_group', 'site', 'vlan_group', 'vlan')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Prefix
|
||||
fields = [
|
||||
'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description',
|
||||
'tenant_group', 'tenant', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Prefix', ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
|
||||
('Site/VLAN Assignment', ('region', 'site_group', 'site', 'vlan_group', 'vlan')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
widgets = {
|
||||
'status': StaticSelect(),
|
||||
}
|
||||
|
||||
|
||||
class IPRangeForm(TenancyForm, CustomFieldModelForm):
|
||||
class IPRangeForm(TenancyForm, NetBoxModelForm):
|
||||
vrf = DynamicModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
@ -279,21 +284,22 @@ class IPRangeForm(TenancyForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = IPRange
|
||||
fields = [
|
||||
'vrf', 'start_address', 'end_address', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
widgets = {
|
||||
'status': StaticSelect(),
|
||||
}
|
||||
|
||||
|
||||
class IPAddressForm(TenancyForm, CustomFieldModelForm):
|
||||
class IPAddressForm(TenancyForm, NetBoxModelForm):
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
@ -506,7 +512,7 @@ class IPAddressForm(TenancyForm, CustomFieldModelForm):
|
||||
return ipaddress
|
||||
|
||||
|
||||
class IPAddressBulkAddForm(TenancyForm, CustomFieldModelForm):
|
||||
class IPAddressBulkAddForm(TenancyForm, NetBoxModelForm):
|
||||
vrf = DynamicModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
@ -540,7 +546,7 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
|
||||
)
|
||||
|
||||
|
||||
class FHRPGroupForm(CustomFieldModelForm):
|
||||
class FHRPGroupForm(NetBoxModelForm):
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
@ -562,16 +568,17 @@ class FHRPGroupForm(CustomFieldModelForm):
|
||||
label='Status'
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('FHRP Group', ('protocol', 'group_id', 'description', 'tags')),
|
||||
('Authentication', ('auth_type', 'auth_key')),
|
||||
('Virtual IP Address', ('ip_vrf', 'ip_address', 'ip_status'))
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = FHRPGroup
|
||||
fields = (
|
||||
'protocol', 'group_id', 'auth_type', 'auth_key', 'description', 'ip_vrf', 'ip_address', 'ip_status', 'tags',
|
||||
)
|
||||
fieldsets = (
|
||||
('FHRP Group', ('protocol', 'group_id', 'description', 'tags')),
|
||||
('Authentication', ('auth_type', 'auth_key')),
|
||||
('Virtual IP Address', ('ip_vrf', 'ip_address', 'ip_status'))
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
instance = super().save(*args, **kwargs)
|
||||
@ -629,7 +636,7 @@ class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm):
|
||||
self.fields['group'].widget.add_query_param('related_ip', ipaddress.pk)
|
||||
|
||||
|
||||
class VLANGroupForm(CustomFieldModelForm):
|
||||
class VLANGroupForm(NetBoxModelForm):
|
||||
scope_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES),
|
||||
required=False
|
||||
@ -699,17 +706,18 @@ class VLANGroupForm(CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('VLAN Group', ('name', 'slug', 'description', 'tags')),
|
||||
('Child VLANs', ('min_vid', 'max_vid')),
|
||||
('Scope', ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VLANGroup
|
||||
fields = [
|
||||
'name', 'slug', 'description', 'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack',
|
||||
'clustergroup', 'cluster', 'min_vid', 'max_vid', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('VLAN Group', ('name', 'slug', 'description', 'tags')),
|
||||
('Child VLANs', ('min_vid', 'max_vid')),
|
||||
('Scope', ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
|
||||
)
|
||||
widgets = {
|
||||
'scope_type': StaticSelect,
|
||||
}
|
||||
@ -736,7 +744,7 @@ class VLANGroupForm(CustomFieldModelForm):
|
||||
self.instance.scope_id = None
|
||||
|
||||
|
||||
class VLANForm(TenancyForm, CustomFieldModelForm):
|
||||
class VLANForm(TenancyForm, NetBoxModelForm):
|
||||
# VLANGroup assignment fields
|
||||
scope_type = forms.ChoiceField(
|
||||
choices=(
|
||||
@ -817,7 +825,7 @@ class VLANForm(TenancyForm, CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ServiceTemplateForm(CustomFieldModelForm):
|
||||
class ServiceTemplateForm(NetBoxModelForm):
|
||||
ports = NumericArrayField(
|
||||
base_field=forms.IntegerField(
|
||||
min_value=SERVICE_PORT_MIN,
|
||||
@ -838,7 +846,7 @@ class ServiceTemplateForm(CustomFieldModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ServiceForm(CustomFieldModelForm):
|
||||
class ServiceForm(NetBoxModelForm):
|
||||
device = DynamicModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False
|
||||
|
@ -1,6 +1,7 @@
|
||||
from django import forms
|
||||
|
||||
from utilities.forms import BootstrapMixin
|
||||
from .base import *
|
||||
|
||||
OBJ_TYPE_CHOICES = (
|
||||
('', 'All Objects'),
|
126
netbox/netbox/forms/base.py
Normal file
126
netbox/netbox/forms/base.py
Normal file
@ -0,0 +1,126 @@
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
|
||||
from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices
|
||||
from extras.forms.customfields import CustomFieldsMixin
|
||||
from extras.models import CustomField, Tag
|
||||
from utilities.forms import BootstrapMixin, BulkEditMixin, CSVModelForm
|
||||
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
||||
|
||||
__all__ = (
|
||||
'NetBoxModelForm',
|
||||
'NetBoxModelCSVForm',
|
||||
'NetBoxModelBulkEditForm',
|
||||
'NetBoxModelFilterSetForm',
|
||||
)
|
||||
|
||||
|
||||
class NetBoxModelForm(BootstrapMixin, CustomFieldsMixin, forms.ModelForm):
|
||||
"""
|
||||
Base form for creating & editing NetBox models. Extends Django's ModelForm to add support for custom fields.
|
||||
|
||||
Attributes:
|
||||
fieldsets: An iterable of two-tuples which define a heading and field set to display per section of
|
||||
the rendered form (optional). If not defined, the all fields will be rendered as a single section.
|
||||
"""
|
||||
fieldsets = ()
|
||||
|
||||
def _get_content_type(self):
|
||||
return ContentType.objects.get_for_model(self._meta.model)
|
||||
|
||||
def _get_form_field(self, customfield):
|
||||
if self.instance.pk:
|
||||
form_field = customfield.to_form_field(set_initial=False)
|
||||
form_field.initial = self.instance.custom_field_data.get(customfield.name, None)
|
||||
return form_field
|
||||
|
||||
return customfield.to_form_field()
|
||||
|
||||
def clean(self):
|
||||
|
||||
# Save custom field data on instance
|
||||
for cf_name, customfield in self.custom_fields.items():
|
||||
key = cf_name[3:] # Strip "cf_" from field name
|
||||
value = self.cleaned_data.get(cf_name)
|
||||
|
||||
# Convert "empty" values to null
|
||||
if value in self.fields[cf_name].empty_values:
|
||||
self.instance.custom_field_data[key] = None
|
||||
else:
|
||||
self.instance.custom_field_data[key] = customfield.serialize(value)
|
||||
|
||||
return super().clean()
|
||||
|
||||
|
||||
class NetBoxModelCSVForm(CSVModelForm, NetBoxModelForm):
|
||||
"""
|
||||
Base form for creating a NetBox objects from CSV data. Used for bulk importing.
|
||||
"""
|
||||
def _get_form_field(self, customfield):
|
||||
return customfield.to_form_field(for_csv_import=True)
|
||||
|
||||
|
||||
class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, BulkEditMixin, forms.Form):
|
||||
"""
|
||||
Base form for modifying multiple NetBox objects (of the same type) in bulk via the UI. Adds support for custom
|
||||
fields and adding/removing tags.
|
||||
|
||||
Attributes:
|
||||
nullable_fields: A list of field names indicating which fields support being set to null/empty
|
||||
"""
|
||||
add_tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
remove_tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
def _get_form_field(self, customfield):
|
||||
return customfield.to_form_field(set_initial=False, enforce_required=False)
|
||||
|
||||
def _append_customfield_fields(self):
|
||||
"""
|
||||
Append form fields for all CustomFields assigned to this object type.
|
||||
"""
|
||||
nullable_custom_fields = []
|
||||
for customfield in self._get_custom_fields(self._get_content_type()):
|
||||
# Record non-required custom fields as nullable
|
||||
if not customfield.required:
|
||||
nullable_custom_fields.append(customfield.name)
|
||||
|
||||
self.fields[customfield.name] = self._get_form_field(customfield)
|
||||
|
||||
# Annotate the field in the list of CustomField form fields
|
||||
self.custom_fields[customfield.name] = customfield
|
||||
|
||||
# Annotate nullable custom fields (if any) on the form instance
|
||||
if nullable_custom_fields:
|
||||
self.custom_fields = (*self.custom_fields, *nullable_custom_fields)
|
||||
|
||||
|
||||
class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
|
||||
"""
|
||||
Base form for FilerSet forms. These are used to filter object lists in the NetBox UI. Note that the
|
||||
corresponding FilterSet *must* provide a `q` filter.
|
||||
|
||||
Attributes:
|
||||
model: The model class associated with the form
|
||||
fieldsets: An iterable of two-tuples which define a heading and field set to display per section of
|
||||
the rendered form (optional). If not defined, the all fields will be rendered as a single section.
|
||||
"""
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
label='Search'
|
||||
)
|
||||
|
||||
def _get_custom_fields(self, content_type):
|
||||
return CustomField.objects.filter(content_types=content_type).exclude(
|
||||
Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
|
||||
Q(type=CustomFieldTypeChoices.TYPE_JSON)
|
||||
)
|
||||
|
||||
def _get_form_field(self, customfield):
|
||||
return customfield.to_form_field(set_initial=False, enforce_required=False)
|
@ -675,14 +675,13 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
"""
|
||||
Delete objects in bulk.
|
||||
|
||||
filterset: FilterSet to apply when deleting by QuerySet
|
||||
table: The table used to display devices being deleted
|
||||
form: The form class used to delete objects in bulk
|
||||
Attributes:
|
||||
filterset: FilterSet to apply when deleting by QuerySet
|
||||
table: The table used to display devices being deleted
|
||||
"""
|
||||
template_name = 'generic/object_bulk_delete.html'
|
||||
filterset = None
|
||||
table = None
|
||||
form = None
|
||||
|
||||
def get_required_permission(self):
|
||||
return get_permission_for_model(self.queryset.model, 'delete')
|
||||
@ -694,9 +693,6 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
|
||||
class BulkDeleteForm(ConfirmationForm):
|
||||
pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput)
|
||||
|
||||
if self.form:
|
||||
return self.form
|
||||
|
||||
return BulkDeleteForm
|
||||
|
||||
#
|
||||
|
@ -33,7 +33,7 @@
|
||||
{% csrf_token %}
|
||||
|
||||
{% block form %}
|
||||
{% if form.Meta.fieldsets %}
|
||||
{% if form.fieldsets %}
|
||||
|
||||
{# Render hidden fields #}
|
||||
{% for field in form.hidden_fields %}
|
||||
@ -41,7 +41,7 @@
|
||||
{% endfor %}
|
||||
|
||||
{# Render grouped fields according to Form #}
|
||||
{% for group, fields in form.Meta.fieldsets %}
|
||||
{% for group, fields in form.fieldsets %}
|
||||
<div class="field-group mb-5">
|
||||
<div class="row mb-2">
|
||||
<h5 class="offset-sm-3">{{ group }}</h5>
|
||||
|
@ -7,21 +7,22 @@
|
||||
{% for field in filter_form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
{% if filter_form.field_groups %}
|
||||
{# List filters by group #}
|
||||
{% for group in filter_form.field_groups %}
|
||||
<div class="col col-12">
|
||||
{% for name in group %}
|
||||
{% with field=filter_form|get_item:name %}
|
||||
{% render_field field %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if not forloop.last %}
|
||||
<hr class="card-divider mt-0" />
|
||||
{# List filters by group #}
|
||||
{% for heading, fields in filter_form.fieldsets %}
|
||||
<div class="col col-12">
|
||||
{% if heading %}
|
||||
<h6>{{ heading }}</h6>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for name in fields %}
|
||||
{% with field=filter_form|get_item:name %}
|
||||
{% render_field field %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if not forloop.last %}
|
||||
<hr class="card-divider mt-0" />
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
{# List all non-customfield filters as declared in the form class #}
|
||||
{% for field in filter_form.visible_fields %}
|
||||
{% if not filter_form.custom_fields or field.name not in filter_form.custom_fields %}
|
||||
@ -30,7 +31,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if filter_form.custom_fields %}
|
||||
{# List all custom field filters #}
|
||||
<hr class="card-divider mt-0" />
|
||||
|
@ -8,7 +8,7 @@
|
||||
<form method="post" action="" id="preferences-update">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for group, fields in form.Meta.fieldsets %}
|
||||
{% for group, fields in form.fieldsets %}
|
||||
<div class="field-group my-5">
|
||||
<div class="row mb-2">
|
||||
<h5 class="offset-sm-3">{{ group }}</h5>
|
||||
|
@ -1,6 +1,6 @@
|
||||
from django import forms
|
||||
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import *
|
||||
from utilities.forms import DynamicModelChoiceField
|
||||
|
||||
@ -17,7 +17,7 @@ __all__ = (
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class TenantGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -31,11 +31,10 @@ class TenantGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['parent', 'description']
|
||||
nullable_fields = ('parent', 'description')
|
||||
|
||||
|
||||
class TenantBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class TenantBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -45,17 +44,14 @@ class TenantBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'group',
|
||||
]
|
||||
nullable_fields = ('group',)
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ContactGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -69,11 +65,10 @@ class ContactGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['parent', 'description']
|
||||
nullable_fields = ('parent', 'description')
|
||||
|
||||
|
||||
class ContactRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ContactRoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ContactRole.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -83,11 +78,10 @@ class ContactRoleBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['description']
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class ContactBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ContactBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Contact.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -112,5 +106,4 @@ class ContactBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['group', 'title', 'phone', 'email', 'address', 'comments']
|
||||
nullable_fields = ('group', 'title', 'phone', 'email', 'address', 'comments')
|
||||
|
@ -1,4 +1,4 @@
|
||||
from extras.forms import CustomFieldModelCSVForm
|
||||
from netbox.forms import NetBoxModelCSVForm
|
||||
from tenancy.models import *
|
||||
from utilities.forms import CSVModelChoiceField, SlugField
|
||||
|
||||
@ -15,7 +15,7 @@ __all__ = (
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantGroupCSVForm(CustomFieldModelCSVForm):
|
||||
class TenantGroupCSVForm(NetBoxModelCSVForm):
|
||||
parent = CSVModelChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
required=False,
|
||||
@ -29,7 +29,7 @@ class TenantGroupCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'slug', 'parent', 'description')
|
||||
|
||||
|
||||
class TenantCSVForm(CustomFieldModelCSVForm):
|
||||
class TenantCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
group = CSVModelChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
@ -47,7 +47,7 @@ class TenantCSVForm(CustomFieldModelCSVForm):
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupCSVForm(CustomFieldModelCSVForm):
|
||||
class ContactGroupCSVForm(NetBoxModelCSVForm):
|
||||
parent = CSVModelChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False,
|
||||
@ -61,7 +61,7 @@ class ContactGroupCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'slug', 'parent', 'description')
|
||||
|
||||
|
||||
class ContactRoleCSVForm(CustomFieldModelCSVForm):
|
||||
class ContactRoleCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@ -69,7 +69,7 @@ class ContactRoleCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'slug', 'description')
|
||||
|
||||
|
||||
class ContactCSVForm(CustomFieldModelCSVForm):
|
||||
class ContactCSVForm(NetBoxModelCSVForm):
|
||||
group = CSVModelChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False,
|
||||
|
@ -1,6 +1,6 @@
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from extras.forms import CustomFieldModelFilterForm
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.models import *
|
||||
from utilities.forms import DynamicModelMultipleChoiceField, TagFilterField
|
||||
|
||||
@ -17,7 +17,7 @@ __all__ = (
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantGroupFilterForm(CustomFieldModelFilterForm):
|
||||
class TenantGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
model = TenantGroup
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
@ -27,12 +27,8 @@ class TenantGroupFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class TenantFilterForm(CustomFieldModelFilterForm):
|
||||
class TenantFilterForm(NetBoxModelFilterSetForm):
|
||||
model = Tenant
|
||||
field_groups = (
|
||||
('q', 'tag'),
|
||||
('group_id',),
|
||||
)
|
||||
group_id = DynamicModelMultipleChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
required=False,
|
||||
@ -46,7 +42,7 @@ class TenantFilterForm(CustomFieldModelFilterForm):
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupFilterForm(CustomFieldModelFilterForm):
|
||||
class ContactGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ContactGroup
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
@ -56,17 +52,13 @@ class ContactGroupFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ContactRoleFilterForm(CustomFieldModelFilterForm):
|
||||
class ContactRoleFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ContactRole
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ContactFilterForm(CustomFieldModelFilterForm):
|
||||
class ContactFilterForm(NetBoxModelFilterSetForm):
|
||||
model = Contact
|
||||
field_groups = (
|
||||
('q', 'tag'),
|
||||
('group_id',),
|
||||
)
|
||||
group_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False,
|
||||
|
@ -1,7 +1,7 @@
|
||||
from django import forms
|
||||
|
||||
from extras.forms import CustomFieldModelForm
|
||||
from extras.models import Tag
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.models import *
|
||||
from utilities.forms import (
|
||||
BootstrapMixin, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, SmallTextarea,
|
||||
@ -22,7 +22,7 @@ __all__ = (
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantGroupForm(CustomFieldModelForm):
|
||||
class TenantGroupForm(NetBoxModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
required=False
|
||||
@ -40,7 +40,7 @@ class TenantGroupForm(CustomFieldModelForm):
|
||||
]
|
||||
|
||||
|
||||
class TenantForm(CustomFieldModelForm):
|
||||
class TenantForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
group = DynamicModelChoiceField(
|
||||
queryset=TenantGroup.objects.all(),
|
||||
@ -52,21 +52,22 @@ class TenantForm(CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Tenant', ('name', 'slug', 'group', 'description', 'tags')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Tenant
|
||||
fields = (
|
||||
'name', 'slug', 'group', 'description', 'comments', 'tags',
|
||||
)
|
||||
fieldsets = (
|
||||
('Tenant', ('name', 'slug', 'group', 'description', 'tags')),
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Contacts
|
||||
#
|
||||
|
||||
class ContactGroupForm(CustomFieldModelForm):
|
||||
class ContactGroupForm(NetBoxModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False
|
||||
@ -82,7 +83,7 @@ class ContactGroupForm(CustomFieldModelForm):
|
||||
fields = ('parent', 'name', 'slug', 'description', 'tags')
|
||||
|
||||
|
||||
class ContactRoleForm(CustomFieldModelForm):
|
||||
class ContactRoleForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -94,7 +95,7 @@ class ContactRoleForm(CustomFieldModelForm):
|
||||
fields = ('name', 'slug', 'description', 'tags')
|
||||
|
||||
|
||||
class ContactForm(CustomFieldModelForm):
|
||||
class ContactForm(NetBoxModelForm):
|
||||
group = DynamicModelChoiceField(
|
||||
queryset=ContactGroup.objects.all(),
|
||||
required=False
|
||||
@ -105,14 +106,15 @@ class ContactForm(CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Contact', ('group', 'name', 'title', 'phone', 'email', 'address', 'tags')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = (
|
||||
'group', 'name', 'title', 'phone', 'email', 'address', 'comments', 'tags',
|
||||
)
|
||||
fieldsets = (
|
||||
('Contact', ('group', 'name', 'title', 'phone', 'email', 'address', 'tags')),
|
||||
)
|
||||
widgets = {
|
||||
'address': SmallTextarea(attrs={'rows': 3}),
|
||||
}
|
||||
|
@ -40,20 +40,20 @@ class UserConfigFormMetaclass(forms.models.ModelFormMetaclass):
|
||||
|
||||
|
||||
class UserConfigForm(BootstrapMixin, forms.ModelForm, metaclass=UserConfigFormMetaclass):
|
||||
fieldsets = (
|
||||
('User Interface', (
|
||||
'pagination.per_page',
|
||||
'pagination.placement',
|
||||
'ui.colormode',
|
||||
)),
|
||||
('Miscellaneous', (
|
||||
'data_format',
|
||||
)),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = UserConfig
|
||||
fields = ()
|
||||
fieldsets = (
|
||||
('User Interface', (
|
||||
'pagination.per_page',
|
||||
'pagination.placement',
|
||||
'ui.colormode',
|
||||
)),
|
||||
('Miscellaneous', (
|
||||
'data_format',
|
||||
)),
|
||||
)
|
||||
|
||||
def __init__(self, *args, instance=None, **kwargs):
|
||||
|
||||
|
@ -1,526 +0,0 @@
|
||||
import csv
|
||||
import json
|
||||
import re
|
||||
from io import StringIO
|
||||
from netaddr import AddrFormatError, EUI
|
||||
|
||||
import django_filters
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
from django.db.models import Count, Q
|
||||
from django.forms import BoundField
|
||||
from django.forms.fields import JSONField as _JSONField, InvalidJSONInput
|
||||
from django.urls import reverse
|
||||
|
||||
from utilities.choices import unpack_grouped_choices
|
||||
from utilities.utils import content_type_identifier, content_type_name
|
||||
from utilities.validators import EnhancedURLValidator
|
||||
from . import widgets
|
||||
from .constants import *
|
||||
from .utils import expand_alphanumeric_pattern, expand_ipaddress_pattern, parse_csv, validate_csv
|
||||
|
||||
__all__ = (
|
||||
'ColorField',
|
||||
'CommentField',
|
||||
'ContentTypeChoiceField',
|
||||
'ContentTypeMultipleChoiceField',
|
||||
'CSVChoiceField',
|
||||
'CSVContentTypeField',
|
||||
'CSVDataField',
|
||||
'CSVFileField',
|
||||
'CSVModelChoiceField',
|
||||
'CSVMultipleChoiceField',
|
||||
'CSVMultipleContentTypeField',
|
||||
'CSVTypedChoiceField',
|
||||
'DynamicModelChoiceField',
|
||||
'DynamicModelMultipleChoiceField',
|
||||
'ExpandableIPAddressField',
|
||||
'ExpandableNameField',
|
||||
'JSONField',
|
||||
'LaxURLField',
|
||||
'MACAddressField',
|
||||
'SlugField',
|
||||
'TagFilterField',
|
||||
)
|
||||
|
||||
|
||||
class CommentField(forms.CharField):
|
||||
"""
|
||||
A textarea with support for Markdown rendering. Exists mostly just to add a standard help_text.
|
||||
"""
|
||||
widget = forms.Textarea
|
||||
default_label = ''
|
||||
# TODO: Port Markdown cheat sheet to internal documentation
|
||||
default_helptext = '<i class="mdi mdi-information-outline"></i> '\
|
||||
'<a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" target="_blank" tabindex="-1">'\
|
||||
'Markdown</a> syntax is supported'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
required = kwargs.pop('required', False)
|
||||
label = kwargs.pop('label', self.default_label)
|
||||
help_text = kwargs.pop('help_text', self.default_helptext)
|
||||
super().__init__(required=required, label=label, help_text=help_text, *args, **kwargs)
|
||||
|
||||
|
||||
class SlugField(forms.SlugField):
|
||||
"""
|
||||
Extend the built-in SlugField to automatically populate from a field called `name` unless otherwise specified.
|
||||
"""
|
||||
|
||||
def __init__(self, slug_source='name', *args, **kwargs):
|
||||
label = kwargs.pop('label', "Slug")
|
||||
help_text = kwargs.pop('help_text', "URL-friendly unique shorthand")
|
||||
widget = kwargs.pop('widget', widgets.SlugWidget)
|
||||
super().__init__(label=label, help_text=help_text, widget=widget, *args, **kwargs)
|
||||
self.widget.attrs['slug-source'] = slug_source
|
||||
|
||||
|
||||
class ColorField(forms.CharField):
|
||||
"""
|
||||
A field which represents a color in hexadecimal RRGGBB format.
|
||||
"""
|
||||
widget = widgets.ColorSelect
|
||||
|
||||
|
||||
class TagFilterField(forms.MultipleChoiceField):
|
||||
"""
|
||||
A filter field for the tags of a model. Only the tags used by a model are displayed.
|
||||
|
||||
:param model: The model of the filter
|
||||
"""
|
||||
widget = widgets.StaticSelectMultiple
|
||||
|
||||
def __init__(self, model, *args, **kwargs):
|
||||
def get_choices():
|
||||
tags = model.tags.annotate(
|
||||
count=Count('extras_taggeditem_items')
|
||||
).order_by('name')
|
||||
return [
|
||||
(str(tag.slug), '{} ({})'.format(tag.name, tag.count)) for tag in tags
|
||||
]
|
||||
|
||||
# Choices are fetched each time the form is initialized
|
||||
super().__init__(label='Tags', choices=get_choices, required=False, *args, **kwargs)
|
||||
|
||||
|
||||
class LaxURLField(forms.URLField):
|
||||
"""
|
||||
Modifies Django's built-in URLField to remove the requirement for fully-qualified domain names
|
||||
(e.g. http://myserver/ is valid)
|
||||
"""
|
||||
default_validators = [EnhancedURLValidator()]
|
||||
|
||||
|
||||
class JSONField(_JSONField):
|
||||
"""
|
||||
Custom wrapper around Django's built-in JSONField to avoid presenting "null" as the default text.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.help_text:
|
||||
self.help_text = 'Enter context data in <a href="https://json.org/">JSON</a> format.'
|
||||
self.widget.attrs['placeholder'] = ''
|
||||
|
||||
def prepare_value(self, value):
|
||||
if isinstance(value, InvalidJSONInput):
|
||||
return value
|
||||
if value is None:
|
||||
return ''
|
||||
return json.dumps(value, sort_keys=True, indent=4)
|
||||
|
||||
|
||||
class MACAddressField(forms.Field):
|
||||
widget = forms.CharField
|
||||
default_error_messages = {
|
||||
'invalid': 'MAC address must be in EUI-48 format',
|
||||
}
|
||||
|
||||
def to_python(self, value):
|
||||
value = super().to_python(value)
|
||||
|
||||
# Validate MAC address format
|
||||
try:
|
||||
value = EUI(value.strip())
|
||||
except AddrFormatError:
|
||||
raise forms.ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
|
||||
return value
|
||||
|
||||
|
||||
#
|
||||
# Content type fields
|
||||
#
|
||||
|
||||
class ContentTypeChoiceMixin:
|
||||
|
||||
def __init__(self, queryset, *args, **kwargs):
|
||||
# Order ContentTypes by app_label
|
||||
queryset = queryset.order_by('app_label', 'model')
|
||||
super().__init__(queryset, *args, **kwargs)
|
||||
|
||||
def label_from_instance(self, obj):
|
||||
try:
|
||||
return content_type_name(obj)
|
||||
except AttributeError:
|
||||
return super().label_from_instance(obj)
|
||||
|
||||
|
||||
class ContentTypeChoiceField(ContentTypeChoiceMixin, forms.ModelChoiceField):
|
||||
widget = widgets.StaticSelect
|
||||
|
||||
|
||||
class ContentTypeMultipleChoiceField(ContentTypeChoiceMixin, forms.ModelMultipleChoiceField):
|
||||
widget = widgets.StaticSelectMultiple
|
||||
|
||||
|
||||
#
|
||||
# CSV fields
|
||||
#
|
||||
|
||||
class CSVDataField(forms.CharField):
|
||||
"""
|
||||
A CharField (rendered as a Textarea) which accepts CSV-formatted data. It returns data as a two-tuple: The first
|
||||
item is a dictionary of column headers, mapping field names to the attribute by which they match a related object
|
||||
(where applicable). The second item is a list of dictionaries, each representing a discrete row of CSV data.
|
||||
|
||||
:param from_form: The form from which the field derives its validation rules.
|
||||
"""
|
||||
widget = forms.Textarea
|
||||
|
||||
def __init__(self, from_form, *args, **kwargs):
|
||||
|
||||
form = from_form()
|
||||
self.model = form.Meta.model
|
||||
self.fields = form.fields
|
||||
self.required_fields = [
|
||||
name for name, field in form.fields.items() if field.required
|
||||
]
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.strip = False
|
||||
if not self.label:
|
||||
self.label = ''
|
||||
if not self.initial:
|
||||
self.initial = ','.join(self.required_fields) + '\n'
|
||||
if not self.help_text:
|
||||
self.help_text = 'Enter the list of column headers followed by one line per record to be imported, using ' \
|
||||
'commas to separate values. Multi-line data and values containing commas may be wrapped ' \
|
||||
'in double quotes.'
|
||||
|
||||
def to_python(self, value):
|
||||
reader = csv.reader(StringIO(value.strip()))
|
||||
|
||||
return parse_csv(reader)
|
||||
|
||||
def validate(self, value):
|
||||
headers, records = value
|
||||
validate_csv(headers, self.fields, self.required_fields)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class CSVFileField(forms.FileField):
|
||||
"""
|
||||
A FileField (rendered as a file input button) which accepts a file containing CSV-formatted data. It returns
|
||||
data as a two-tuple: The first item is a dictionary of column headers, mapping field names to the attribute
|
||||
by which they match a related object (where applicable). The second item is a list of dictionaries, each
|
||||
representing a discrete row of CSV data.
|
||||
|
||||
:param from_form: The form from which the field derives its validation rules.
|
||||
"""
|
||||
|
||||
def __init__(self, from_form, *args, **kwargs):
|
||||
|
||||
form = from_form()
|
||||
self.model = form.Meta.model
|
||||
self.fields = form.fields
|
||||
self.required_fields = [
|
||||
name for name, field in form.fields.items() if field.required
|
||||
]
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_python(self, file):
|
||||
if file is None:
|
||||
return None
|
||||
|
||||
csv_str = file.read().decode('utf-8').strip()
|
||||
reader = csv.reader(StringIO(csv_str))
|
||||
headers, records = parse_csv(reader)
|
||||
|
||||
return headers, records
|
||||
|
||||
def validate(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
headers, records = value
|
||||
validate_csv(headers, self.fields, self.required_fields)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class CSVChoicesMixin:
|
||||
STATIC_CHOICES = True
|
||||
|
||||
def __init__(self, *, choices=(), **kwargs):
|
||||
super().__init__(choices=choices, **kwargs)
|
||||
self.choices = unpack_grouped_choices(choices)
|
||||
|
||||
|
||||
class CSVChoiceField(CSVChoicesMixin, forms.ChoiceField):
|
||||
"""
|
||||
A CSV field which accepts a single selection value.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CSVMultipleChoiceField(CSVChoicesMixin, forms.MultipleChoiceField):
|
||||
"""
|
||||
A CSV field which accepts multiple selection values.
|
||||
"""
|
||||
def to_python(self, value):
|
||||
if not value:
|
||||
return []
|
||||
if not isinstance(value, str):
|
||||
raise forms.ValidationError(f"Invalid value for a multiple choice field: {value}")
|
||||
return value.split(',')
|
||||
|
||||
|
||||
class CSVTypedChoiceField(forms.TypedChoiceField):
|
||||
STATIC_CHOICES = True
|
||||
|
||||
|
||||
class CSVModelChoiceField(forms.ModelChoiceField):
|
||||
"""
|
||||
Provides additional validation for model choices entered as CSV data.
|
||||
"""
|
||||
default_error_messages = {
|
||||
'invalid_choice': 'Object not found.',
|
||||
}
|
||||
|
||||
def to_python(self, value):
|
||||
try:
|
||||
return super().to_python(value)
|
||||
except MultipleObjectsReturned:
|
||||
raise forms.ValidationError(
|
||||
f'"{value}" is not a unique value for this field; multiple objects were found'
|
||||
)
|
||||
|
||||
|
||||
class CSVContentTypeField(CSVModelChoiceField):
|
||||
"""
|
||||
Reference a ContentType in the form <app>.<model>
|
||||
"""
|
||||
STATIC_CHOICES = True
|
||||
|
||||
def prepare_value(self, value):
|
||||
return content_type_identifier(value)
|
||||
|
||||
def to_python(self, value):
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
app_label, model = value.split('.')
|
||||
except ValueError:
|
||||
raise forms.ValidationError(f'Object type must be specified as "<app>.<model>"')
|
||||
try:
|
||||
return self.queryset.get(app_label=app_label, model=model)
|
||||
except ObjectDoesNotExist:
|
||||
raise forms.ValidationError(f'Invalid object type')
|
||||
|
||||
|
||||
class CSVMultipleContentTypeField(forms.ModelMultipleChoiceField):
|
||||
STATIC_CHOICES = True
|
||||
|
||||
# TODO: Improve validation of selected ContentTypes
|
||||
def prepare_value(self, value):
|
||||
if type(value) is str:
|
||||
ct_filter = Q()
|
||||
for name in value.split(','):
|
||||
app_label, model = name.split('.')
|
||||
ct_filter |= Q(app_label=app_label, model=model)
|
||||
return list(ContentType.objects.filter(ct_filter).values_list('pk', flat=True))
|
||||
return content_type_identifier(value)
|
||||
|
||||
|
||||
#
|
||||
# Expansion fields
|
||||
#
|
||||
|
||||
class ExpandableNameField(forms.CharField):
|
||||
"""
|
||||
A field which allows for numeric range expansion
|
||||
Example: 'Gi0/[1-3]' => ['Gi0/1', 'Gi0/2', 'Gi0/3']
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.help_text:
|
||||
self.help_text = """
|
||||
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
||||
are not supported. Example: <code>[ge,xe]-0/0/[0-9]</code>
|
||||
"""
|
||||
|
||||
def to_python(self, value):
|
||||
if not value:
|
||||
return ''
|
||||
if re.search(ALPHANUMERIC_EXPANSION_PATTERN, value):
|
||||
return list(expand_alphanumeric_pattern(value))
|
||||
return [value]
|
||||
|
||||
|
||||
class ExpandableIPAddressField(forms.CharField):
|
||||
"""
|
||||
A field which allows for expansion of IP address ranges
|
||||
Example: '192.0.2.[1-254]/24' => ['192.0.2.1/24', '192.0.2.2/24', '192.0.2.3/24' ... '192.0.2.254/24']
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.help_text:
|
||||
self.help_text = 'Specify a numeric range to create multiple IPs.<br />'\
|
||||
'Example: <code>192.0.2.[1,5,100-254]/24</code>'
|
||||
|
||||
def to_python(self, value):
|
||||
# Hackish address family detection but it's all we have to work with
|
||||
if '.' in value and re.search(IP4_EXPANSION_PATTERN, value):
|
||||
return list(expand_ipaddress_pattern(value, 4))
|
||||
elif ':' in value and re.search(IP6_EXPANSION_PATTERN, value):
|
||||
return list(expand_ipaddress_pattern(value, 6))
|
||||
return [value]
|
||||
|
||||
|
||||
#
|
||||
# Dynamic fields
|
||||
#
|
||||
|
||||
class DynamicModelChoiceMixin:
|
||||
"""
|
||||
:param query_params: A dictionary of additional key/value pairs to attach to the API request
|
||||
:param initial_params: A dictionary of child field references to use for selecting a parent field's initial value
|
||||
:param null_option: The string used to represent a null selection (if any)
|
||||
:param disabled_indicator: The name of the field which, if populated, will disable selection of the
|
||||
choice (optional)
|
||||
:param str fetch_trigger: The event type which will cause the select element to
|
||||
fetch data from the API. Must be 'load', 'open', or 'collapse'. (optional)
|
||||
"""
|
||||
filter = django_filters.ModelChoiceFilter
|
||||
widget = widgets.APISelect
|
||||
|
||||
def __init__(self, query_params=None, initial_params=None, null_option=None, disabled_indicator=None,
|
||||
fetch_trigger=None, empty_label=None, *args, **kwargs):
|
||||
self.query_params = query_params or {}
|
||||
self.initial_params = initial_params or {}
|
||||
self.null_option = null_option
|
||||
self.disabled_indicator = disabled_indicator
|
||||
self.fetch_trigger = fetch_trigger
|
||||
|
||||
# to_field_name is set by ModelChoiceField.__init__(), but we need to set it early for reference
|
||||
# by widget_attrs()
|
||||
self.to_field_name = kwargs.get('to_field_name')
|
||||
self.empty_option = empty_label or ""
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
attrs = {
|
||||
'data-empty-option': self.empty_option
|
||||
}
|
||||
|
||||
# Set value-field attribute if the field specifies to_field_name
|
||||
if self.to_field_name:
|
||||
attrs['value-field'] = self.to_field_name
|
||||
|
||||
# Set the string used to represent a null option
|
||||
if self.null_option is not None:
|
||||
attrs['data-null-option'] = self.null_option
|
||||
|
||||
# Set the disabled indicator, if any
|
||||
if self.disabled_indicator is not None:
|
||||
attrs['disabled-indicator'] = self.disabled_indicator
|
||||
|
||||
# Set the fetch trigger, if any.
|
||||
if self.fetch_trigger is not None:
|
||||
attrs['data-fetch-trigger'] = self.fetch_trigger
|
||||
|
||||
# Attach any static query parameters
|
||||
if (len(self.query_params) > 0):
|
||||
widget.add_query_params(self.query_params)
|
||||
|
||||
return attrs
|
||||
|
||||
def get_bound_field(self, form, field_name):
|
||||
bound_field = BoundField(form, self, field_name)
|
||||
|
||||
# Set initial value based on prescribed child fields (if not already set)
|
||||
if not self.initial and self.initial_params:
|
||||
filter_kwargs = {}
|
||||
for kwarg, child_field in self.initial_params.items():
|
||||
value = form.initial.get(child_field.lstrip('$'))
|
||||
if value:
|
||||
filter_kwargs[kwarg] = value
|
||||
if filter_kwargs:
|
||||
self.initial = self.queryset.filter(**filter_kwargs).first()
|
||||
|
||||
# Modify the QuerySet of the field before we return it. Limit choices to any data already bound: Options
|
||||
# will be populated on-demand via the APISelect widget.
|
||||
data = bound_field.value()
|
||||
if data:
|
||||
field_name = getattr(self, 'to_field_name') or 'pk'
|
||||
filter = self.filter(field_name=field_name)
|
||||
try:
|
||||
self.queryset = filter.filter(self.queryset, data)
|
||||
except (TypeError, ValueError):
|
||||
# Catch any error caused by invalid initial data passed from the user
|
||||
self.queryset = self.queryset.none()
|
||||
else:
|
||||
self.queryset = self.queryset.none()
|
||||
|
||||
# Set the data URL on the APISelect widget (if not already set)
|
||||
widget = bound_field.field.widget
|
||||
if not widget.attrs.get('data-url'):
|
||||
app_label = self.queryset.model._meta.app_label
|
||||
model_name = self.queryset.model._meta.model_name
|
||||
data_url = reverse('{}-api:{}-list'.format(app_label, model_name))
|
||||
widget.attrs['data-url'] = data_url
|
||||
|
||||
return bound_field
|
||||
|
||||
|
||||
class DynamicModelChoiceField(DynamicModelChoiceMixin, forms.ModelChoiceField):
|
||||
"""
|
||||
Override get_bound_field() to avoid pre-populating field choices with a SQL query. The field will be
|
||||
rendered only with choices set via bound data. Choices are populated on-demand via the APISelect widget.
|
||||
"""
|
||||
|
||||
def clean(self, value):
|
||||
"""
|
||||
When null option is enabled and "None" is sent as part of a form to be submitted, it is sent as the
|
||||
string 'null'. This will check for that condition and gracefully handle the conversion to a NoneType.
|
||||
"""
|
||||
if self.null_option is not None and value == settings.FILTERS_NULL_CHOICE_VALUE:
|
||||
return None
|
||||
return super().clean(value)
|
||||
|
||||
|
||||
class DynamicModelMultipleChoiceField(DynamicModelChoiceMixin, forms.ModelMultipleChoiceField):
|
||||
"""
|
||||
A multiple-choice version of DynamicModelChoiceField.
|
||||
"""
|
||||
filter = django_filters.ModelMultipleChoiceFilter
|
||||
widget = widgets.APISelectMultiple
|
||||
|
||||
def clean(self, value):
|
||||
"""
|
||||
When null option is enabled and "None" is sent as part of a form to be submitted, it is sent as the
|
||||
string 'null'. This will check for that condition and gracefully handle the conversion to a NoneType.
|
||||
"""
|
||||
if self.null_option is not None and settings.FILTERS_NULL_CHOICE_VALUE in value:
|
||||
value = [v for v in value if v != settings.FILTERS_NULL_CHOICE_VALUE]
|
||||
return [None, *value]
|
||||
return super().clean(value)
|
5
netbox/utilities/forms/fields/__init__.py
Normal file
5
netbox/utilities/forms/fields/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
from .content_types import *
|
||||
from .csv import *
|
||||
from .dynamic import *
|
||||
from .expandable import *
|
||||
from .fields import *
|
37
netbox/utilities/forms/fields/content_types.py
Normal file
37
netbox/utilities/forms/fields/content_types.py
Normal file
@ -0,0 +1,37 @@
|
||||
from django import forms
|
||||
|
||||
from utilities.forms import widgets
|
||||
from utilities.utils import content_type_name
|
||||
|
||||
__all__ = (
|
||||
'ContentTypeChoiceField',
|
||||
'ContentTypeMultipleChoiceField',
|
||||
)
|
||||
|
||||
|
||||
class ContentTypeChoiceMixin:
|
||||
|
||||
def __init__(self, queryset, *args, **kwargs):
|
||||
# Order ContentTypes by app_label
|
||||
queryset = queryset.order_by('app_label', 'model')
|
||||
super().__init__(queryset, *args, **kwargs)
|
||||
|
||||
def label_from_instance(self, obj):
|
||||
try:
|
||||
return content_type_name(obj)
|
||||
except AttributeError:
|
||||
return super().label_from_instance(obj)
|
||||
|
||||
|
||||
class ContentTypeChoiceField(ContentTypeChoiceMixin, forms.ModelChoiceField):
|
||||
"""
|
||||
Selection field for a single content type.
|
||||
"""
|
||||
widget = widgets.StaticSelect
|
||||
|
||||
|
||||
class ContentTypeMultipleChoiceField(ContentTypeChoiceMixin, forms.ModelMultipleChoiceField):
|
||||
"""
|
||||
Selection field for one or more content types.
|
||||
"""
|
||||
widget = widgets.StaticSelectMultiple
|
193
netbox/utilities/forms/fields/csv.py
Normal file
193
netbox/utilities/forms/fields/csv.py
Normal file
@ -0,0 +1,193 @@
|
||||
import csv
|
||||
from io import StringIO
|
||||
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
from django.db.models import Q
|
||||
|
||||
from utilities.choices import unpack_grouped_choices
|
||||
from utilities.forms.utils import parse_csv, validate_csv
|
||||
from utilities.utils import content_type_identifier
|
||||
|
||||
__all__ = (
|
||||
'CSVChoiceField',
|
||||
'CSVContentTypeField',
|
||||
'CSVDataField',
|
||||
'CSVFileField',
|
||||
'CSVModelChoiceField',
|
||||
'CSVMultipleChoiceField',
|
||||
'CSVMultipleContentTypeField',
|
||||
'CSVTypedChoiceField',
|
||||
)
|
||||
|
||||
|
||||
class CSVDataField(forms.CharField):
|
||||
"""
|
||||
A CharField (rendered as a Textarea) which accepts CSV-formatted data. It returns data as a two-tuple: The first
|
||||
item is a dictionary of column headers, mapping field names to the attribute by which they match a related object
|
||||
(where applicable). The second item is a list of dictionaries, each representing a discrete row of CSV data.
|
||||
|
||||
:param from_form: The form from which the field derives its validation rules.
|
||||
"""
|
||||
widget = forms.Textarea
|
||||
|
||||
def __init__(self, from_form, *args, **kwargs):
|
||||
|
||||
form = from_form()
|
||||
self.model = form.Meta.model
|
||||
self.fields = form.fields
|
||||
self.required_fields = [
|
||||
name for name, field in form.fields.items() if field.required
|
||||
]
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.strip = False
|
||||
if not self.label:
|
||||
self.label = ''
|
||||
if not self.initial:
|
||||
self.initial = ','.join(self.required_fields) + '\n'
|
||||
if not self.help_text:
|
||||
self.help_text = 'Enter the list of column headers followed by one line per record to be imported, using ' \
|
||||
'commas to separate values. Multi-line data and values containing commas may be wrapped ' \
|
||||
'in double quotes.'
|
||||
|
||||
def to_python(self, value):
|
||||
reader = csv.reader(StringIO(value.strip()))
|
||||
|
||||
return parse_csv(reader)
|
||||
|
||||
def validate(self, value):
|
||||
headers, records = value
|
||||
validate_csv(headers, self.fields, self.required_fields)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class CSVFileField(forms.FileField):
|
||||
"""
|
||||
A FileField (rendered as a file input button) which accepts a file containing CSV-formatted data. It returns
|
||||
data as a two-tuple: The first item is a dictionary of column headers, mapping field names to the attribute
|
||||
by which they match a related object (where applicable). The second item is a list of dictionaries, each
|
||||
representing a discrete row of CSV data.
|
||||
|
||||
:param from_form: The form from which the field derives its validation rules.
|
||||
"""
|
||||
|
||||
def __init__(self, from_form, *args, **kwargs):
|
||||
|
||||
form = from_form()
|
||||
self.model = form.Meta.model
|
||||
self.fields = form.fields
|
||||
self.required_fields = [
|
||||
name for name, field in form.fields.items() if field.required
|
||||
]
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_python(self, file):
|
||||
if file is None:
|
||||
return None
|
||||
|
||||
csv_str = file.read().decode('utf-8').strip()
|
||||
reader = csv.reader(StringIO(csv_str))
|
||||
headers, records = parse_csv(reader)
|
||||
|
||||
return headers, records
|
||||
|
||||
def validate(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
headers, records = value
|
||||
validate_csv(headers, self.fields, self.required_fields)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class CSVChoicesMixin:
|
||||
STATIC_CHOICES = True
|
||||
|
||||
def __init__(self, *, choices=(), **kwargs):
|
||||
super().__init__(choices=choices, **kwargs)
|
||||
self.choices = unpack_grouped_choices(choices)
|
||||
|
||||
|
||||
class CSVChoiceField(CSVChoicesMixin, forms.ChoiceField):
|
||||
"""
|
||||
A CSV field which accepts a single selection value.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CSVMultipleChoiceField(CSVChoicesMixin, forms.MultipleChoiceField):
|
||||
"""
|
||||
A CSV field which accepts multiple selection values.
|
||||
"""
|
||||
def to_python(self, value):
|
||||
if not value:
|
||||
return []
|
||||
if not isinstance(value, str):
|
||||
raise forms.ValidationError(f"Invalid value for a multiple choice field: {value}")
|
||||
return value.split(',')
|
||||
|
||||
|
||||
class CSVTypedChoiceField(forms.TypedChoiceField):
|
||||
STATIC_CHOICES = True
|
||||
|
||||
|
||||
class CSVModelChoiceField(forms.ModelChoiceField):
|
||||
"""
|
||||
Extends Django's `ModelChoiceField` to provide additional validation for CSV values.
|
||||
"""
|
||||
default_error_messages = {
|
||||
'invalid_choice': 'Object not found.',
|
||||
}
|
||||
|
||||
def to_python(self, value):
|
||||
try:
|
||||
return super().to_python(value)
|
||||
except MultipleObjectsReturned:
|
||||
raise forms.ValidationError(
|
||||
f'"{value}" is not a unique value for this field; multiple objects were found'
|
||||
)
|
||||
|
||||
|
||||
class CSVContentTypeField(CSVModelChoiceField):
|
||||
"""
|
||||
CSV field for referencing a single content type, in the form `<app>.<model>`.
|
||||
"""
|
||||
STATIC_CHOICES = True
|
||||
|
||||
def prepare_value(self, value):
|
||||
return content_type_identifier(value)
|
||||
|
||||
def to_python(self, value):
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
app_label, model = value.split('.')
|
||||
except ValueError:
|
||||
raise forms.ValidationError(f'Object type must be specified as "<app>.<model>"')
|
||||
try:
|
||||
return self.queryset.get(app_label=app_label, model=model)
|
||||
except ObjectDoesNotExist:
|
||||
raise forms.ValidationError(f'Invalid object type')
|
||||
|
||||
|
||||
class CSVMultipleContentTypeField(forms.ModelMultipleChoiceField):
|
||||
"""
|
||||
CSV field for referencing one or more content types, in the form `<app>.<model>`.
|
||||
"""
|
||||
STATIC_CHOICES = True
|
||||
|
||||
# TODO: Improve validation of selected ContentTypes
|
||||
def prepare_value(self, value):
|
||||
if type(value) is str:
|
||||
ct_filter = Q()
|
||||
for name in value.split(','):
|
||||
app_label, model = name.split('.')
|
||||
ct_filter |= Q(app_label=app_label, model=model)
|
||||
return list(ContentType.objects.filter(ct_filter).values_list('pk', flat=True))
|
||||
return content_type_identifier(value)
|
141
netbox/utilities/forms/fields/dynamic.py
Normal file
141
netbox/utilities/forms/fields/dynamic.py
Normal file
@ -0,0 +1,141 @@
|
||||
import django_filters
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.forms import BoundField
|
||||
from django.urls import reverse
|
||||
|
||||
from utilities.forms import widgets
|
||||
|
||||
__all__ = (
|
||||
'DynamicModelChoiceField',
|
||||
'DynamicModelMultipleChoiceField',
|
||||
)
|
||||
|
||||
|
||||
class DynamicModelChoiceMixin:
|
||||
"""
|
||||
Override `get_bound_field()` to avoid pre-populating field choices with a SQL query. The field will be
|
||||
rendered only with choices set via bound data. Choices are populated on-demand via the APISelect widget.
|
||||
|
||||
Attributes:
|
||||
query_params: A dictionary of additional key/value pairs to attach to the API request
|
||||
initial_params: A dictionary of child field references to use for selecting a parent field's initial value
|
||||
null_option: The string used to represent a null selection (if any)
|
||||
disabled_indicator: The name of the field which, if populated, will disable selection of the
|
||||
choice (optional)
|
||||
fetch_trigger: The event type which will cause the select element to
|
||||
fetch data from the API. Must be 'load', 'open', or 'collapse'. (optional)
|
||||
"""
|
||||
filter = django_filters.ModelChoiceFilter
|
||||
widget = widgets.APISelect
|
||||
|
||||
def __init__(self, query_params=None, initial_params=None, null_option=None, disabled_indicator=None,
|
||||
fetch_trigger=None, empty_label=None, *args, **kwargs):
|
||||
self.query_params = query_params or {}
|
||||
self.initial_params = initial_params or {}
|
||||
self.null_option = null_option
|
||||
self.disabled_indicator = disabled_indicator
|
||||
self.fetch_trigger = fetch_trigger
|
||||
|
||||
# to_field_name is set by ModelChoiceField.__init__(), but we need to set it early for reference
|
||||
# by widget_attrs()
|
||||
self.to_field_name = kwargs.get('to_field_name')
|
||||
self.empty_option = empty_label or ""
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
attrs = {
|
||||
'data-empty-option': self.empty_option
|
||||
}
|
||||
|
||||
# Set value-field attribute if the field specifies to_field_name
|
||||
if self.to_field_name:
|
||||
attrs['value-field'] = self.to_field_name
|
||||
|
||||
# Set the string used to represent a null option
|
||||
if self.null_option is not None:
|
||||
attrs['data-null-option'] = self.null_option
|
||||
|
||||
# Set the disabled indicator, if any
|
||||
if self.disabled_indicator is not None:
|
||||
attrs['disabled-indicator'] = self.disabled_indicator
|
||||
|
||||
# Set the fetch trigger, if any.
|
||||
if self.fetch_trigger is not None:
|
||||
attrs['data-fetch-trigger'] = self.fetch_trigger
|
||||
|
||||
# Attach any static query parameters
|
||||
if (len(self.query_params) > 0):
|
||||
widget.add_query_params(self.query_params)
|
||||
|
||||
return attrs
|
||||
|
||||
def get_bound_field(self, form, field_name):
|
||||
bound_field = BoundField(form, self, field_name)
|
||||
|
||||
# Set initial value based on prescribed child fields (if not already set)
|
||||
if not self.initial and self.initial_params:
|
||||
filter_kwargs = {}
|
||||
for kwarg, child_field in self.initial_params.items():
|
||||
value = form.initial.get(child_field.lstrip('$'))
|
||||
if value:
|
||||
filter_kwargs[kwarg] = value
|
||||
if filter_kwargs:
|
||||
self.initial = self.queryset.filter(**filter_kwargs).first()
|
||||
|
||||
# Modify the QuerySet of the field before we return it. Limit choices to any data already bound: Options
|
||||
# will be populated on-demand via the APISelect widget.
|
||||
data = bound_field.value()
|
||||
if data:
|
||||
field_name = getattr(self, 'to_field_name') or 'pk'
|
||||
filter = self.filter(field_name=field_name)
|
||||
try:
|
||||
self.queryset = filter.filter(self.queryset, data)
|
||||
except (TypeError, ValueError):
|
||||
# Catch any error caused by invalid initial data passed from the user
|
||||
self.queryset = self.queryset.none()
|
||||
else:
|
||||
self.queryset = self.queryset.none()
|
||||
|
||||
# Set the data URL on the APISelect widget (if not already set)
|
||||
widget = bound_field.field.widget
|
||||
if not widget.attrs.get('data-url'):
|
||||
app_label = self.queryset.model._meta.app_label
|
||||
model_name = self.queryset.model._meta.model_name
|
||||
data_url = reverse('{}-api:{}-list'.format(app_label, model_name))
|
||||
widget.attrs['data-url'] = data_url
|
||||
|
||||
return bound_field
|
||||
|
||||
|
||||
class DynamicModelChoiceField(DynamicModelChoiceMixin, forms.ModelChoiceField):
|
||||
"""
|
||||
Dynamic selection field for a single object, backed by NetBox's REST API.
|
||||
"""
|
||||
def clean(self, value):
|
||||
"""
|
||||
When null option is enabled and "None" is sent as part of a form to be submitted, it is sent as the
|
||||
string 'null'. This will check for that condition and gracefully handle the conversion to a NoneType.
|
||||
"""
|
||||
if self.null_option is not None and value == settings.FILTERS_NULL_CHOICE_VALUE:
|
||||
return None
|
||||
return super().clean(value)
|
||||
|
||||
|
||||
class DynamicModelMultipleChoiceField(DynamicModelChoiceMixin, forms.ModelMultipleChoiceField):
|
||||
"""
|
||||
A multiple-choice version of `DynamicModelChoiceField`.
|
||||
"""
|
||||
filter = django_filters.ModelMultipleChoiceFilter
|
||||
widget = widgets.APISelectMultiple
|
||||
|
||||
def clean(self, value):
|
||||
"""
|
||||
When null option is enabled and "None" is sent as part of a form to be submitted, it is sent as the
|
||||
string 'null'. This will check for that condition and gracefully handle the conversion to a NoneType.
|
||||
"""
|
||||
if self.null_option is not None and settings.FILTERS_NULL_CHOICE_VALUE in value:
|
||||
value = [v for v in value if v != settings.FILTERS_NULL_CHOICE_VALUE]
|
||||
return [None, *value]
|
||||
return super().clean(value)
|
54
netbox/utilities/forms/fields/expandable.py
Normal file
54
netbox/utilities/forms/fields/expandable.py
Normal file
@ -0,0 +1,54 @@
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
|
||||
from utilities.forms.constants import *
|
||||
from utilities.forms.utils import expand_alphanumeric_pattern, expand_ipaddress_pattern
|
||||
|
||||
__all__ = (
|
||||
'ExpandableIPAddressField',
|
||||
'ExpandableNameField',
|
||||
)
|
||||
|
||||
|
||||
class ExpandableNameField(forms.CharField):
|
||||
"""
|
||||
A field which allows for numeric range expansion
|
||||
Example: 'Gi0/[1-3]' => ['Gi0/1', 'Gi0/2', 'Gi0/3']
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.help_text:
|
||||
self.help_text = """
|
||||
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
||||
are not supported. Example: <code>[ge,xe]-0/0/[0-9]</code>
|
||||
"""
|
||||
|
||||
def to_python(self, value):
|
||||
if not value:
|
||||
return ''
|
||||
if re.search(ALPHANUMERIC_EXPANSION_PATTERN, value):
|
||||
return list(expand_alphanumeric_pattern(value))
|
||||
return [value]
|
||||
|
||||
|
||||
class ExpandableIPAddressField(forms.CharField):
|
||||
"""
|
||||
A field which allows for expansion of IP address ranges
|
||||
Example: '192.0.2.[1-254]/24' => ['192.0.2.1/24', '192.0.2.2/24', '192.0.2.3/24' ... '192.0.2.254/24']
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.help_text:
|
||||
self.help_text = 'Specify a numeric range to create multiple IPs.<br />'\
|
||||
'Example: <code>192.0.2.[1,5,100-254]/24</code>'
|
||||
|
||||
def to_python(self, value):
|
||||
# Hackish address family detection but it's all we have to work with
|
||||
if '.' in value and re.search(IP4_EXPANSION_PATTERN, value):
|
||||
return list(expand_ipaddress_pattern(value, 4))
|
||||
elif ':' in value and re.search(IP6_EXPANSION_PATTERN, value):
|
||||
return list(expand_ipaddress_pattern(value, 6))
|
||||
return [value]
|
127
netbox/utilities/forms/fields/fields.py
Normal file
127
netbox/utilities/forms/fields/fields.py
Normal file
@ -0,0 +1,127 @@
|
||||
import json
|
||||
|
||||
from django import forms
|
||||
from django.db.models import Count
|
||||
from django.forms.fields import JSONField as _JSONField, InvalidJSONInput
|
||||
from netaddr import AddrFormatError, EUI
|
||||
|
||||
from utilities.forms import widgets
|
||||
from utilities.validators import EnhancedURLValidator
|
||||
|
||||
__all__ = (
|
||||
'ColorField',
|
||||
'CommentField',
|
||||
'JSONField',
|
||||
'LaxURLField',
|
||||
'MACAddressField',
|
||||
'SlugField',
|
||||
'TagFilterField',
|
||||
)
|
||||
|
||||
|
||||
class CommentField(forms.CharField):
|
||||
"""
|
||||
A textarea with support for Markdown rendering. Exists mostly just to add a standard `help_text`.
|
||||
"""
|
||||
widget = forms.Textarea
|
||||
# TODO: Port Markdown cheat sheet to internal documentation
|
||||
help_text = """
|
||||
<i class="mdi mdi-information-outline"></i>
|
||||
<a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" target="_blank" tabindex="-1">
|
||||
Markdown</a> syntax is supported
|
||||
"""
|
||||
|
||||
def __init__(self, *, help_text=help_text, required=False, **kwargs):
|
||||
super().__init__(help_text=help_text, required=required, **kwargs)
|
||||
|
||||
|
||||
class SlugField(forms.SlugField):
|
||||
"""
|
||||
Extend Django's built-in SlugField to automatically populate from a field called `name` unless otherwise specified.
|
||||
|
||||
Parameters:
|
||||
slug_source: Name of the form field from which the slug value will be derived
|
||||
"""
|
||||
widget = widgets.SlugWidget
|
||||
help_text = "URL-friendly unique shorthand"
|
||||
|
||||
def __init__(self, *, slug_source='name', help_text=help_text, **kwargs):
|
||||
super().__init__(help_text=help_text, **kwargs)
|
||||
|
||||
self.widget.attrs['slug-source'] = slug_source
|
||||
|
||||
|
||||
class ColorField(forms.CharField):
|
||||
"""
|
||||
A field which represents a color value in hexadecimal `RRGGBB` format. Utilizes NetBox's `ColorSelect` widget to
|
||||
render choices.
|
||||
"""
|
||||
widget = widgets.ColorSelect
|
||||
|
||||
|
||||
class TagFilterField(forms.MultipleChoiceField):
|
||||
"""
|
||||
A filter field for the tags of a model. Only the tags used by a model are displayed.
|
||||
|
||||
:param model: The model of the filter
|
||||
"""
|
||||
widget = widgets.StaticSelectMultiple
|
||||
|
||||
def __init__(self, model, *args, **kwargs):
|
||||
def get_choices():
|
||||
tags = model.tags.annotate(
|
||||
count=Count('extras_taggeditem_items')
|
||||
).order_by('name')
|
||||
return [
|
||||
(str(tag.slug), '{} ({})'.format(tag.name, tag.count)) for tag in tags
|
||||
]
|
||||
|
||||
# Choices are fetched each time the form is initialized
|
||||
super().__init__(label='Tags', choices=get_choices, required=False, *args, **kwargs)
|
||||
|
||||
|
||||
class LaxURLField(forms.URLField):
|
||||
"""
|
||||
Modifies Django's built-in URLField to remove the requirement for fully-qualified domain names
|
||||
(e.g. http://myserver/ is valid)
|
||||
"""
|
||||
default_validators = [EnhancedURLValidator()]
|
||||
|
||||
|
||||
class JSONField(_JSONField):
|
||||
"""
|
||||
Custom wrapper around Django's built-in JSONField to avoid presenting "null" as the default text.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.help_text:
|
||||
self.help_text = 'Enter context data in <a href="https://json.org/">JSON</a> format.'
|
||||
self.widget.attrs['placeholder'] = ''
|
||||
|
||||
def prepare_value(self, value):
|
||||
if isinstance(value, InvalidJSONInput):
|
||||
return value
|
||||
if value is None:
|
||||
return ''
|
||||
return json.dumps(value, sort_keys=True, indent=4)
|
||||
|
||||
|
||||
class MACAddressField(forms.Field):
|
||||
"""
|
||||
Validates a 48-bit MAC address.
|
||||
"""
|
||||
widget = forms.CharField
|
||||
default_error_messages = {
|
||||
'invalid': 'MAC address must be in EUI-48 format',
|
||||
}
|
||||
|
||||
def to_python(self, value):
|
||||
value = super().to_python(value)
|
||||
|
||||
# Validate MAC address format
|
||||
try:
|
||||
value = EUI(value.strip())
|
||||
except AddrFormatError:
|
||||
raise forms.ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
|
||||
return value
|
@ -10,7 +10,7 @@ from .widgets import APISelect, APISelectMultiple, ClearableFileInput, StaticSel
|
||||
__all__ = (
|
||||
'BootstrapMixin',
|
||||
'BulkEditForm',
|
||||
'BulkEditBaseForm',
|
||||
'BulkEditMixin',
|
||||
'BulkRenameForm',
|
||||
'ConfirmationForm',
|
||||
'CSVModelForm',
|
||||
@ -21,6 +21,10 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Mixins
|
||||
#
|
||||
|
||||
class BootstrapMixin:
|
||||
"""
|
||||
Add the base Bootstrap CSS classes to form elements.
|
||||
@ -61,6 +65,21 @@ class BootstrapMixin:
|
||||
field.widget.attrs['class'] = ' '.join((css, 'form-select')).strip()
|
||||
|
||||
|
||||
class BulkEditMixin:
|
||||
"""
|
||||
Base form for editing multiple objects in bulk
|
||||
"""
|
||||
nullable_fields = ()
|
||||
|
||||
def __init__(self, model, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.model = model
|
||||
|
||||
|
||||
#
|
||||
# Form classes
|
||||
#
|
||||
|
||||
class ReturnURLForm(forms.Form):
|
||||
"""
|
||||
Provides a hidden return URL field to control where the user is directed after the form is submitted.
|
||||
@ -75,21 +94,7 @@ class ConfirmationForm(BootstrapMixin, ReturnURLForm):
|
||||
confirm = forms.BooleanField(required=True, widget=forms.HiddenInput(), initial=True)
|
||||
|
||||
|
||||
class BulkEditBaseForm(forms.Form):
|
||||
"""
|
||||
Base form for editing multiple objects in bulk
|
||||
"""
|
||||
def __init__(self, model, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.model = model
|
||||
self.nullable_fields = []
|
||||
|
||||
# Copy any nullable fields defined in Meta
|
||||
if hasattr(self.Meta, 'nullable_fields'):
|
||||
self.nullable_fields = self.Meta.nullable_fields
|
||||
|
||||
|
||||
class BulkEditForm(BootstrapMixin, BulkEditBaseForm):
|
||||
class BulkEditForm(BootstrapMixin, BulkEditMixin, forms.Form):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -3,8 +3,8 @@ from django import forms
|
||||
from dcim.choices import InterfaceModeChoices
|
||||
from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
|
||||
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||
from ipam.models import VLAN
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BulkEditNullBooleanSelect, BulkRenameForm, CommentField, DynamicModelChoiceField,
|
||||
@ -23,7 +23,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ClusterTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ClusterTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ClusterType.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -33,11 +33,10 @@ class ClusterTypeBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['description']
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class ClusterGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ClusterGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -47,11 +46,10 @@ class ClusterGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['description']
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class ClusterBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class ClusterBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Cluster.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -89,13 +87,12 @@ class ClusterBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'group', 'site', 'comments', 'tenant',
|
||||
]
|
||||
nullable_fields = (
|
||||
'group', 'site', 'comments', 'tenant',
|
||||
)
|
||||
|
||||
|
||||
class VirtualMachineBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -144,13 +141,12 @@ class VirtualMachineBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
|
||||
]
|
||||
nullable_fields = (
|
||||
'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
|
||||
)
|
||||
|
||||
|
||||
class VMInterfaceBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class VMInterfaceBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=VMInterface.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
@ -197,10 +193,9 @@ class VMInterfaceBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = [
|
||||
'parent', 'bridge', 'mtu', 'description',
|
||||
]
|
||||
nullable_fields = (
|
||||
'parent', 'bridge', 'mtu', 'description',
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -1,6 +1,6 @@
|
||||
from dcim.choices import InterfaceModeChoices
|
||||
from dcim.models import DeviceRole, Platform, Site
|
||||
from extras.forms import CustomFieldModelCSVForm
|
||||
from netbox.forms import NetBoxModelCSVForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
|
||||
from virtualization.choices import *
|
||||
@ -15,7 +15,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ClusterTypeCSVForm(CustomFieldModelCSVForm):
|
||||
class ClusterTypeCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@ -23,7 +23,7 @@ class ClusterTypeCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'slug', 'description')
|
||||
|
||||
|
||||
class ClusterGroupCSVForm(CustomFieldModelCSVForm):
|
||||
class ClusterGroupCSVForm(NetBoxModelCSVForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@ -31,7 +31,7 @@ class ClusterGroupCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'slug', 'description')
|
||||
|
||||
|
||||
class ClusterCSVForm(CustomFieldModelCSVForm):
|
||||
class ClusterCSVForm(NetBoxModelCSVForm):
|
||||
type = CSVModelChoiceField(
|
||||
queryset=ClusterType.objects.all(),
|
||||
to_field_name='name',
|
||||
@ -61,7 +61,7 @@ class ClusterCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'type', 'group', 'site', 'comments')
|
||||
|
||||
|
||||
class VirtualMachineCSVForm(CustomFieldModelCSVForm):
|
||||
class VirtualMachineCSVForm(NetBoxModelCSVForm):
|
||||
status = CSVChoiceField(
|
||||
choices=VirtualMachineStatusChoices,
|
||||
help_text='Operational status of device'
|
||||
@ -99,7 +99,7 @@ class VirtualMachineCSVForm(CustomFieldModelCSVForm):
|
||||
)
|
||||
|
||||
|
||||
class VMInterfaceCSVForm(CustomFieldModelCSVForm):
|
||||
class VMInterfaceCSVForm(NetBoxModelCSVForm):
|
||||
virtual_machine = CSVModelChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
to_field_name='name'
|
||||
|
@ -2,7 +2,8 @@ from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
|
||||
from extras.forms import CustomFieldModelFilterForm, LocalConfigContextFilterForm
|
||||
from extras.forms import LocalConfigContextFilterForm
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import TenancyFilterForm
|
||||
from utilities.forms import (
|
||||
DynamicModelMultipleChoiceField, StaticSelect, StaticSelectMultiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
|
||||
@ -19,24 +20,24 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ClusterTypeFilterForm(CustomFieldModelFilterForm):
|
||||
class ClusterTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ClusterType
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ClusterGroupFilterForm(CustomFieldModelFilterForm):
|
||||
class ClusterGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ClusterGroup
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ClusterFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class ClusterFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Cluster
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['group_id', 'type_id'],
|
||||
['region_id', 'site_group_id', 'site_id'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('group_id', 'type_id')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
type_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ClusterType.objects.all(),
|
||||
required=False,
|
||||
@ -71,15 +72,15 @@ class ClusterFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldModelFilterForm):
|
||||
class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VirtualMachine
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['cluster_group_id', 'cluster_type_id', 'cluster_id'],
|
||||
['region_id', 'site_group_id', 'site_id'],
|
||||
['status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data'],
|
||||
['tenant_group_id', 'tenant_id'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
('Attriubtes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
cluster_group_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
required=False,
|
||||
@ -151,13 +152,13 @@ class VirtualMachineFilterForm(LocalConfigContextFilterForm, TenancyFilterForm,
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VMInterfaceFilterForm(CustomFieldModelFilterForm):
|
||||
class VMInterfaceFilterForm(NetBoxModelFilterSetForm):
|
||||
model = VMInterface
|
||||
field_groups = [
|
||||
['q', 'tag'],
|
||||
['cluster_id', 'virtual_machine_id'],
|
||||
['enabled', 'mac_address'],
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Virtual Machine', ('cluster_id', 'virtual_machine_id')),
|
||||
('Attributes', ('enabled', 'mac_address')),
|
||||
)
|
||||
cluster_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Cluster.objects.all(),
|
||||
required=False,
|
||||
|
@ -5,9 +5,9 @@ from django.core.exceptions import ValidationError
|
||||
from dcim.forms.common import InterfaceCommonForm
|
||||
from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
|
||||
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
|
||||
from extras.forms import CustomFieldModelForm
|
||||
from extras.models import Tag
|
||||
from ipam.models import IPAddress, VLAN, VLANGroup
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import (
|
||||
BootstrapMixin, CommentField, ConfirmationForm, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
@ -26,7 +26,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ClusterTypeForm(CustomFieldModelForm):
|
||||
class ClusterTypeForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -40,7 +40,7 @@ class ClusterTypeForm(CustomFieldModelForm):
|
||||
)
|
||||
|
||||
|
||||
class ClusterGroupForm(CustomFieldModelForm):
|
||||
class ClusterGroupForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
tags = DynamicModelMultipleChoiceField(
|
||||
queryset=Tag.objects.all(),
|
||||
@ -54,7 +54,7 @@ class ClusterGroupForm(CustomFieldModelForm):
|
||||
)
|
||||
|
||||
|
||||
class ClusterForm(TenancyForm, CustomFieldModelForm):
|
||||
class ClusterForm(TenancyForm, NetBoxModelForm):
|
||||
type = DynamicModelChoiceField(
|
||||
queryset=ClusterType.objects.all()
|
||||
)
|
||||
@ -90,15 +90,16 @@ class ClusterForm(TenancyForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Cluster', ('name', 'type', 'group', 'region', 'site_group', 'site', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Cluster
|
||||
fields = (
|
||||
'name', 'type', 'group', 'tenant', 'region', 'site_group', 'site', 'comments', 'tags',
|
||||
)
|
||||
fieldsets = (
|
||||
('Cluster', ('name', 'type', 'group', 'region', 'site_group', 'site', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
|
||||
|
||||
class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
|
||||
@ -171,7 +172,7 @@ class ClusterRemoveDevicesForm(ConfirmationForm):
|
||||
)
|
||||
|
||||
|
||||
class VirtualMachineForm(TenancyForm, CustomFieldModelForm):
|
||||
class VirtualMachineForm(TenancyForm, NetBoxModelForm):
|
||||
cluster_group = DynamicModelChoiceField(
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
required=False,
|
||||
@ -206,20 +207,21 @@ class VirtualMachineForm(TenancyForm, CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Virtual Machine', ('name', 'role', 'status', 'tags')),
|
||||
('Cluster', ('cluster_group', 'cluster')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
('Management', ('platform', 'primary_ip4', 'primary_ip6')),
|
||||
('Resources', ('vcpus', 'memory', 'disk')),
|
||||
('Config Context', ('local_context_data',)),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VirtualMachine
|
||||
fields = [
|
||||
'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4',
|
||||
'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
|
||||
]
|
||||
fieldsets = (
|
||||
('Virtual Machine', ('name', 'role', 'status', 'tags')),
|
||||
('Cluster', ('cluster_group', 'cluster')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
('Management', ('platform', 'primary_ip4', 'primary_ip6')),
|
||||
('Resources', ('vcpus', 'memory', 'disk')),
|
||||
('Config Context', ('local_context_data',)),
|
||||
)
|
||||
help_texts = {
|
||||
'local_context_data': "Local config context data overwrites all sources contexts in the final rendered "
|
||||
"config context",
|
||||
@ -271,7 +273,7 @@ class VirtualMachineForm(TenancyForm, CustomFieldModelForm):
|
||||
self.fields['primary_ip6'].widget.attrs['readonly'] = True
|
||||
|
||||
|
||||
class VMInterfaceForm(InterfaceCommonForm, CustomFieldModelForm):
|
||||
class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=VMInterface.objects.all(),
|
||||
required=False,
|
||||
|
@ -1,8 +1,8 @@
|
||||
from django import forms
|
||||
|
||||
from dcim.choices import LinkStatusChoices
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldModelBulkEditForm
|
||||
from ipam.models import VLAN
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from utilities.forms import add_blank_choice, DynamicModelChoiceField
|
||||
from wireless.choices import *
|
||||
from wireless.constants import SSID_MAX_LENGTH
|
||||
@ -15,7 +15,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class WirelessLANGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class WirelessLANGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=WirelessLANGroup.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -29,11 +29,10 @@ class WirelessLANGroupBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditFo
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['parent', 'description']
|
||||
nullable_fields = ('parent', 'description')
|
||||
|
||||
|
||||
class WirelessLANBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class WirelessLANBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=WirelessLAN.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -68,11 +67,12 @@ class WirelessLANBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
label='Pre-shared key'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['ssid', 'group', 'vlan', 'description', 'auth_type', 'auth_cipher', 'auth_psk']
|
||||
nullable_fields = (
|
||||
'ssid', 'group', 'vlan', 'description', 'auth_type', 'auth_cipher', 'auth_psk',
|
||||
)
|
||||
|
||||
|
||||
class WirelessLinkBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
class WirelessLinkBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=WirelessLink.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
@ -102,5 +102,6 @@ class WirelessLinkBulkEditForm(AddRemoveTagsForm, CustomFieldModelBulkEditForm):
|
||||
label='Pre-shared key'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['ssid', 'description', 'auth_type', 'auth_cipher', 'auth_psk']
|
||||
nullable_fields = (
|
||||
'ssid', 'description', 'auth_type', 'auth_cipher', 'auth_psk',
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
from dcim.choices import LinkStatusChoices
|
||||
from dcim.models import Interface
|
||||
from extras.forms import CustomFieldModelCSVForm
|
||||
from ipam.models import VLAN
|
||||
from netbox.forms import NetBoxModelCSVForm
|
||||
from utilities.forms import CSVChoiceField, CSVModelChoiceField, SlugField
|
||||
from wireless.choices import *
|
||||
from wireless.models import *
|
||||
@ -13,7 +13,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class WirelessLANGroupCSVForm(CustomFieldModelCSVForm):
|
||||
class WirelessLANGroupCSVForm(NetBoxModelCSVForm):
|
||||
parent = CSVModelChoiceField(
|
||||
queryset=WirelessLANGroup.objects.all(),
|
||||
required=False,
|
||||
@ -27,7 +27,7 @@ class WirelessLANGroupCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('name', 'slug', 'parent', 'description')
|
||||
|
||||
|
||||
class WirelessLANCSVForm(CustomFieldModelCSVForm):
|
||||
class WirelessLANCSVForm(NetBoxModelCSVForm):
|
||||
group = CSVModelChoiceField(
|
||||
queryset=WirelessLANGroup.objects.all(),
|
||||
required=False,
|
||||
@ -56,7 +56,7 @@ class WirelessLANCSVForm(CustomFieldModelCSVForm):
|
||||
fields = ('ssid', 'group', 'description', 'vlan', 'auth_type', 'auth_cipher', 'auth_psk')
|
||||
|
||||
|
||||
class WirelessLinkCSVForm(CustomFieldModelCSVForm):
|
||||
class WirelessLinkCSVForm(NetBoxModelCSVForm):
|
||||
status = CSVChoiceField(
|
||||
choices=LinkStatusChoices,
|
||||
help_text='Connection status'
|
||||
|
@ -2,7 +2,7 @@ from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from dcim.choices import LinkStatusChoices
|
||||
from extras.forms import CustomFieldModelFilterForm
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from utilities.forms import add_blank_choice, DynamicModelMultipleChoiceField, StaticSelect, TagFilterField
|
||||
from wireless.choices import *
|
||||
from wireless.models import *
|
||||
@ -14,7 +14,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class WirelessLANGroupFilterForm(CustomFieldModelFilterForm):
|
||||
class WirelessLANGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
model = WirelessLANGroup
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
queryset=WirelessLANGroup.objects.all(),
|
||||
@ -24,12 +24,13 @@ class WirelessLANGroupFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class WirelessLANFilterForm(CustomFieldModelFilterForm):
|
||||
class WirelessLANFilterForm(NetBoxModelFilterSetForm):
|
||||
model = WirelessLAN
|
||||
field_groups = [
|
||||
('q', 'tag'),
|
||||
('group_id',),
|
||||
]
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
('Attributes', ('ssid', 'group_id',)),
|
||||
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
|
||||
)
|
||||
ssid = forms.CharField(
|
||||
required=False,
|
||||
label='SSID'
|
||||
@ -56,7 +57,7 @@ class WirelessLANFilterForm(CustomFieldModelFilterForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class WirelessLinkFilterForm(CustomFieldModelFilterForm):
|
||||
class WirelessLinkFilterForm(NetBoxModelFilterSetForm):
|
||||
model = WirelessLink
|
||||
ssid = forms.CharField(
|
||||
required=False,
|
||||
|
@ -1,7 +1,7 @@
|
||||
from dcim.models import Device, Interface, Location, Site
|
||||
from extras.forms import CustomFieldModelForm
|
||||
from extras.models import Tag
|
||||
from ipam.models import VLAN
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, StaticSelect
|
||||
from wireless.models import *
|
||||
|
||||
@ -12,7 +12,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class WirelessLANGroupForm(CustomFieldModelForm):
|
||||
class WirelessLANGroupForm(NetBoxModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=WirelessLANGroup.objects.all(),
|
||||
required=False
|
||||
@ -30,7 +30,7 @@ class WirelessLANGroupForm(CustomFieldModelForm):
|
||||
]
|
||||
|
||||
|
||||
class WirelessLANForm(CustomFieldModelForm):
|
||||
class WirelessLANForm(NetBoxModelForm):
|
||||
group = DynamicModelChoiceField(
|
||||
queryset=WirelessLANGroup.objects.all(),
|
||||
required=False
|
||||
@ -45,23 +45,24 @@ class WirelessLANForm(CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Wireless LAN', ('ssid', 'group', 'description', 'tags')),
|
||||
('VLAN', ('vlan',)),
|
||||
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = WirelessLAN
|
||||
fields = [
|
||||
'ssid', 'group', 'description', 'vlan', 'auth_type', 'auth_cipher', 'auth_psk', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Wireless LAN', ('ssid', 'group', 'description', 'tags')),
|
||||
('VLAN', ('vlan',)),
|
||||
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
|
||||
)
|
||||
widgets = {
|
||||
'auth_type': StaticSelect,
|
||||
'auth_cipher': StaticSelect,
|
||||
}
|
||||
|
||||
|
||||
class WirelessLinkForm(CustomFieldModelForm):
|
||||
class WirelessLinkForm(NetBoxModelForm):
|
||||
site_a = DynamicModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
@ -141,18 +142,19 @@ class WirelessLinkForm(CustomFieldModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Side A', ('site_a', 'location_a', 'device_a', 'interface_a')),
|
||||
('Side B', ('site_b', 'location_b', 'device_b', 'interface_b')),
|
||||
('Link', ('status', 'ssid', 'description', 'tags')),
|
||||
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = WirelessLink
|
||||
fields = [
|
||||
'site_a', 'location_a', 'device_a', 'interface_a', 'site_b', 'location_b', 'device_b', 'interface_b',
|
||||
'status', 'ssid', 'description', 'auth_type', 'auth_cipher', 'auth_psk', 'tags',
|
||||
]
|
||||
fieldsets = (
|
||||
('Side A', ('site_a', 'location_a', 'device_a', 'interface_a')),
|
||||
('Side B', ('site_b', 'location_b', 'device_b', 'interface_b')),
|
||||
('Link', ('status', 'ssid', 'description', 'tags')),
|
||||
('Authentication', ('auth_type', 'auth_cipher', 'auth_psk')),
|
||||
)
|
||||
widgets = {
|
||||
'status': StaticSelect,
|
||||
'auth_type': StaticSelect,
|
||||
|
Loading…
Reference in New Issue
Block a user